|
|
@@ -16,7 +16,7 @@ from fastapi.responses import StreamingResponse
|
|
|
from core.base.redis_duplicate_checker import RedisDuplicateChecker
|
|
|
from foundation.logger.loggering import server_logger as logger
|
|
|
from foundation.trace.trace_context import TraceContext, auto_trace
|
|
|
-from foundation.utils.redis_utils import get_file_info
|
|
|
+from foundation.utils.redis_utils import get_file_info,store_file_info
|
|
|
from core.base.workflow_manager import WorkflowManager
|
|
|
from core.base.progress_manager import ProgressManager, sse_callback_manager
|
|
|
from views.construction_review.file_upload import validate_upload_parameters
|
|
|
@@ -66,8 +66,11 @@ class SimpleSSEManager:
|
|
|
"""发送进度更新 - 将进度数据放入队列推送给客户端"""
|
|
|
queue = self.connections.get(callback_task_id)
|
|
|
if queue:
|
|
|
- # 根据数据状态决定事件类型
|
|
|
- event_type = "unit_review_update" if current_data.get("status") == "unit_review_update" else "progress_update"
|
|
|
+ # 优先使用progress_manager传递的event_type,如果没有则使用默认逻辑
|
|
|
+ event_type = current_data.get("event_type", "processing")
|
|
|
+ # 保持向后兼容性
|
|
|
+ if event_type == "processing" and current_data.get("status") == "unit_review_update":
|
|
|
+ event_type = "unit_review_update"
|
|
|
|
|
|
await queue.put({
|
|
|
"type": event_type,
|
|
|
@@ -95,6 +98,10 @@ class LaunchReviewRequest(BaseModel):
|
|
|
"""启动审查请求模型"""
|
|
|
callback_task_id: str = Field(..., description="回调任务ID,从文件上传接口获取")
|
|
|
user_id: str = Field(..., description="用户标识")
|
|
|
+ tendency_review_role: str = Field(
|
|
|
+ "default_role",
|
|
|
+ description="倾向性审查角色,如选项为 一线施工人员、项目负责人、监理、检测员、项目总工、环保检测人员等"
|
|
|
+ )
|
|
|
review_config: List[str] = Field(
|
|
|
...,
|
|
|
description="审查配置列表,包含的项为启用状态"
|
|
|
@@ -146,6 +153,26 @@ def validate_project_plan_type(project_plan_type: str) -> None:
|
|
|
if project_plan_type not in supported_types:
|
|
|
raise LaunchReviewErrors.project_plan_type_invalid()
|
|
|
|
|
|
+def validate_tendency_review_role(tendency_review_role: str) -> None:
|
|
|
+ """验证倾向性审查角色"""
|
|
|
+ # 当前支持的倾向性审查角色类型
|
|
|
+ supported_roles = {
|
|
|
+ 'default_role', # 默认角色
|
|
|
+ }
|
|
|
+
|
|
|
+ if tendency_review_role not in supported_roles:
|
|
|
+ raise LaunchReviewErrors.tendency_review_role_invalid()
|
|
|
+
|
|
|
+def validate_user_id(user_id: str) -> None:
|
|
|
+ """验证用户标识"""
|
|
|
+ # 当前支持的用户标识列表
|
|
|
+ supported_users = {
|
|
|
+ 'user-001'
|
|
|
+ }
|
|
|
+
|
|
|
+ if user_id not in supported_users:
|
|
|
+ raise LaunchReviewErrors.invalid_user()
|
|
|
+
|
|
|
|
|
|
@launch_review_router.post("/sse/launch_review")
|
|
|
@auto_trace(generate_if_missing=True)
|
|
|
@@ -161,10 +188,15 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
"""
|
|
|
callback_task_id = request_data.callback_task_id
|
|
|
TraceContext.set_trace_id(callback_task_id)
|
|
|
+ user_id = request_data.user_id
|
|
|
review_config = request_data.review_config
|
|
|
project_plan_type = request_data.project_plan_type
|
|
|
+ tendency_review_role = request_data.tendency_review_role
|
|
|
+
|
|
|
+ logger.info(f"收到审查启动SSE请求: callback_task_id={callback_task_id}, user_id={user_id}, tendency_review_role={tendency_review_role}")
|
|
|
|
|
|
- logger.info(f"收到审查启动SSE请求: callback_task_id={callback_task_id}")
|
|
|
+ # 验证用户标识
|
|
|
+ validate_user_id(user_id)
|
|
|
|
|
|
# 验证审查配置
|
|
|
validate_review_config(review_config)
|
|
|
@@ -172,6 +204,9 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
# 验证工程方案类型
|
|
|
validate_project_plan_type(project_plan_type)
|
|
|
|
|
|
+ # 验证倾向性审查角色
|
|
|
+ validate_tendency_review_role(tendency_review_role)
|
|
|
+
|
|
|
# 注册SSE回调
|
|
|
sse_callback_manager.register_callback(callback_task_id, sse_progress_callback)
|
|
|
queue = await sse_manager.connect(callback_task_id)
|
|
|
@@ -182,9 +217,14 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
# 发送连接确认
|
|
|
connected_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "stage": "startup",
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": 0,
|
|
|
+ "stage_name": "启动审查SSE连接",
|
|
|
+ "status": "connected",
|
|
|
"message": "启动审查SSE连接已建立,正在处理请求...",
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "processing",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": []
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("connected", connected_data)
|
|
|
|
|
|
@@ -197,9 +237,14 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
# 发送处理状态
|
|
|
status_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "stage": "validation",
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": 5,
|
|
|
+ "stage_name": f"验证文件信息: {file_id}",
|
|
|
+ "status": "processing",
|
|
|
"message": f"正在验证文件信息: {file_id}",
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "processing",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": []
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("processing", status_data)
|
|
|
|
|
|
@@ -222,7 +267,6 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
|
|
|
# 立即更新Redis中的callback_task_id为当前值
|
|
|
try:
|
|
|
- from foundation.utils.redis_utils import store_file_info
|
|
|
await store_file_info(file_id, {'callback_task_id': callback_task_id})
|
|
|
logger.info(f"已更新Redis中的callback_task_id: {callback_task_id}")
|
|
|
except Exception as e:
|
|
|
@@ -230,8 +274,10 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
|
|
|
# 添加审查配置到文件信息,并确保使用当前正确的callback_task_id
|
|
|
file_info.update({
|
|
|
+ 'user_id': user_id,
|
|
|
'review_config': review_config,
|
|
|
'project_plan_type': project_plan_type,
|
|
|
+ 'tendency_review_role': tendency_review_role,
|
|
|
'launched_at': int(time.time()),
|
|
|
'callback_task_id': callback_task_id # 确保使用当前正确的callback_task_id
|
|
|
})
|
|
|
@@ -244,13 +290,14 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
# 发送成功启动状态
|
|
|
success_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "file_id": file_info['file_id'],
|
|
|
- "review_config": review_config,
|
|
|
- "project_plan_type": project_plan_type,
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": 10,
|
|
|
+ "stage_name": "任务启动成功",
|
|
|
"status": "submitted",
|
|
|
- "submitted_at": file_info['launched_at'],
|
|
|
"message": "施工方案审查任务启动成功,请耐心等待结果...",
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "processing",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": []
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("submitted", success_data)
|
|
|
|
|
|
@@ -260,31 +307,66 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
try:
|
|
|
message = await queue.get()
|
|
|
|
|
|
- if message.get("type") == "progress_update":
|
|
|
- current_data = message.get("data")
|
|
|
- if current_data:
|
|
|
- progress_json = json.dumps(current_data, ensure_ascii=False)
|
|
|
- yield format_sse_event("progress", progress_json)
|
|
|
- elif message.get("type") == "unit_review_update":
|
|
|
- current_data = message.get("data")
|
|
|
- if current_data:
|
|
|
- unit_review_json = json.dumps(current_data, ensure_ascii=False)
|
|
|
- yield format_sse_event("unit_review", unit_review_json)
|
|
|
-
|
|
|
- # # 统一检查任务完成状态
|
|
|
- # if 'current_data' in locals() and current_data:
|
|
|
- # overall_task_status = current_data.get("overall_task_status")
|
|
|
- # if overall_task_status in ["completed", "failed"]:
|
|
|
- # completion_data = {
|
|
|
- # "callback_task_id": callback_task_id,
|
|
|
- # "task_status": overall_task_status,
|
|
|
- # "overall_progress": current_data.get("current", 100),
|
|
|
- # "timestamp": datetime.now().isoformat(),
|
|
|
- # "message": "审查任务处理完成!"
|
|
|
- # }
|
|
|
- # completion_json = json.dumps(completion_data, ensure_ascii=False)
|
|
|
- # yield format_sse_event("completed", completion_json)
|
|
|
- # break
|
|
|
+ # 处理所有类型的进度更新消息
|
|
|
+ message_type = message.get("type")
|
|
|
+ current_data = message.get("data")
|
|
|
+
|
|
|
+ if current_data:
|
|
|
+ # 根据消息类型决定数据格式
|
|
|
+ if message_type == "unit_review_update":
|
|
|
+ # 单元审查更新的特殊格式
|
|
|
+ unified_data = {
|
|
|
+ "callback_task_id": callback_task_id,
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": current_data.get("current", 0),
|
|
|
+ "stage_name": current_data.get("stage_name", "单元审查"),
|
|
|
+ "status": "unit_review_update",
|
|
|
+ "message": current_data.get("message", ""),
|
|
|
+ "overall_task_status": current_data.get("overall_task_status", "processing"),
|
|
|
+ "updated_at": current_data.get("updated_at", int(time.time())),
|
|
|
+ "issues": current_data.get("issues", [])
|
|
|
+ }
|
|
|
+ else:
|
|
|
+ # 通用进度更新格式(包括 processing_flag, processing, completed 等)
|
|
|
+ unified_data = {
|
|
|
+ "callback_task_id": callback_task_id,
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": current_data.get("current", 0),
|
|
|
+ "stage_name": current_data.get("stage_name", "处理中"),
|
|
|
+ "status": current_data.get("status", "processing"),
|
|
|
+ "message": current_data.get("message", ""),
|
|
|
+ "overall_task_status": current_data.get("overall_task_status", "processing"),
|
|
|
+ "updated_at": current_data.get("updated_at", int(time.time())),
|
|
|
+ "issues": current_data.get("issues", [])
|
|
|
+ }
|
|
|
+
|
|
|
+ # 使用从progress_manager传递的事件类型,或回退到消息类型
|
|
|
+ sse_event_type = current_data.get("event_type", message_type)
|
|
|
+ if not sse_event_type:
|
|
|
+ sse_event_type = "processing" # 最终回退
|
|
|
+
|
|
|
+ logger.debug(f"生成SSE事件: {sse_event_type}, 消息类型: {message_type}, current: {current_data.get('current')}")
|
|
|
+ unified_data_json = json.dumps(unified_data, ensure_ascii=False)
|
|
|
+ yield format_sse_event(sse_event_type, unified_data_json)
|
|
|
+
|
|
|
+ # 统一检查任务完成状态
|
|
|
+ if current_data:
|
|
|
+ overall_task_status = current_data.get("overall_task_status")
|
|
|
+ if overall_task_status in ["completed", "failed"]:
|
|
|
+ completion_data = {
|
|
|
+ "callback_task_id": callback_task_id,
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": current_data.get("current", 100),
|
|
|
+ "stage_name": "审查完成",
|
|
|
+ "status": "completed",
|
|
|
+ "message": "施工审查方案处理完成!",
|
|
|
+ "overall_task_status": overall_task_status,
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": current_data.get("issues", []),
|
|
|
+ }
|
|
|
+ completion_json = json.dumps(completion_data, ensure_ascii=False)
|
|
|
+ yield format_sse_event("completed", completion_json)
|
|
|
+ break
|
|
|
|
|
|
except Exception as e:
|
|
|
logger.error(f"队列消息处理异常: {callback_task_id}")
|
|
|
@@ -298,9 +380,15 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
logger.error(f"异常堆栈: {traceback.format_exc()}")
|
|
|
error_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "error": e.detail.get("code") if hasattr(e, 'detail') and e.detail else "http_error",
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": 0,
|
|
|
+ "stage_name": "处理异常",
|
|
|
+ "status": "error",
|
|
|
"message": e.detail.get("message") if hasattr(e, 'detail') and e.detail else str(e),
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "failed",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": [],
|
|
|
+ "error": e.detail.get("code") if hasattr(e, 'detail') and e.detail else "http_error"
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("error", error_data)
|
|
|
|
|
|
@@ -310,9 +398,15 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
logger.error(f"异常堆栈: {traceback.format_exc()}")
|
|
|
error_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "error": "internal_error",
|
|
|
+ "user_id": user_id,
|
|
|
+ "current": 0,
|
|
|
+ "stage_name": "内部错误",
|
|
|
+ "status": "error",
|
|
|
"message": f"服务端内部错误: {str(e)}",
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "failed",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": [],
|
|
|
+ "error": "internal_error"
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("error", error_data)
|
|
|
|
|
|
@@ -322,9 +416,15 @@ async def launch_review_sse(request_data: LaunchReviewRequest):
|
|
|
logger.error(f"异常堆栈: {traceback.format_exc()}")
|
|
|
error_data = json.dumps({
|
|
|
"callback_task_id": callback_task_id,
|
|
|
- "error": "sse_error",
|
|
|
+ "user_id": user_id if 'user_id' in locals() else "unknown",
|
|
|
+ "current": 0,
|
|
|
+ "stage_name": "SSE流异常",
|
|
|
+ "status": "error",
|
|
|
"message": f"SSE流异常: {str(e)}",
|
|
|
- "timestamp": datetime.now().isoformat()
|
|
|
+ "overall_task_status": "failed",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ "issues": [],
|
|
|
+ "error": "sse_error"
|
|
|
}, ensure_ascii=False)
|
|
|
yield format_sse_event("error", error_data)
|
|
|
|