fix: auto-fix code issues (cron)
- 修复重复导入/字段 - 修复异常处理 (将裸 except Exception 改为具体异常类型) - 修复PEP8格式问题 - 清理未使用导入 - 添加 UUID_LENGTH 常量替代魔法数字 - 添加 DEFAULT_RATE_LIMIT, MASTER_KEY_RATE_LIMIT, IP_RATE_LIMIT 常量 - 添加 MAX_TEXT_LENGTH, DEFAULT_TIMEOUT 常量 涉及文件: - backend/main.py - backend/db_manager.py - backend/llm_client.py - backend/neo4j_manager.py - backend/tingwu_client.py - backend/tenant_manager.py - backend/growth_manager.py - backend/workflow_manager.py - backend/image_processor.py - backend/multimodal_entity_linker.py - backend/multimodal_processor.py - backend/plugin_manager.py - backend/rate_limiter.py
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
104
backend/main.py
104
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"])
|
||||
|
||||
@@ -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="", # 将在后续填充
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
"""限流异常"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user