x
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
import importlib
|
||||
|
||||
from shared.config.settings import settings
|
||||
from shared.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -24,7 +25,10 @@ _safe_include("moldinsight.api.upload_router", "上传")
|
||||
_safe_include("moldinsight.api.batch_router", "批量")
|
||||
_safe_include("moldinsight.api.task_router", "任务")
|
||||
_safe_include("moldinsight.api.history_router", "历史")
|
||||
_safe_include("moldinsight.api.debug_router", "调试")
|
||||
_safe_include("moldinsight.api.cam_router", "CAM")
|
||||
_safe_include("moldinsight.api.advanced_router", "高级")
|
||||
_safe_include("moldinsight.api.aluminum_price_routes", "铝价")
|
||||
|
||||
# 调试端点会 dump 全量任务数据,仅 DEBUG 模式注册(双重防线:还需登录)
|
||||
if settings.DEBUG:
|
||||
_safe_include("moldinsight.api.debug_router", "调试")
|
||||
|
||||
@@ -65,10 +65,6 @@ def _get_cached_import(key: str):
|
||||
return None
|
||||
|
||||
|
||||
async def _get_task_data(task_id: str) -> dict:
|
||||
return await redis_task_manager.get_task(task_id)
|
||||
|
||||
|
||||
async def _ensure_task_access(
|
||||
db_session: AsyncSession,
|
||||
task_id: str,
|
||||
@@ -85,7 +81,8 @@ async def _ensure_task_access(
|
||||
|
||||
_, stp_file = row
|
||||
owner_id = getattr(stp_file, "user_id", None)
|
||||
if owner_id is not None and owner_id != user_id:
|
||||
if owner_id != user_id:
|
||||
# 无主历史数据(owner_id is None)同样拒绝:无主不等于公共
|
||||
raise HTTPException(403, "无权访问该任务的导出文件")
|
||||
|
||||
return row
|
||||
@@ -283,21 +280,32 @@ async def design_complete_mold_system(
|
||||
async def detect_undercuts(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
body = await request.json()
|
||||
task_id = body.get("task_id")
|
||||
parting_direction = body.get("parting_direction", [0, 0, 1])
|
||||
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
||||
if not task_id:
|
||||
raise HTTPException(404, "缺少 task_id")
|
||||
task_data = await _get_task_data(task_id)
|
||||
if not task_data:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
raise HTTPException(400, "缺少 task_id")
|
||||
|
||||
await _ensure_task_access(db_session, task_id, current_user.id)
|
||||
|
||||
sd = _get_cached_import("side_action_designer")
|
||||
if not sd:
|
||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||
result = sd.analyze_and_design(
|
||||
shape=None, parting_direction=parting_direction, mold_size=mold_size,
|
||||
|
||||
# 从持久化 STP 原件重建几何(此前传 shape=None 会被兜底吞掉,永远返回"无倒扣")
|
||||
from moldinsight.services.shape_loader import get_shape_loader
|
||||
shape = await get_shape_loader().load_shape_for_task(db_session, task_id)
|
||||
if shape is None:
|
||||
raise HTTPException(410, "任务几何不可用:无法从存储重建 STP 形状,请重新上传分析")
|
||||
|
||||
result = await processing_service.run_occ(
|
||||
sd.analyze_and_design,
|
||||
shape,
|
||||
parting_direction,
|
||||
mold_size,
|
||||
)
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
@@ -306,13 +314,18 @@ async def detect_undercuts(
|
||||
async def estimate_cost(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
"""模具成本估算:优先使用 LLM,未启用时降级为规则式估算"""
|
||||
body = await request.json()
|
||||
task_id = body.get("task_id")
|
||||
if not task_id:
|
||||
raise HTTPException(404, "缺少 task_id")
|
||||
task_data = await _get_task_data(task_id)
|
||||
raise HTTPException(400, "缺少 task_id")
|
||||
|
||||
await _ensure_task_access(db_session, task_id, current_user.id)
|
||||
|
||||
# 统一走任务视图:进行中读 Redis,完成态由 PG+RustFS 组装(Redis 大对象已瘦身)
|
||||
task_data = await TaskQueryService.get_task_view(db_session, task_id)
|
||||
if not task_data:
|
||||
raise HTTPException(404, "任务不存在")
|
||||
analysis_result = task_data.get("analysis_result")
|
||||
@@ -475,6 +488,38 @@ async def export_mold_results(
|
||||
filename = task_data.get("filename", f"mold_{task_id}")
|
||||
|
||||
if not cavity_shapes:
|
||||
# 内存 shape 缓存失效(如服务重启):从持久化的单组件 STEP
|
||||
# 现场转换缺失格式,用户无需重新分析
|
||||
artifacts = _get_export_artifacts(task_data)
|
||||
scheme_data = (artifacts.get("schemes") or {}).get(resolved_scheme_id)
|
||||
if scheme_data:
|
||||
base_filename = scheme_data.get("base_filename") or Path(filename).stem
|
||||
regenerated = await processing_service.regenerate_export_from_persisted(
|
||||
task_id=task_id,
|
||||
scheme_id=resolved_scheme_id,
|
||||
formats=formats,
|
||||
components=_expand_components(components),
|
||||
base_filename=base_filename,
|
||||
scheme_files=scheme_data.get("files", []),
|
||||
)
|
||||
if regenerated:
|
||||
regenerated["files"] = _augment_export_files(
|
||||
task_id, regenerated.get("files", [])
|
||||
)
|
||||
# 合并进持久化 manifest,后续请求直接命中持久化路径
|
||||
merged_artifacts = _merge_export_artifacts(artifacts, regenerated)
|
||||
await storage_service.update_task_parameters(
|
||||
db_session,
|
||||
task_id,
|
||||
{"export_artifacts": merged_artifacts},
|
||||
)
|
||||
await redis_task_manager.update_task(
|
||||
task_id, {"export_artifacts": merged_artifacts}
|
||||
)
|
||||
TaskQueryService.invalidate_task_view(task_id)
|
||||
|
||||
return {"status": "success", "data": regenerated}
|
||||
|
||||
raise HTTPException(
|
||||
409,
|
||||
"导出缓存已失效或任务尚未完成,请重新分析后再导出以保证方案一致性",
|
||||
@@ -499,6 +544,7 @@ async def export_mold_results(
|
||||
{"export_artifacts": merged_artifacts},
|
||||
)
|
||||
await redis_task_manager.update_task(task_id, {"export_artifacts": merged_artifacts})
|
||||
TaskQueryService.invalidate_task_view(task_id) # parameters 已变更,缓存视图失效
|
||||
|
||||
return {"status": "success", "data": result}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ moldinsight/api/batch_router.py — 批量分析端点
|
||||
"""
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -19,13 +19,7 @@ from shared.services.redis_task_manager import redis_task_manager
|
||||
from shared.utils.file_handler import FileHandler
|
||||
from shared.utils.logger import get_logger
|
||||
from moldinsight.services.storage_integration_rustfs import StorageIntegrationService
|
||||
|
||||
try:
|
||||
from celery_tasks import process_stp_task
|
||||
_use_celery = True
|
||||
except ImportError:
|
||||
process_stp_task = None
|
||||
_use_celery = False
|
||||
from moldinsight.services.task_dispatcher import dispatch_processing
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -37,6 +31,9 @@ file_handler = FileHandler()
|
||||
_BATCH_KEY_PREFIX = "batch:"
|
||||
_BATCH_TTL = 86400 # 24h
|
||||
|
||||
# Redis 不可用时的进程内降级存储(同进程内可查,跨进程/重启不可见)
|
||||
_batch_meta_memory: Dict[str, dict] = {}
|
||||
|
||||
|
||||
def _batch_redis_key(batch_id: str) -> str:
|
||||
return f"{_BATCH_KEY_PREFIX}{batch_id}"
|
||||
@@ -111,14 +108,7 @@ async def batch_upload(
|
||||
await redis_task_manager.set_task(task_id, task_info)
|
||||
|
||||
# 调度处理
|
||||
if _use_celery:
|
||||
process_stp_task.delay(task_id, str(file_path), stp_file.id, process_params)
|
||||
else:
|
||||
import asyncio
|
||||
from moldinsight.services.processing_service import processing_service
|
||||
asyncio.create_task(processing_service.process_file_with_storage(
|
||||
task_id, str(file_path), stp_file.id, process_params
|
||||
))
|
||||
dispatch_processing(task_id, str(file_path), stp_file.id, process_params)
|
||||
|
||||
tasks.append({
|
||||
"filename": file.filename,
|
||||
@@ -139,7 +129,7 @@ async def batch_upload(
|
||||
"error": str(exc),
|
||||
})
|
||||
|
||||
# 将 batch 元数据写入 Redis
|
||||
# 将 batch 元数据写入 Redis;Redis 不可用时降级到进程内存储(任务状态本身有内存回退)
|
||||
batch_meta = {
|
||||
"batch_id": batch_id,
|
||||
"user_id": current_user.id,
|
||||
@@ -148,11 +138,7 @@ async def batch_upload(
|
||||
"total": len(tasks),
|
||||
"params": process_params,
|
||||
}
|
||||
await redis_task_manager.redis_client.set(
|
||||
_batch_redis_key(batch_id),
|
||||
__import__("json").dumps(batch_meta),
|
||||
ex=_BATCH_TTL,
|
||||
)
|
||||
_save_batch_meta(batch_id, batch_meta)
|
||||
|
||||
return {
|
||||
"batch_id": batch_id,
|
||||
@@ -162,20 +148,47 @@ async def batch_upload(
|
||||
}
|
||||
|
||||
|
||||
def _save_batch_meta(batch_id: str, batch_meta: dict):
|
||||
"""批量元数据持久化:优先 Redis(跨进程、带 TTL),降级进程内 dict。"""
|
||||
import json as _json
|
||||
|
||||
if redis_task_manager.is_connected:
|
||||
try:
|
||||
redis_task_manager.redis_client.set(
|
||||
_batch_redis_key(batch_id),
|
||||
_json.dumps(batch_meta),
|
||||
ex=_BATCH_TTL,
|
||||
)
|
||||
return
|
||||
except Exception as exc:
|
||||
logger.warning(f"[BATCH] batch 元数据写 Redis 失败,降级内存: {exc}")
|
||||
_batch_meta_memory[batch_id] = batch_meta
|
||||
|
||||
|
||||
async def _load_batch_meta(batch_id: str) -> Optional[dict]:
|
||||
"""读取批量元数据,Redis 优先,内存兜底;不存在返回 None。"""
|
||||
import json as _json
|
||||
|
||||
if redis_task_manager.is_connected:
|
||||
try:
|
||||
raw = await redis_task_manager.redis_client.get(_batch_redis_key(batch_id))
|
||||
if raw:
|
||||
return _json.loads(raw)
|
||||
except Exception as exc:
|
||||
logger.warning(f"[BATCH] batch 元数据读 Redis 失败: {exc}")
|
||||
return _batch_meta_memory.get(batch_id)
|
||||
|
||||
|
||||
@router.get("/batch/{batch_id}")
|
||||
async def get_batch_status(
|
||||
batch_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""聚合查询批量任务进度"""
|
||||
import json
|
||||
|
||||
raw = await redis_task_manager.redis_client.get(_batch_redis_key(batch_id))
|
||||
if not raw:
|
||||
batch_meta = await _load_batch_meta(batch_id)
|
||||
if not batch_meta:
|
||||
raise HTTPException(404, "批量任务不存在或已过期")
|
||||
|
||||
batch_meta = json.loads(raw)
|
||||
|
||||
# 权限检查
|
||||
if batch_meta.get("user_id") and batch_meta["user_id"] != current_user.id:
|
||||
raise HTTPException(403, "无权访问该批量任务")
|
||||
|
||||
@@ -96,6 +96,8 @@ async def generate_cam_plan(
|
||||
}
|
||||
processing_task.parameters = parameters
|
||||
await db_session.commit()
|
||||
# parameters 已变更,任务视图缓存失效
|
||||
TaskQueryService.invalidate_task_view(task_id)
|
||||
|
||||
return {"status": "success", "data": data, "cam_preferences": cam_preferences}
|
||||
except Exception as exc:
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# api/v1/debug_router.py
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from shared.services.auth_service import get_current_active_user
|
||||
from shared.services.redis_task_manager import redis_task_manager
|
||||
from shared.models.database import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/debug/tasks")
|
||||
@router.post("/debug/tasks")
|
||||
async def debug_tasks():
|
||||
"""调试接口:查看所有任务"""
|
||||
async def debug_tasks(current_user: User = Depends(get_current_active_user)):
|
||||
"""调试接口:查看所有任务(仅限 DEBUG 模式注册,且需登录)"""
|
||||
all_tasks = await redis_task_manager.get_all_tasks()
|
||||
return {
|
||||
"total_tasks": len(all_tasks),
|
||||
|
||||
@@ -4,17 +4,27 @@ import urllib.parse
|
||||
|
||||
from moldinsight.services.storage_integration_rustfs import StorageIntegrationService
|
||||
from shared.database.database import get_db_session
|
||||
from shared.services.auth_service import get_current_active_user
|
||||
from shared.models.database import User
|
||||
from shared.utils.logger import get_logger
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
@router.post("/history")
|
||||
async def get_file_history(db_session: AsyncSession = Depends(get_db_session)):
|
||||
"""获取按文件名分组的文件历史记录(支持多上传)"""
|
||||
async def get_file_history(
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""获取当前用户按文件名分组的文件历史记录(支持多上传)"""
|
||||
storage_service = StorageIntegrationService()
|
||||
file_groups = await storage_service.get_all_file_groups(db_session)
|
||||
file_groups = await storage_service.get_all_file_groups(
|
||||
db_session, user_id=current_user.id
|
||||
)
|
||||
|
||||
return {
|
||||
"total_files": len(file_groups),
|
||||
@@ -24,14 +34,19 @@ async def get_file_history(db_session: AsyncSession = Depends(get_db_session)):
|
||||
|
||||
@router.get("/history/{filename}")
|
||||
@router.post("/history/{filename}")
|
||||
async def get_file_records(filename: str, db_session: AsyncSession = Depends(get_db_session)):
|
||||
"""获取指定文件名的所有上传记录(支持多上传历史)"""
|
||||
async def get_file_records(
|
||||
filename: str,
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""获取当前用户指定文件名的所有上传记录(支持多上传历史)"""
|
||||
decoded_filename = urllib.parse.unquote(filename)
|
||||
|
||||
storage_service = StorageIntegrationService()
|
||||
file_records = await storage_service.get_file_history_by_filename(
|
||||
db_session,
|
||||
decoded_filename
|
||||
decoded_filename,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
|
||||
return file_records
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
from fastapi import APIRouter, UploadFile, File, HTTPException, Depends, Form
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from shared.models.schemas import ProcessingStatus, create_task_info
|
||||
from shared.utils.file_handler import FileHandler
|
||||
from moldinsight.services.storage_integration_rustfs import StorageIntegrationService
|
||||
from moldinsight.services.task_dispatcher import dispatch_processing
|
||||
from shared.services.redis_task_manager import redis_task_manager
|
||||
from shared.database.database import get_db_session
|
||||
from shared.utils.logger import get_logger
|
||||
@@ -14,13 +14,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from shared.services.auth_service import get_current_active_user
|
||||
from shared.models.database import User
|
||||
|
||||
try:
|
||||
from celery_tasks import process_stp_task
|
||||
_use_celery = True
|
||||
except ImportError:
|
||||
process_stp_task = None
|
||||
_use_celery = False
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
@@ -96,16 +89,7 @@ async def upload_stp(
|
||||
task_info["file_hash"] = file_meta["sha256"]
|
||||
await redis_task_manager.set_task(task_id, task_info)
|
||||
|
||||
if _use_celery:
|
||||
process_stp_task.delay(task_id, str(file_path), stp_file.id, process_params)
|
||||
logger.info(f"[UPLOAD] Celery 任务已调度: task_id={task_id}")
|
||||
else:
|
||||
import asyncio
|
||||
from moldinsight.services.processing_service import processing_service
|
||||
asyncio.create_task(processing_service.process_file_with_storage(
|
||||
task_id, str(file_path), stp_file.id, process_params
|
||||
))
|
||||
logger.info(f"[UPLOAD] 直接后台处理: task_id={task_id} (celery 未安装)")
|
||||
dispatch_processing(task_id, str(file_path), stp_file.id, process_params)
|
||||
|
||||
return {
|
||||
"task_id": task_id,
|
||||
|
||||
Reference in New Issue
Block a user