批次4后续专项完成:D11清偿 + OCC方案B实施 + 部署参数 + D2诚实标注 + CI门禁
① D11 HTML 报告 RustFS 单源化(TECH_DEBT P2 清偿):可视化产物写任务临时目录后
裸传报告键 html/reports/{filename}(文件名寻址),/html StaticFiles 挂载删除,
新增 html_report_router 根路径代理(报告键→遗留 JSON 包装→本地卷兜底→404,
防穿越);URL 形状 /html/{filename} 不变,持久化引用零迁移;celery 摘除
html_data 卷,镜像不再烤入陈旧报告;顺带删除 get_stp_file_with_data 死数据块
② OCC 方案 B(D10 清偿):run_occ(op_name, payload) 契约 + 常驻工作进程池
(occ_process_pool + occ_worker 操作注册表),超时/崩溃 terminate 换新补位、
任务级超时 recover 整体重建,残留线程泄漏根治;TopoDS 不跨进程(generate_cavity
分模 + 方案 STEP 持久化全在子进程内,返回 export_manifest);删除内存形状缓存链、
CADExporter.export_mold_results、shape_loader(→ stp_materializer)
③ OCC 方案 A 部署参数:CELERY_CONCURRENCY / CELERY_MAX_TASKS_PER_CHILD 进
Dockerfile.celery + compose + .env.example
④ D2 诚实标注:铝价响应带 source: "simulated",前端按来源渲染标注(原硬编码
"上海期货交易所"属虚假声明),死代码 getAluminumPrice 删除
⑤ CI 门禁:.gitea/workflows/ci.yml 三 job(pytest / 前端构建含 vue-tsc /
openapi 漂移检测)
接口变更三件套随批完成(openapi 76→77 paths + gen:api + 前端构建通过;方案 B
接口面零变化)。测试基线 143 passed, 0 skipped(新增 16 项)。文档六处同步。
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# services/stp_materializer.py
|
||||
"""按 task_id 把 STP 原件从持久化存储落盘为临时文件(方案 B)。
|
||||
|
||||
任务完成后 TopoDS_Shape 不驻留内存/Redis;需要几何的端点(倒扣检测、
|
||||
按需重导出等)通过 STP 原件重建:PG(object_key) -> RustFS 下载 -> 临时文件,
|
||||
OCC 解析在常驻子进程内完成(occ_worker 的 detect_undercuts 等操作,见
|
||||
occ_process_pool)。本模块只负责把原件落到调用方可传路径的临时文件。
|
||||
"""
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from moldinsight.models import ProcessingTask, STPFile
|
||||
from shared.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class STPMaterializer:
|
||||
"""任务 STP 原件落盘器"""
|
||||
|
||||
async def materialize_stp_for_task(
|
||||
self, db_session: AsyncSession, task_id: str
|
||||
) -> Optional[Path]:
|
||||
"""重建任务的产品几何原件为临时文件。
|
||||
|
||||
任务不存在或 STP 原件不可用时返回 None;返回的临时文件由调用方
|
||||
finally 清理(unlink)。
|
||||
"""
|
||||
result = await db_session.execute(
|
||||
select(ProcessingTask, STPFile)
|
||||
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
|
||||
.where(ProcessingTask.task_id == task_id)
|
||||
)
|
||||
row = result.first()
|
||||
if not row:
|
||||
logger.warning(f"STP 原件落盘失败:任务不存在 {task_id}")
|
||||
return None
|
||||
|
||||
_, stp_file = row
|
||||
if not stp_file.object_key:
|
||||
logger.warning(f"STP 原件落盘失败:任务缺少 object_key {task_id}")
|
||||
return None
|
||||
|
||||
from moldinsight.storage.rustfs_storage import rustfs_manager
|
||||
|
||||
try:
|
||||
data = await rustfs_manager.download_file(
|
||||
file_type="stp_files", object_key=stp_file.object_key
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(f"STP 原件落盘失败:下载失败 {task_id}: {exc}")
|
||||
return None
|
||||
|
||||
original_name = Path(stp_file.original_filename or "model.stp").name or "model.stp"
|
||||
tmp = tempfile.NamedTemporaryFile(
|
||||
suffix=Path(original_name).suffix or ".stp", prefix=f"mold_{task_id}_", delete=False
|
||||
)
|
||||
tmp.write(data)
|
||||
tmp.close()
|
||||
logger.debug(f"STP 原件已落盘: {tmp.name}")
|
||||
return Path(tmp.name)
|
||||
|
||||
|
||||
# 惰性单例
|
||||
_stp_materializer: Optional[STPMaterializer] = None
|
||||
|
||||
|
||||
def get_stp_materializer() -> STPMaterializer:
|
||||
global _stp_materializer
|
||||
if _stp_materializer is None:
|
||||
_stp_materializer = STPMaterializer()
|
||||
return _stp_materializer
|
||||
Reference in New Issue
Block a user