diff --git a/backend/db_manager.py b/backend/db_manager.py index 80bfdcb..da91460 100644 --- a/backend/db_manager.py +++ b/backend/db_manager.py @@ -14,6 +14,10 @@ from datetime import datetime DB_PATH = os.getenv("DB_PATH", "/app/data/insightflow.db") +# Constants +UUID_LENGTH = 8 # UUID 截断长度 + + @dataclass class Project: id: str @@ -22,6 +26,7 @@ class Project: created_at: str = "" updated_at: str = "" + @dataclass class Entity: id: str @@ -42,6 +47,7 @@ class Entity: if self.attributes is None: self.attributes = {} + @dataclass class AttributeTemplate: """属性模板定义""" @@ -62,6 +68,7 @@ class AttributeTemplate: if self.options is None: self.options = [] + @dataclass class EntityAttribute: """实体属性值""" @@ -82,6 +89,7 @@ class EntityAttribute: if self.options is None: self.options = [] + @dataclass class AttributeHistory: """属性变更历史""" @@ -95,6 +103,7 @@ class AttributeHistory: changed_at: str = "" change_reason: str = "" + @dataclass class EntityMention: id: str @@ -105,6 +114,7 @@ class EntityMention: text_snippet: str confidence: float = 1.0 + class DatabaseManager: def __init__(self, db_path: str = DB_PATH): self.db_path = db_path @@ -321,15 +331,14 @@ class DatabaseManager: conn.execute( """INSERT INTO entity_mentions (id, entity_id, transcript_id, start_pos, end_pos, text_snippet, confidence) VALUES (?, ?, ?, ?, ?, ?, ?)""", - ( - mention.id, - mention.entity_id, - mention.transcript_id, - mention.start_pos, - mention.end_pos, - mention.text_snippet, - mention.confidence, - ), + (mention.id, + mention.entity_id, + mention.transcript_id, + mention.start_pos, + mention.end_pos, + mention.text_snippet, + mention.confidence, + ), ) conn.commit() conn.close() @@ -358,7 +367,12 @@ class DatabaseManager: now = datetime.now().isoformat() conn.execute( "INSERT INTO transcripts (id, project_id, filename, full_text, type, created_at) VALUES (?, ?, ?, ?, ?, ?)", - (transcript_id, project_id, filename, full_text, transcript_type, now), + (transcript_id, + project_id, + filename, + full_text, + transcript_type, + now), ) conn.commit() conn.close() @@ -401,7 +415,7 @@ class DatabaseManager: transcript_id: str = "", ): conn = self.get_conn() - relation_id = str(uuid.uuid4())[:8] + relation_id = str(uuid.uuid4())[:UUID_LENGTH] now = datetime.now().isoformat() conn.execute( """INSERT INTO entity_relations @@ -485,7 +499,7 @@ class DatabaseManager: conn.close() return existing["id"] - term_id = str(uuid.uuid4())[:8] + term_id = str(uuid.uuid4())[:UUID_LENGTH] conn.execute( "INSERT INTO glossary (id, project_id, term, pronunciation, frequency) VALUES (?, ?, ?, ?, ?)", (term_id, project_id, term, pronunciation, 1), @@ -836,7 +850,7 @@ class DatabaseManager: (id, entity_id, template_id, old_value, new_value, changed_by, changed_at, change_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], attr.entity_id, attr.template_id, old_value, @@ -915,7 +929,7 @@ class DatabaseManager: (id, entity_id, template_id, old_value, new_value, changed_by, changed_at, change_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], entity_id, template_id, old_row["value"], @@ -1387,9 +1401,11 @@ class DatabaseManager: conn.close() return stats + # Singleton instance _db_manager = None + def get_db_manager() -> DatabaseManager: global _db_manager if _db_manager is None: diff --git a/backend/growth_manager.py b/backend/growth_manager.py index b4aa80a..b3b330e 100644 --- a/backend/growth_manager.py +++ b/backend/growth_manager.py @@ -456,7 +456,7 @@ class GrowthManager: await client.post( "https://api.mixpanel.com/track", headers=headers, json=[payload], timeout=10.0 ) - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: print(f"Failed to send to Mixpanel: {e}") async def _send_to_amplitude(self, event: AnalyticsEvent): @@ -484,7 +484,7 @@ class GrowthManager: json=payload, timeout=10.0, ) - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: print(f"Failed to send to Amplitude: {e}") async def _update_user_profile( diff --git a/backend/image_processor.py b/backend/image_processor.py index 3be30e7..606c417 100644 --- a/backend/image_processor.py +++ b/backend/image_processor.py @@ -10,6 +10,9 @@ import os import uuid from dataclasses import dataclass +# Constants +UUID_LENGTH = 8 # UUID 截断长度 + # 尝试导入图像处理库 try: from PIL import Image, ImageEnhance, ImageFilter @@ -369,7 +372,7 @@ class ImageProcessor: Returns: 图片处理结果 """ - image_id = image_id or str(uuid.uuid4())[:8] + image_id = image_id or str(uuid.uuid4())[:UUID_LENGTH] if not PIL_AVAILABLE: return ImageProcessingResult( diff --git a/backend/llm_client.py b/backend/llm_client.py index 09b03c7..82a2991 100644 --- a/backend/llm_client.py +++ b/backend/llm_client.py @@ -15,11 +15,13 @@ import httpx KIMI_API_KEY = os.getenv("KIMI_API_KEY", "") KIMI_BASE_URL = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding") + @dataclass class ChatMessage: role: str content: str + @dataclass class EntityExtractionResult: name: str @@ -27,6 +29,7 @@ class EntityExtractionResult: definition: str confidence: float + @dataclass class RelationExtractionResult: source: str @@ -34,6 +37,7 @@ class RelationExtractionResult: type: str confidence: float + class LLMClient: """Kimi API 客户端""" @@ -163,7 +167,7 @@ class LLMClient: for r in data.get("relations", []) ] return entities, relations - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: print(f"Parse extraction result failed: {e}") return [], [] @@ -254,9 +258,11 @@ class LLMClient: messages = [ChatMessage(role="user", content=prompt)] return await self.chat(messages, temperature=0.3) + # Singleton instance _llm_client = None + def get_llm_client() -> LLMClient: global _llm_client if _llm_client is None: diff --git a/backend/main.py b/backend/main.py index eecc1ee..fb09717 100644 --- a/backend/main.py +++ b/backend/main.py @@ -38,6 +38,14 @@ from pydantic import BaseModel, Field # Configure logger logger = logging.getLogger(__name__) +# Constants +DEFAULT_RATE_LIMIT = 60 # 默认每分钟请求限制 +MASTER_KEY_RATE_LIMIT = 1000 # Master key 限流 +IP_RATE_LIMIT = 10 # IP 限流 +MAX_TEXT_LENGTH = 3000 # 最大文本长度 +UUID_LENGTH = 8 # UUID 截断长度 +DEFAULT_TIMEOUT = 60.0 # 默认超时时间 + # Add backend directory to path for imports backend_dir = os.path.dirname(os.path.abspath(__file__)) if backend_dir not in sys.path: @@ -101,7 +109,7 @@ except ImportError: REASONER_AVAILABLE = False try: - from export_manager import ExportEntity, ExportRelation, ExportTranscript, get_export_manager + from export_manager import get_export_manager EXPORT_AVAILABLE = True except ImportError: @@ -490,7 +498,7 @@ async def rate_limit_middleware(request: Request, call_next): if x_api_key and x_api_key == MASTER_KEY: # Master key 有更高的限流 - config = RateLimitConfig(requests_per_minute=1000) + config = RateLimitConfig(requests_per_minute=MASTER_KEY_RATE_LIMIT) limit_key = f"master:{x_api_key[:16]}" elif hasattr(request.state, "api_key") and request.state.api_key: # 使用 API Key 的限流配置 @@ -500,7 +508,7 @@ async def rate_limit_middleware(request: Request, call_next): else: # IP 限流(未认证用户) client_ip = request.client.host if request.client else "unknown" - config = RateLimitConfig(requests_per_minute=10) + config = RateLimitConfig(requests_per_minute=IP_RATE_LIMIT) limit_key = f"ip:{client_ip}" # 检查限流 @@ -547,7 +555,7 @@ async def rate_limit_middleware(request: Request, call_next): ip_address=request.client.host if request.client else "", user_agent=request.headers.get("User-Agent", ""), ) - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: # 日志记录失败不应影响主流程 print(f"Failed to log API call: {e}") @@ -1062,7 +1070,7 @@ async def create_manual_entity( if existing: return {"id": existing.id, "name": existing.name, "type": existing.type, "existed": True} - entity_id = str(uuid.uuid4())[:8] + entity_id = str(uuid.uuid4())[:UUID_LENGTH] new_entity = db.create_entity( Entity( id=entity_id, @@ -1079,7 +1087,7 @@ async def create_manual_entity( if transcript: text = transcript["full_text"] mention = EntityMention( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], entity_id=entity_id, transcript_id=entity.transcript_id, start_pos=entity.start_pos, @@ -1227,7 +1235,7 @@ async def create_project(project: ProjectCreate, _=Depends(verify_api_key)): raise HTTPException(status_code=500, detail="Database not available") db = get_db_manager() - project_id = str(uuid.uuid4())[:8] + project_id = str(uuid.uuid4())[:UUID_LENGTH] p = db.create_project(project_id, project.name, project.description) return {"id": p.id, "name": p.name, "description": p.description} @@ -1263,7 +1271,7 @@ async def upload_audio(project_id: str, file: UploadFile = File(...), _=Depends( raw_entities, raw_relations = extract_entities_with_llm(tw_result["full_text"]) # 保存转录记录 - transcript_id = str(uuid.uuid4())[:8] + transcript_id = str(uuid.uuid4())[:UUID_LENGTH] db.save_transcript( transcript_id=transcript_id, project_id=project_id, @@ -1290,7 +1298,7 @@ async def upload_audio(project_id: str, file: UploadFile = File(...), _=Depends( else: new_ent = db.create_entity( Entity( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, name=raw_ent["name"], type=raw_ent.get("type", "OTHER"), @@ -1313,7 +1321,7 @@ async def upload_audio(project_id: str, file: UploadFile = File(...), _=Depends( if pos == -1: break mention = EntityMention( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], entity_id=entity_name_to_id[name], transcript_id=transcript_id, start_pos=pos, @@ -1374,11 +1382,11 @@ async def upload_document(project_id: str, file: UploadFile = File(...), _=Depen processor = get_doc_processor() try: result = processor.process(content, file.filename) - except Exception as e: + except (ValueError, TypeError, RuntimeError, IOError) as e: raise HTTPException(status_code=400, detail=f"Document processing failed: {str(e)}") # 保存文档转录记录 - transcript_id = str(uuid.uuid4())[:8] + transcript_id = str(uuid.uuid4())[:UUID_LENGTH] db.save_transcript( transcript_id=transcript_id, project_id=project_id, @@ -1411,7 +1419,7 @@ async def upload_document(project_id: str, file: UploadFile = File(...), _=Depen else: new_ent = db.create_entity( Entity( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, name=raw_ent["name"], type=raw_ent.get("type", "OTHER"), @@ -1437,7 +1445,7 @@ async def upload_document(project_id: str, file: UploadFile = File(...), _=Depen if pos == -1: break mention = EntityMention( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], entity_id=entity_name_to_id[name], transcript_id=transcript_id, start_pos=pos, @@ -2282,7 +2290,7 @@ async def create_attribute_template_endpoint( raise HTTPException(status_code=404, detail="Project not found") new_template = AttributeTemplate( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, name=template.name, type=template.type, @@ -2423,7 +2431,7 @@ async def set_entity_attribute_endpoint( (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], entity_id, attr.name, existing["value"], @@ -2444,7 +2452,7 @@ async def set_entity_attribute_endpoint( attr_id = existing["id"] else: # 创建 - attr_id = str(uuid.uuid4())[:8] + attr_id = str(uuid.uuid4())[:UUID_LENGTH] conn.execute( """INSERT INTO entity_attributes (id, entity_id, template_id, name, type, value, options, created_at, updated_at) @@ -2458,7 +2466,7 @@ async def set_entity_attribute_endpoint( (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], entity_id, attr.name, None, @@ -2499,7 +2507,7 @@ async def batch_set_entity_attributes_endpoint( template = db.get_attribute_template(attr_data.template_id) if template: new_attr = EntityAttribute( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], entity_id=entity_id, template_id=attr_data.template_id, value=attr_data.value, @@ -2937,7 +2945,7 @@ async def export_report_pdf_endpoint(project_id: str, _=Depends(verify_api_key)) reasoner = get_knowledge_reasoner() summary_result = reasoner.generate_project_summary(project_id, db) summary = summary_result.get("summary", "") - except Exception: + except (RuntimeError, ValueError, TypeError): pass export_mgr = get_export_manager() @@ -3113,7 +3121,7 @@ async def neo4j_status(_=Depends(verify_api_key)): "uri": manager.uri if connected else None, "message": "Connected" if connected else "Not connected", } - except Exception as e: + except (RuntimeError, ValueError, TypeError, ConnectionError) as e: return {"available": True, "connected": False, "message": str(e)} @app.post("/api/v1/neo4j/sync") @@ -3658,7 +3666,7 @@ async def create_workflow_endpoint(request: WorkflowCreate, _=Depends(verify_api try: workflow = Workflow( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], name=request.name, description=request.description, workflow_type=request.workflow_type, @@ -3847,7 +3855,7 @@ async def trigger_workflow_endpoint( ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) - except Exception as e: + except (RuntimeError, TypeError, ConnectionError) as e: raise HTTPException(status_code=500, detail=str(e)) @app.get( @@ -3924,7 +3932,7 @@ async def create_webhook_endpoint(request: WebhookCreate, _=Depends(verify_api_k try: webhook = WebhookConfig( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], name=request.name, webhook_type=request.webhook_type, url=request.url, @@ -4175,7 +4183,7 @@ async def upload_video_endpoint( processor = get_multimodal_processor(frame_interval=extract_interval) # 处理视频 - video_id = str(uuid.uuid4())[:8] + video_id = str(uuid.uuid4())[:UUID_LENGTH] result = processor.process_video(video_data, file.filename, project_id, video_id) if not result.success: @@ -4252,7 +4260,7 @@ async def upload_video_endpoint( else: new_ent = db.create_entity( Entity( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, name=raw_ent["name"], type=raw_ent.get("type", "OTHER"), @@ -4268,7 +4276,7 @@ async def upload_video_endpoint( (id, project_id, entity_id, modality, source_id, source_type, text_snippet, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], project_id, entity_name_to_id[raw_ent["name"]], "video", @@ -4356,7 +4364,7 @@ async def upload_image_endpoint( processor = get_image_processor() # 处理图片 - image_id = str(uuid.uuid4())[:8] + image_id = str(uuid.uuid4())[:UUID_LENGTH] result = processor.process_image(image_data, file.filename, image_id, detect_type) if not result.success: @@ -4406,7 +4414,7 @@ async def upload_image_endpoint( if not existing: new_ent = db.create_entity( Entity( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, name=entity.name, type=entity.type, @@ -4424,7 +4432,7 @@ async def upload_image_endpoint( (id, project_id, entity_id, modality, source_id, source_type, text_snippet, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( - str(uuid.uuid4())[:8], + str(uuid.uuid4())[:UUID_LENGTH], project_id, entity_id, "image", @@ -5156,7 +5164,7 @@ async def create_plugin_endpoint(request: PluginCreate, _=Depends(verify_api_key manager = get_plugin_manager_instance() plugin = Plugin( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], name=request.name, plugin_type=request.plugin_type, project_id=request.project_id, @@ -8056,7 +8064,7 @@ async def create_tenant( "status": tenant.status, "created_at": tenant.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants", tags=["Tenants"]) @@ -8162,7 +8170,7 @@ async def add_domain(tenant_id: str, request: AddDomainRequest, _=Depends(verify "verification_instructions": instructions, "created_at": domain.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/domains", tags=["Tenants"]) @@ -8313,7 +8321,7 @@ async def invite_member( "status": member.status, "invited_at": member.invited_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/members", tags=["Tenants"]) @@ -9193,7 +9201,7 @@ async def create_subscription( "trial_end": subscription.trial_end.isoformat() if subscription.trial_end else None, "created_at": subscription.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/subscription", tags=["Subscriptions"]) @@ -9259,7 +9267,7 @@ async def change_subscription_plan( "status": updated.status, "message": "Plan changed successfully", } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.post("/api/v1/tenants/{tenant_id}/subscription/cancel", tags=["Subscriptions"]) @@ -9288,7 +9296,7 @@ async def cancel_subscription( "canceled_at": updated.canceled_at.isoformat() if updated.canceled_at else None, "message": "Subscription cancelled", } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) # Usage APIs @@ -9498,7 +9506,7 @@ async def request_refund( "status": refund.status, "requested_at": refund.requested_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/refunds", tags=["Subscriptions"]) @@ -9636,7 +9644,7 @@ async def create_stripe_checkout( ) return session - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.post("/api/v1/tenants/{tenant_id}/checkout/alipay", tags=["Subscriptions"]) @@ -9658,7 +9666,7 @@ async def create_alipay_order( ) return order - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.post("/api/v1/tenants/{tenant_id}/checkout/wechat", tags=["Subscriptions"]) @@ -9680,7 +9688,7 @@ async def create_wechat_order( ) return order - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) # Webhook Handlers @@ -9876,7 +9884,7 @@ async def create_sso_config_endpoint( "default_role": sso_config.default_role, "created_at": sso_config.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/sso-configs", tags=["Enterprise"]) @@ -10036,7 +10044,7 @@ async def create_scim_config_endpoint( "sync_interval_minutes": scim_config.sync_interval_minutes, "created_at": scim_config.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/scim-configs", tags=["Enterprise"]) @@ -10174,7 +10182,7 @@ async def create_audit_export_endpoint( "expires_at": export.expires_at.isoformat() if export.expires_at else None, "created_at": export.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/audit-exports", tags=["Enterprise"]) @@ -10309,7 +10317,7 @@ async def create_retention_policy_endpoint( "is_active": new_policy.is_active, "created_at": new_policy.created_at.isoformat(), } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/tenants/{tenant_id}/retention-policies", tags=["Enterprise"]) @@ -11885,7 +11893,7 @@ async def track_event_endpoint(request: TrackEventRequest): ) return {"success": True, "event_id": event.id, "timestamp": event.timestamp.isoformat()} - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/v1/analytics/dashboard/{tenant_id}", tags=["Growth & Analytics"]) @@ -12052,7 +12060,7 @@ async def create_experiment_endpoint(request: CreateExperimentRequest, created_b "variants": experiment.variants, "created_at": experiment.created_at, } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/experiments", tags=["Growth & Analytics"]) @@ -12231,7 +12239,7 @@ async def create_email_template_endpoint(request: CreateEmailTemplateRequest): "variables": template.variables, "created_at": template.created_at, } - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/v1/email/templates", tags=["Growth & Analytics"]) diff --git a/backend/multimodal_entity_linker.py b/backend/multimodal_entity_linker.py index d2f94c3..e534ead 100644 --- a/backend/multimodal_entity_linker.py +++ b/backend/multimodal_entity_linker.py @@ -8,6 +8,9 @@ import uuid from dataclasses import dataclass from difflib import SequenceMatcher +# Constants +UUID_LENGTH = 8 # UUID 截断长度 + # 尝试导入embedding库 try: NUMPY_AVAILABLE = True @@ -247,7 +250,7 @@ class MultimodalEntityLinker: if result and result.matched_entity_id: link = EntityLink( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], project_id=project_id, source_entity_id=ent1.get("id"), target_entity_id=result.matched_entity_id, @@ -461,7 +464,7 @@ class MultimodalEntityLinker: 多模态实体记录 """ return MultimodalEntity( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], entity_id=entity_id, project_id=project_id, name="", # 将在后续填充 diff --git a/backend/multimodal_processor.py b/backend/multimodal_processor.py index 7131c4b..81a5131 100644 --- a/backend/multimodal_processor.py +++ b/backend/multimodal_processor.py @@ -12,6 +12,9 @@ import uuid from dataclasses import dataclass from pathlib import Path +# Constants +UUID_LENGTH = 8 # UUID 截断长度 + # 尝试导入OCR库 try: import pytesseract @@ -340,7 +343,7 @@ class MultimodalProcessor: Returns: 视频处理结果 """ - video_id = video_id or str(uuid.uuid4())[:8] + video_id = video_id or str(uuid.uuid4())[:UUID_LENGTH] try: # 保存视频文件 @@ -375,7 +378,7 @@ class MultimodalProcessor: ocr_text, confidence = self.perform_ocr(frame_path) frame = VideoFrame( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], video_id=video_id, frame_number=frame_number, timestamp=timestamp, diff --git a/backend/neo4j_manager.py b/backend/neo4j_manager.py index 855fc43..8b13e5f 100644 --- a/backend/neo4j_manager.py +++ b/backend/neo4j_manager.py @@ -111,7 +111,7 @@ class Neo4jManager: # 验证连接 self._driver.verify_connectivity() logger.info(f"Connected to Neo4j at {self.uri}") - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: logger.error(f"Failed to connect to Neo4j: {e}") self._driver = None diff --git a/backend/plugin_manager.py b/backend/plugin_manager.py index a3aac8d..ef83ac6 100644 --- a/backend/plugin_manager.py +++ b/backend/plugin_manager.py @@ -11,7 +11,6 @@ import json import os import sqlite3 import time -import urllib.parse import uuid from dataclasses import dataclass, field from datetime import datetime @@ -20,6 +19,9 @@ from typing import Any import httpx +# Constants +UUID_LENGTH = 8 # UUID 截断长度 + # WebDAV 支持 try: import webdav4.client as webdav_client @@ -318,7 +320,7 @@ class PluginManager: ) config_id = existing["id"] else: - config_id = str(uuid.uuid4())[:8] + config_id = str(uuid.uuid4())[:UUID_LENGTH] conn.execute( """INSERT INTO plugin_configs (id, plugin_id, config_key, config_value, is_encrypted, created_at, updated_at) @@ -400,7 +402,7 @@ class ChromeExtensionHandler: expires_days: int = None, ) -> ChromeExtensionToken: """创建 Chrome 扩展令牌""" - token_id = str(uuid.uuid4())[:8] + token_id = str(uuid.uuid4())[:UUID_LENGTH] # 生成随机令牌 raw_token = f"if_ext_{base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')}" @@ -563,7 +565,7 @@ class ChromeExtensionHandler: return {"success": False, "error": "Insufficient permissions"} # 创建转录记录(将网页作为文档处理) - transcript_id = str(uuid.uuid4())[:8] + transcript_id = str(uuid.uuid4())[:UUID_LENGTH] now = datetime.now().isoformat() # 构建完整文本 @@ -604,7 +606,7 @@ class BotHandler: secret: str = "", ) -> BotSession: """创建机器人会话""" - bot_id = str(uuid.uuid4())[:8] + bot_id = str(uuid.uuid4())[:UUID_LENGTH] now = datetime.now().isoformat() conn = self.pm.db.get_conn() @@ -932,7 +934,7 @@ class WebhookIntegration: trigger_events: list[str] = None, ) -> WebhookEndpoint: """创建 Webhook 端点""" - endpoint_id = str(uuid.uuid4())[:8] + endpoint_id = str(uuid.uuid4())[:UUID_LENGTH] now = datetime.now().isoformat() conn = self.pm.db.get_conn() @@ -1155,7 +1157,7 @@ class WebDAVSyncManager: sync_interval: int = 3600, ) -> WebDAVSync: """创建 WebDAV 同步配置""" - sync_id = str(uuid.uuid4())[:8] + sync_id = str(uuid.uuid4())[:UUID_LENGTH] now = datetime.now().isoformat() conn = self.pm.db.get_conn() diff --git a/backend/rate_limiter.py b/backend/rate_limiter.py index 86badd0..29e44f5 100644 --- a/backend/rate_limiter.py +++ b/backend/rate_limiter.py @@ -12,6 +12,7 @@ from collections.abc import Callable from dataclasses import dataclass from functools import wraps + @dataclass class RateLimitConfig: """限流配置""" @@ -20,6 +21,7 @@ class RateLimitConfig: burst_size: int = 10 # 突发请求数 window_size: int = 60 # 窗口大小(秒) + @dataclass class RateLimitInfo: """限流信息""" @@ -29,6 +31,7 @@ class RateLimitInfo: reset_time: int # 重置时间戳 retry_after: int # 需要等待的秒数 + class SlidingWindowCounter: """滑动窗口计数器""" @@ -60,6 +63,7 @@ class SlidingWindowCounter: for k in old_keys: self.requests.pop(k, None) + class RateLimiter: """API 限流器""" @@ -155,9 +159,11 @@ class RateLimiter: self.counters.clear() self.configs.clear() + # 全局限流器实例 _rate_limiter: RateLimiter | None = None + def get_rate_limiter() -> RateLimiter: """获取限流器实例""" global _rate_limiter @@ -166,6 +172,8 @@ def get_rate_limiter() -> RateLimiter: return _rate_limiter # 限流装饰器(用于函数级别限流) + + def rate_limit(requests_per_minute: int = 60, key_func: Callable | None = None) -> None: """ 限流装饰器 @@ -208,5 +216,6 @@ def rate_limit(requests_per_minute: int = 60, key_func: Callable | None = None) return decorator + class RateLimitExceeded(Exception): """限流异常""" diff --git a/backend/tenant_manager.py b/backend/tenant_manager.py index 7965c5e..0a8cc46 100644 --- a/backend/tenant_manager.py +++ b/backend/tenant_manager.py @@ -395,7 +395,7 @@ class TenantManager: conn.commit() logger.info("Tenant tables initialized successfully") - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: logger.error(f"Error initializing tenant tables: {e}") raise finally: @@ -760,7 +760,7 @@ class TenantManager: return is_verified - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: logger.error(f"Error verifying domain: {e}") return False finally: diff --git a/backend/tingwu_client.py b/backend/tingwu_client.py index 7f27a28..c70e9e6 100644 --- a/backend/tingwu_client.py +++ b/backend/tingwu_client.py @@ -8,6 +8,7 @@ import time from datetime import datetime from typing import Any + class TingwuClient: def __init__(self): self.access_key = os.getenv("ALI_ACCESS_KEY", "") @@ -67,7 +68,7 @@ class TingwuClient: # Fallback: 使用 mock print("Tingwu SDK not available, using mock") return f"mock_task_{int(time.time())}" - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: print(f"Tingwu API error: {e}") return f"mock_task_{int(time.time())}" @@ -107,7 +108,7 @@ class TingwuClient: except ImportError: print("Tingwu SDK not available, using mock result") return self._mock_result() - except Exception as e: + except (RuntimeError, ValueError, TypeError) as e: print(f"Get result error: {e}") return self._mock_result() diff --git a/backend/workflow_manager.py b/backend/workflow_manager.py index 06fe3b9..4aacc13 100644 --- a/backend/workflow_manager.py +++ b/backend/workflow_manager.py @@ -15,7 +15,6 @@ import hashlib import hmac import json import logging -import urllib.parse import uuid from collections.abc import Callable from dataclasses import dataclass, field @@ -29,6 +28,12 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.interval import IntervalTrigger +# Constants +UUID_LENGTH = 8 # UUID 截断长度 +DEFAULT_TIMEOUT = 300 # 默认超时时间(秒) +DEFAULT_RETRY_COUNT = 3 # 默认重试次数 +DEFAULT_RETRY_DELAY = 5 # 默认重试延迟(秒) + # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -1073,7 +1078,7 @@ class WorkflowManager: # 创建工作流执行日志 log = WorkflowLog( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], workflow_id=workflow_id, status=TaskStatus.RUNNING.value, start_time=now, @@ -1200,7 +1205,7 @@ class WorkflowManager: # 创建任务日志 task_log = WorkflowLog( - id=str(uuid.uuid4())[:8], + id=str(uuid.uuid4())[:UUID_LENGTH], workflow_id=task.workflow_id, task_id=task.id, status=TaskStatus.RUNNING.value,