后端设计治理:批次 0-4 全部完成(安全/部署/一致性/结构/架构)
按 ROADMAP §3.1 治理批次推进的后端设计审查整改:
- 批次 0(安全):/api/status/{task_id} 补 JWT 鉴权与任务归属校验;
pythonocc_available 真实探测;bcrypt 超 72 字节显式拒绝;
SECRET_KEY/RUSTFS_* 惰性校验,代码侧弱默认移除
- 批次 1(部署正确性):主处理链路改走 RustFS(分派入参 stp_file_id 化,
worker 按 object_key 下载);AUTO_MIGRATE 开关 + 迁移目录 alembic/→migrations/
修复包遮蔽(自动迁移此前从未真正生效);OCC 镜像改 conda 原生执行 +
基础镜像 tag 锁定;compose 关键项改 ${VAR:?} 强制显式配置
- 批次 2(任务一致性):删除 Redis 进程内存回退,PG 为任务状态单一事实源;
批量元数据入库(processing_tasks.batch_id,迁移 a3f8c2d91e47);
型腔失败任务标 failed 不再静默 completed;事务边界收口
(数据本体写 flush-only、失败先回滚再置 failed、进度更新保留即时 commit)
- 批次 3(API 与代码结构):592 行 advanced_router 拆为 design/cost/machining/
export 四子路由,请求体全量 Pydantic 化;ROUTE_MODULES + route_registry
(/api/health 呈现 degraded,DEBUG fail fast);纯计算端点统一 to_thread;
StorageIntegrationService 按职责三拆;MAX_FILE_SIZE 接线生效、
celery 复用 Settings.redis_url;管理员重置密码改 JSON body(端到端断裂修复);
openapi.json 重导出(76 paths)+ 前端 gen:api
- 批次 4(架构演进):共享 ORM 按模块拆分(shared/models/base.py + identity.py、
moldinsight/models/、inventory/models/,删除三条无使用方的跨模块
relationship,跨模块桥接收敛为裸 FK 硬规则,无兼容 facade);
OCC executor 重建补 cancel_futures=True(消除旧队列被慢恢复线程
并行消化的数据竞争);OCC 吞吐方案设计先行
(docs/topics/performance/OCC_THROUGHPUT.md);顺手清偿 D15
(vite.config.ts 未用参数致 npm run build 失败)
测试基线:125 passed, 2 skipped(pytest + sqlite+aiosqlite;归属边界、
路由契约、配置治理、鉴权回归等随批新增)
文档同步:STATUS / TECH_DEBT / ROADMAP / ARCHITECTURE / API_CONTRACT /
OPERATIONS / AGENTS
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+10
-7
@@ -15,7 +15,7 @@
|
||||
| moldinsight-only | `/api/*`(moldinsight)+ `/api/auth/*` + `/health` |
|
||||
| inventory-only | `/api/*`(inventory)+ `/api/auth/*` + `/health` |
|
||||
|
||||
- moldinsight 路由在 [moldinsight/api/\_\_init\_\_.py](../src/moldinsight/api/__init__.py) 经 `_safe_include` 聚合(子 router 加载失败仅 WARNING 跳过;`debug_router` 仅 `DEBUG=true` 注册)。
|
||||
- moldinsight 路由在 [moldinsight/api/\_\_init\_\_.py](../src/moldinsight/api/__init__.py) 按 `ROUTE_MODULES` 清单经 `_safe_include` 聚合(装载失败登记 [route_registry.py](../src/moldinsight/api/route_registry.py),`/api/health` 呈现 `degraded` 并列出失败模块;`DEBUG=true` 下失败直接抛错;`debug_router` 仅 `DEBUG=true` 注册)。
|
||||
- inventory 路由在 [inventory/api/\_\_init\_\_.py](../src/inventory/api/__init__.py) 按域静态聚合。
|
||||
- 认证路由来自 [shared/services/auth_routes.py](../src/shared/services/auth_routes.py),由 [app_factory](../src/shared/app_factory.py) 挂载,三种形态共用。
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|---|---|---|
|
||||
| 登录 | `/api/auth/login`、`/api/auth/login/json`、`/api/auth/logout` | auth_routes.py |
|
||||
| 当前用户 | `/api/auth/me` | auth_routes.py |
|
||||
| 用户管理 | `/api/auth/users`、`/api/auth/users/{user_id}`、`/api/auth/users/{user_id}/reset-password` | auth_routes.py |
|
||||
| 用户管理 | `/api/auth/users`、`/api/auth/users/{user_id}`、`/api/auth/users/{user_id}/reset-password`(管理员;JSON body `{ new_password }`,最短 6 位) | auth_routes.py |
|
||||
| 角色权限 | `/api/auth/roles`、`/api/auth/roles/{role_id}`、`/api/auth/roles/{role_id}/permissions`、`/api/auth/permissions`、`/api/auth/permissions/{permission_id}` | auth_routes.py |
|
||||
|
||||
### 3.2 moldinsight(模具分析)
|
||||
@@ -47,11 +47,14 @@
|
||||
| 批量分析 | `/api/batch-upload`、`/api/batch/{batch_id}`(聚合状态以 PG 为准;响应含 `current_step`;他人批次 403、不存在 404) | batch_router.py |
|
||||
| 任务状态 | `/api/status/{task_id}`(需登录;仅任务所有者可访问,他人/无主任务 403,不存在 404) | task_router.py |
|
||||
| 历史结果 | `/api/history`、`/api/history/{filename}` | history_router.py |
|
||||
| CAM | `/api/cam/plan` | cam_router.py |
|
||||
| CAM | `/api/cam/plan`(Pydantic 请求模型;未提供的偏好回落任务持久化偏好再回落默认) | cam_router.py |
|
||||
| 设计 | `/api/optimize-layout`、`/api/design-cooling`、`/api/design-gating`、`/api/design-mold-system`、`/api/detect-undercuts` | design_router.py |
|
||||
| 成本估算 | `/api/cost-estimate` | cost_router.py |
|
||||
| 加工 | `/api/design-cam`、`/api/check-collision`、`/api/optimize-toolpath`、`/api/design-electrodes`、`/api/simulate-machining` | machining_router.py |
|
||||
| 导出 | `/api/export-mold`、`/api/export-download/{filepath}`、`/api/export-recommendations` | export_router.py |
|
||||
| 铝价(模拟数据) | `/api/aluminum-price/current`、`/api/aluminum-price/history` | aluminum_price_routes.py |
|
||||
| 健康检查 | `/api/health` | health_router.py |
|
||||
| 健康检查 | `/api/health`(有路由装载失败时 `status: degraded` 并列出失败清单;`pythonocc` 为真实探测) | health_router.py |
|
||||
| 调试(仅 DEBUG) | `/api/debug/tasks` | debug_router.py |
|
||||
| 导出/估算/设计等高级接口 | 见 `openapi.json` 对应路径 | advanced_router.py(技术债 D1:待拆分) |
|
||||
|
||||
### 3.3 inventory(进销存)
|
||||
|
||||
@@ -93,12 +96,12 @@
|
||||
|
||||
- `frontend/src/types/api.ts` 是**生成物,禁止手改**;前端代码类型引用它。
|
||||
- 三步缺一即前后端契约漂移(硬约束,见 [AGENTS.md](../AGENTS.md) §2)。
|
||||
- **当前已知滞后**:checked-in `openapi.json`(2026-07-27)落后当前代码(实际 76 paths vs 文件内 70),下次接口变更时按上述流程重导出。
|
||||
- 当前 `openapi.json` 于 2026-09-17 随批次 3 重导出(76 paths),前端 `src/types/api.ts` 同步再生。
|
||||
|
||||
## 5. 契约变更规则
|
||||
|
||||
- 新增接口先定模块归属(moldinsight / inventory / shared auth),再写路由;返回结构、路径、鉴权发生变化时,同步更新本文相应表格。
|
||||
- 路由文件过大按职责拆分(现状债务:`advanced_router` 待拆分,见 [TECH_DEBT.md](TECH_DEBT.md) D1)。
|
||||
- 路由文件过大按职责拆分(参照批次 3 的 design / cost / machining / export 拆分先例;新增路由须登记 [moldinsight/api/\_\_init\_\_.py](../src/moldinsight/api/__init__.py) 的 `ROUTE_MODULES`)。
|
||||
- 破坏性变更(删字段 / 改语义)需在 [STATUS.md](STATUS.md) 日志条目中记录,并确认前端同仓同步修改。
|
||||
|
||||
## 6. 前端消费约定
|
||||
|
||||
+14
-3
@@ -159,6 +159,8 @@ geMoldInsight/
|
||||
典型桥接关系示例:
|
||||
- `STPFile.product_id -> Product.id`
|
||||
|
||||
桥接只允许**裸 FK 列**(字符串表名),**不允许跨模块 ORM relationship**——单模块部署下另一模块的模型类可能未注册,跨模块 relationship 会让 mapper 配置直接失败(2026-09-17 批次 4 起为硬规则,原三条跨模块 relationship 均无使用方,已删除;对象化查询由使用方显式 select)。
|
||||
|
||||
### 5.2 模块边界优先于“临时方便”
|
||||
|
||||
新增逻辑时,应优先放入对应业务模块,而不是继续堆进 `shared`。
|
||||
@@ -178,9 +180,16 @@ geMoldInsight/
|
||||
|
||||
虽然模块化已经成型,但仍有几个关键耦合点需要持续关注:
|
||||
|
||||
### 6.1 共享 ORM 模型
|
||||
### 6.1 共享 ORM 模型 —— 已按模块拆分(2026-09-17,批次 4)
|
||||
|
||||
当前 [src/shared/models/database.py](../src/shared/models/database.py) 同时承载 identity、moldinsight、inventory 三类模型,是当前最强耦合点之一。
|
||||
历史上的 `shared/models/database.py`(31 个模型类三类同居)已拆除,现为按归属分置:
|
||||
|
||||
- [src/shared/models/base.py](../src/shared/models/base.py):唯一 `Base` + 归属约定与全量注册点说明
|
||||
- [src/shared/models/identity.py](../src/shared/models/identity.py):用户/角色/权限/审计(平台层,所有部署形态共用)
|
||||
- [src/moldinsight/models/](../src/moldinsight/models/):STEP 分析域 9 表(stp_files 及各阶段产物、processing_tasks)
|
||||
- [src/inventory/models/](../src/inventory/models/):进销存 15 表(catalog / warehouse / trading / finance 四域文件)
|
||||
|
||||
跨模块只允许裸 FK(规则见 §5.1);全量模型注册点收敛为 `migrations/env.py` 与 `tests/conftest.py`;归属边界由 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定(含单模块独立 mapper 配置与旧模块无 facade 断言)。
|
||||
|
||||
### 6.2 app factory 组合职责偏重
|
||||
|
||||
@@ -199,8 +208,10 @@ geMoldInsight/
|
||||
- 存储方向:
|
||||
- [topics/storage/RUSTFS_STORAGE.md](topics/storage/RUSTFS_STORAGE.md)
|
||||
- [topics/storage/STORAGE_SETUP.md](topics/storage/STORAGE_SETUP.md)
|
||||
- 性能方向:
|
||||
- [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md)(OCC 吞吐与隔离方案设计,TECH_DEBT D10 归属)
|
||||
|
||||
AI、性能、铝泡沫等更偏历史设计/规划性质的专题材料已迁入 [archive/README.md](archive/README.md)。
|
||||
AI、铝泡沫等更偏历史设计/规划性质的专题材料已迁入 [archive/README.md](archive/README.md)。
|
||||
|
||||
阶段性任务清单、迁移计划、历史总结等文档会逐步迁入 [archive/README.md](archive/README.md)。
|
||||
|
||||
|
||||
+5
-1
@@ -16,7 +16,8 @@
|
||||
- `SECRET_KEY`:JWT 签名密钥,**无默认**;生产必须 ≥32 字符强随机。
|
||||
- `ADMIN_PASSWORD`:初始管理员密码,**无默认**;首次建库前必须设置。
|
||||
- `RUSTFS_*`:对象存储(兼容 `MINIO_*` 别名写法);本地开发缺省值仅为占位,连不上会在用到存储的链路报错。
|
||||
- `REDIS_*`:默认 `localhost:6379` 无密码(本地开发语义),生产必须显式覆盖。
|
||||
- `REDIS_*`:默认 `localhost:6379` 无密码(本地开发语义),生产必须显式覆盖;连接串唯一拼装点为 `Settings.redis_url`(Celery broker/backend 复用)。
|
||||
- `MAX_FILE_SIZE`:上传文件大小上限(字节),默认 `104857600`(100MB);此前为死配置(处理器硬编码 50MB),2026-09-17 起真实生效,收紧上限需同步调整该值。
|
||||
- `CORS_ORIGINS`:逗号分隔白名单;不设默认放行 `*`,**生产必须显式设置**。
|
||||
- `LOG_FORMAT`:`json`(生产默认,结构化)/ `text`(开发人可读);`LOG_LEVEL`:DEBUG/INFO/WARNING/ERROR。
|
||||
- `DEBUG`:`true` 时额外注册 `/api/debug/*` 调试路由(仍需登录),**生产必须为 false**。
|
||||
@@ -48,6 +49,9 @@ Celery worker(moldinsight 异步分析链路;本地从 `src` 目录跑,与
|
||||
cd src && celery -A celery_app worker --concurrency=2 --loglevel=info
|
||||
```
|
||||
|
||||
- `--concurrency=N` 即 OCC 并行分析数(每个 prefork 子进程持一个串行 OCC 通道,见 [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md) 方案 A);调大时预算好每子进程内存与 PG 连接数。
|
||||
- 建议生产加 `--max-tasks-per-child=M`(如 50):子进程定期重启,兜底回收 OCC 超时后滞留的线程。
|
||||
|
||||
前端:
|
||||
|
||||
```bash
|
||||
|
||||
+7
-7
@@ -45,7 +45,7 @@ geMoldInsight 已从历史单体逐步演进为“双业务模块 + 共享平台
|
||||
|
||||
重点方向:
|
||||
|
||||
- `advanced_router` 拆分与请求模型规范化
|
||||
- ~~`advanced_router` 拆分与请求模型规范化~~(2026-09-17 批次 3 完成)
|
||||
- 模具分析链路的结构继续收口
|
||||
- OCC 依赖场景下的契约测试/集成测试继续补齐
|
||||
|
||||
@@ -88,13 +88,13 @@ geMoldInsight 已从历史单体逐步演进为“双业务模块 + 共享平台
|
||||
|
||||
### P1:moldinsight API 结构整理
|
||||
|
||||
- 拆分 `advanced_router`
|
||||
- 为高频接口引入 Pydantic 请求模型
|
||||
- 继续减少 `request.json()` 风格手动解析
|
||||
- ~~拆分 `advanced_router`~~(2026-09-17 批次 3 完成)
|
||||
- ~~为高频接口引入 Pydantic 请求模型~~(2026-09-17 批次 3 完成)
|
||||
- 继续减少 `request.json()` 风格手动解析(存量端点已清零,新增接口守此约定)
|
||||
|
||||
### P2:shared/platform 边界继续收敛
|
||||
|
||||
- 梳理共享 ORM 与业务模型的归属
|
||||
- ~~梳理共享 ORM 与业务模型的归属~~(2026-09-17 批次 4 完成:ORM 已按模块拆分,跨模块只许裸 FK)
|
||||
- 继续减少 shared 直接承担业务组合逻辑
|
||||
- 为后续平台层命名与目录调整准备条件
|
||||
|
||||
@@ -117,11 +117,11 @@ geMoldInsight 已从历史单体逐步演进为“双业务模块 + 共享平台
|
||||
| 批次 1 | 部署正确性(1–2 天) | 主链路改走 RustFS(分派入参`file_path` → `stp_file_id`,worker 按 object_key 下载解析);compose 共享卷兜底(过渡);alembic 移出 startup(`AUTO_MIGRATE` 开关);OCC 镜像引入方式修正 + 依赖锁文件 | D6、D12、D13 |
|
||||
| 批次 2 | 任务一致性模型(2–4 天) | PG 为单一事实源、Redis 仅热缓存;去掉多进程内存回退;批量元数据入库;型腔失败标 failed;持久化事务边界收口 | D7、D8、D9、D11 |
|
||||
| 批次 3 | API 与代码结构(3–5 天) | `_safe_include` 失败显式化(/health 暴露缺失路由);advanced_router 拆分 + Pydantic 请求模型;async 重计算统一 executor;StorageIntegrationService 拆分;配置治理 | D1、D14 |
|
||||
| 批次 4 | 架构演进(5 天+) | 共享 ORM 按模块拆分;OCC 吞吐方案设计先行;文档 / 契约同步 | D3、D10 |
|
||||
| 批次 4 | 架构演进(5 天+) | ~~共享 ORM 按模块拆分;OCC 吞吐方案设计先行;文档 / 契约同步~~(2026-09-17 完成) | D3、D10 |
|
||||
|
||||
**执行顺序建议**:批次 0 与批次 1 的 D6(RustFS 主链路)先行——前者是确认的安全漏洞,后者是部署根本性缺陷,两者互不依赖、改动可控。其余按批次顺序推进,每批完成同步 STATUS / TECH_DEBT / API_CONTRACT。
|
||||
|
||||
> 进度:批次 0 / 1 / 2 已于 2026-09-16 完成(D13 的 pip 全量锁文件为批次 1 遗留项,随下次镜像构建补齐;D11 留待后续批次,正确性已由批次 1 共享卷兜底);完成明细见 [STATUS.md](STATUS.md) 与 [TECH_DEBT.md](TECH_DEBT.md) §2.5–2.6。
|
||||
> 进度:批次 0 / 1 / 2 已于 2026-09-16 完成、批次 3 / 4 已于 2026-09-17 完成,§3.1 批次计划**全部执行完毕**(遗留:D13 的 pip 全量锁文件随下次镜像构建补齐;D11 留待后续批次,正确性已由批次 1 共享卷兜底;批次 4 遗留中期项——OCC 进程池方案 B 实施待独立排期,见 [TECH_DEBT.md](TECH_DEBT.md) D10 与 [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md))。完成明细见 [STATUS.md](STATUS.md) 与 [TECH_DEBT.md](TECH_DEBT.md) §2.5–2.8。后续优先项回到 §3 P2 / P3 与主线方向。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
> 文档定位:**唯一的「现在到哪了」**。README / AGENTS / 各主文档只链接到这里,不复制状态内容。
|
||||
> 维护规则:每完整完成一个需求,**倒序在本文顶部加一条**(日期 + 主题 + 关键事实);其余主文档(架构 / 规划 / 技术债 / 部署)维护各自的"当前有效说法",本文只记录"什么时候做到了哪一步"。维护规则出处见根目录 [AGENTS.md](../AGENTS.md)。
|
||||
|
||||
> 2026-09-17(**批次 4(架构演进)完成,§3.1 治理批次全部执行完毕**:① D3 主体清偿——891 行的旧 `shared/models/database.py`(31 模型类三类同居,已删除)按归属拆为 [shared/models/base.py](../src/shared/models/base.py)(唯一 Base)+ [shared/models/identity.py](../src/shared/models/identity.py)(身份权限 7 表)+ [moldinsight/models/](../src/moldinsight/models/)(分析域 9 表)+ [inventory/models/](../src/inventory/models/)(进销存 15 表,catalog/warehouse/trading/finance 四文件);**三条跨模块 ORM relationship(`User.stp_files` / `STPFile.user` / `STPFile.product`)经全仓核实零使用,直接删除**——跨模块桥接收敛为裸 FK 硬规则([ARCHITECTURE.md](ARCHITECTURE.md) §5.1),单模块部署 mapper 可独立配置;约 45 处 import 全量改写(含 migrations/env.py 全量注册、scripts/ 两个一次性脚本),旧模块物理删除无兼容 facade;零调用方死方法 `db_manager.create_tables` 一并删除(拆分后会静默建残缺 schema);② D10 治理——`_reset_occ_executor` 补 `cancel_futures=True`(旧实现下"慢恢复"的旧线程会继续消化旧队列,与新 executor **并发操作非线程安全的 OCC**,属数据竞争而非单纯泄漏);吞吐方案设计先行定稿 [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md)(短期 A:celery `--concurrency` 伸缩 + `--max-tasks-per-child` 兜底,启动参数已记 [OPERATIONS.md](OPERATIONS.md) §3;中期 B:`run_occ` 契约进程化 + kill-on-timeout,待独立排期);③ 新增 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定归属边界(31 表全量注册 / 单模块独立 mapper 配置 / 旧模块无 facade);④ 顺手清偿 D15——[vite.config.ts](../frontend/vite.config.ts) 删除未用的 `mode` 参数,`vue-tsc -b` 恢复通过,前端生产构建链路解除阻断。**接口面零变化**(无路由与 schema 变更,openapi.json 不触发重导出)。**测试基线**:**125 passed, 2 skipped**(基线 122 + 新增归属测试 3 项)。**下一步**:治理批次收尾后回到主线方向;遗留项 D11(HTML RustFS 单源)、D13(pip 锁文件)、OCC 方案 B 独立批次。)
|
||||
|
||||
> 2026-09-17(**批次 3(API 与代码结构)完成**:① D1 清偿——592 行 advanced_router 拆为 [design_router](../src/moldinsight/api/design_router.py) / [cost_router](../src/moldinsight/api/cost_router.py) / [machining_router](../src/moldinsight/api/machining_router.py) / [export_router](../src/moldinsight/api/export_router.py) 四个子路由(端点路径不变),请求体全量 Pydantic 模型化(`request.json()` 手动解析退役,校验统一 422);② 路由装载失败显式化:`ROUTE_MODULES` 清单 + [route_registry](../src/moldinsight/api/route_registry.py),失败经 `/api/health` 呈现 `degraded` 并列出清单(`pythonocc` 改真实探测),DEBUG 下 fail fast——此前失败仅 WARNING 后静默跳过,进程带病启动不可感知;③ 纯 Python 重计算端点(设计/加工/CAM 打包)统一 `asyncio.to_thread` 投放线程池,不再阻塞事件循环(OCC 仍走单线程 executor,D10 留批次 4);④ `StorageIntegrationService`(867 行)按职责拆为 [task_storage](../src/moldinsight/services/task_storage_service.py) / [analysis_storage](../src/moldinsight/services/analysis_storage_service.py) / [file_history](../src/moldinsight/services/file_history_service.py) 三服务,无调用方死代码 `log_user_activity` 删除;⑤ D14 收尾清偿——`MAX_FILE_SIZE` 接线生效(默认上限 50MB→100MB,以 .env 为准)、celery_app 复用 `Settings.redis_url`(连接串唯一拼装点)。**连带修复**:管理员重置密码改 JSON body `{ new_password }`(原裸 str 参数被解析为 query param,前端两个调用点均发 body,功能端到端断裂)+ [UsersView.vue](../frontend/src/modules/users/UsersView.vue) 同步;Dockerfile.celery 的 FROM 对齐 `gemold-backend:latest`(此前引用不存在的 tag,干净环境 celery 镜像必构建失败)。**接口变更三件套随批完成**:openapi.json 重导出(76 paths)+ 前端 `gen:api` 再生。**连带发现**:`npm run build` 因 vite.config.ts 既有 TS6133 失败(与本项目改动无关,登记 D15)。**测试基线**:**122 passed, 2 skipped**(新增 4 个测试文件共 17 项:[test_advanced_split_contract](../tests/test_advanced_split_contract.py) / [test_route_load_status](../tests/test_route_load_status.py) / [test_config_governance](../tests/test_config_governance.py) / [test_auth_password_reset](../tests/test_auth_password_reset.py);skips 为 alembic / celery 缺失环境)。**下一步**:批次 4(架构演进:共享 ORM 拆分、OCC 吞吐方案,见 [ROADMAP.md](ROADMAP.md) §3.1)。)
|
||||
|
||||
> 2026-09-16(**批次 2(任务一致性模型)完成**:① D7 清偿——Redis 进程内存回退**彻底删除**(写 no-op / 读 None,查询路径自然落 PG),PG 为任务状态单一事实源;批量元数据入库:`processing_tasks` 新增 `batch_id` 列(迁移 `a3f8c2d91e47`,**升级后首次启动自动执行**),`GET /api/batch/{batch_id}` 改为 PG 聚合查询 + `STPFile.user_id` 归属校验,删除 Redis batch key 与内存 dict 双通道;`TaskQueryService` PG 视图与 batch 聚合响应补 `progress` / `current_step`(Redis 不可用时前端仍能看到进度);② D8 清偿——型腔分模失败不再吞异常,任务标 failed 并带明确错误(已提交的几何/网格保留);③ D9 清偿——数据本体写方法只 flush,编排层分阶段原子收口(阶段 A 几何+网格、阶段 B 型腔+HTML+特征+指标+验证、完成时参数随状态一并提交),失败先 rollback 再置 failed;进度/状态更新保留即时 commit(长任务进度可见性);upload/batch/advanced 调用方补显式 commit,STPFile + ProcessingTask 原子落库消除孤儿文件记录。D11 未动(共享卷已兜正确性,留后续批次)。**测试基线**:**105 passed, 1 skipped**(新增 [tests/test_batch_status_pg.py](../tests/test_batch_status_pg.py) 4 项 + [tests/test_redis_no_fallback.py](../tests/test_redis_no_fallback.py) 3 项)。**下一步**:批次 3(API 与代码结构:`_safe_include` 失败显式化、advanced_router 拆分 + Pydantic 请求模型、配置治理,见 [ROADMAP.md](ROADMAP.md) §3.1)。)
|
||||
|
||||
> 2026-09-16(**批次 1(部署正确性)完成**:① D6 清偿——分派入参 `file_path` → `stp_file_id`,处理方按 PG 元数据从 RustFS 下载源文件到任务专属临时目录(RustFS 异常时回退节点本地路径),compose 增 `uploads_data` / `html_data` 共享卷过渡兜底;② D12 清偿——新增 `AUTO_MIGRATE` 开关(默认 true 保持单机行为;多副本设 false 改部署流程单点迁移),迁移脚本与 alembic.ini 补进镜像。**连带发现并修复**:迁移目录 `alembic/` 与 alembic 包重名,应用内 `import alembic` 被遮蔽——启动期自动迁移自引入 alembic 起**从未真正生效**(异常被 init_database 吞掉只打日志),且镜像原本未打包迁移脚本;目录已改名 `migrations/`(alembic.ini + 4 处文档引用同步);③ D13 主体——Dockerfile.moldinsight 改为 conda 运行时原生执行(不再跨镜像拷贝 site-packages),基础镜像 tag 锁定;pip 全量锁文件遗留,随下次镜像构建 `pip freeze` 生成;④ compose 关键项去弱默认:`SECRET_KEY` / `ADMIN_PASSWORD` 改 `${VAR:?}` 强制显式配置(与 OPERATIONS「无默认」声明对齐),`create_admin_user` 对空口令显式报错。**测试基线**:**98 passed, 1 skipped**(新增 [tests/test_deployment_config.py](../tests/test_deployment_config.py);alembic 缺失环境 skip)。**遗留**:D13 pip 锁文件;既有问题待查——Dockerfile.celery `FROM gemold-moldinsight:latest`,而 build.sh 只构建 `gemold-backend` tag,干净机器上 build.sh 的 celery 步骤会失败。**下一步**:批次 2(任务一致性模型,见 [ROADMAP.md](ROADMAP.md) §3.1)。)
|
||||
|
||||
+51
-50
@@ -55,29 +55,35 @@
|
||||
- 持久化事务边界收口:数据本体分阶段原子提交、失败先回滚再置 failed(原 D9)
|
||||
- D11(HTML 双写双读)本批未动:正确性已由共享卷兜底,RustFS 单一来源留待后续批次
|
||||
|
||||
### 2.7 API 与代码结构(2026-09-17,批次 3)
|
||||
- `advanced_router` 按职责拆为 design / cost / machining / export 四个子路由,端点路径不变,请求体全量 Pydantic 化(原 D1)
|
||||
- 路由装载失败显式化:`ROUTE_MODULES` 清单 + route_registry,失败经 `/api/health` 呈现 degraded(含真实 pythonocc 探测),DEBUG 下 fail fast
|
||||
- 纯 Python 重计算端点(设计/加工/CAM 打包)统一 `asyncio.to_thread` 投放线程池,不再阻塞事件循环;OCC 操作仍走单线程 executor(D10 不变,批次 4)
|
||||
- `StorageIntegrationService`(867 行)按职责拆为 TaskStorage / AnalysisStorage / FileHistory 三服务;无调用方的 `log_user_activity` 死代码删除
|
||||
- 配置治理收尾:`MAX_FILE_SIZE` 接线生效、celery_app 复用 `Settings.redis_url`(原 D14)
|
||||
- 连带修复:管理员重置密码改 JSON body(原裸 str 参数被解析为 query param,前端发 body 必 422,功能端到端断裂);Dockerfile.celery 的 FROM tag 与 compose/build.sh 实际构建的 `gemold-backend:latest` 对齐(此前干净环境 celery 镜像必构建失败)
|
||||
- 接口变更三件套随批完成:openapi.json 重导出(76 paths)+ 前端 `gen:api`
|
||||
|
||||
### 2.8 架构演进(2026-09-17,批次 4)
|
||||
- 共享 ORM 按模块拆分(原 D3 主体):891 行 `shared/models/database.py`(31 模型类三类同居)拆为 `shared/models/base.py`(唯一 Base + 归属约定)/ `shared/models/identity.py`(7 表)/ `moldinsight/models/`(9 表)/ `inventory/models/`(catalog/warehouse/trading/finance 15 表);**三条跨模块 ORM relationship(`User.stp_files`、`STPFile.user`、`STPFile.product`)经全仓核实均无使用方,直接删除**——跨模块桥接收敛为裸 FK 硬规则(ARCHITECTURE §5.1),单模块部署不再依赖另一侧模型注册;约 45 处 import 全量改写,无兼容 facade;全量注册点收敛为 migrations/env.py 与 tests/conftest.py;零调用方的死方法 `db_manager.create_tables` 一并删除(拆分后会静默建残缺 schema)
|
||||
- OCC 泄漏治理 + 吞吐方案设计先行(原 D10):`_reset_occ_executor` 补 `cancel_futures=True`——不止卫生问题:旧实现下"慢恢复"的旧线程会继续消化旧队列,与新 executor **并发操作非线程安全的 OCC**(数据竞争);吞吐路线定稿于 [topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md)(短期 A:celery prefork 伸缩 + max-tasks-per-child 兜底;中期 B:run_occ 接口进程化 + kill-on-timeout 根治)
|
||||
- 归属边界回归测试:[tests/test_model_ownership.py](../tests/test_model_ownership.py)(31 表全量注册、单模块独立 mapper 配置、旧模块无 facade)
|
||||
|
||||
详细历史过程保留在原始技术债文档中,后续将转入归档。
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前活跃技术债
|
||||
|
||||
### D1. `advanced_router` 过大,职责混杂
|
||||
### D1. `advanced_router` 过大,职责混杂 —— 已清偿(2026-09-17,批次 3)
|
||||
|
||||
现状:
|
||||
- 导出、估算、设计/分析相关接口仍混在同一个 router 中
|
||||
- 请求体仍有较多手动解析逻辑
|
||||
修复内容:
|
||||
- 592 行的 advanced_router 按职责拆为四个子路由,端点路径全部不变:[design_router.py](../src/moldinsight/api/design_router.py)(布局/冷浇/模架/倒扣)、[cost_router.py](../src/moldinsight/api/cost_router.py)、[machining_router.py](../src/moldinsight/api/machining_router.py)(CAM/碰撞/刀路/电极/仿真)、[export_router.py](../src/moldinsight/api/export_router.py)(导出/下载/建议)
|
||||
- 全部请求体改 Pydantic 模型(`request.json()` 手动解析退役),校验失败统一 422;`_get_cached_import` 上提为 [core_modules.py](../src/moldinsight/api/core_modules.py) 共用
|
||||
- 契约测试:[tests/test_advanced_split_contract.py](../tests/test_advanced_split_contract.py)(路径不丢、鉴权不丢、422 语义、纯计算端点冒烟)
|
||||
- openapi.json 重导出 + 前端 `gen:api`(接口变更三件套随批完成)
|
||||
|
||||
影响:
|
||||
- 路由边界不清晰
|
||||
- OpenAPI 可读性差
|
||||
- 接口参数校验不统一
|
||||
- 后续继续扩展时维护成本高
|
||||
|
||||
建议:
|
||||
- 拆分为 export / design / cost 等子路由
|
||||
- 高优先级请求体改为 Pydantic 模型
|
||||
|
||||
优先级:**P1**
|
||||
~~原现状 / 影响~~:导出/估算/设计接口混在单文件,边界不清晰、OpenAPI 可读性差、参数校验不统一。
|
||||
|
||||
### D2. 铝价模拟数据未显式标注来源
|
||||
|
||||
@@ -93,21 +99,17 @@
|
||||
|
||||
优先级:**P2**
|
||||
|
||||
### D3. shared/platform 边界仍需继续收敛
|
||||
### D3. shared/platform 边界仍需继续收敛 —— ORM 归属已清偿(2026-09-17,批次 4)
|
||||
|
||||
现状:
|
||||
- `shared` 同时承担平台基础能力与部分历史耦合职责
|
||||
- 共享 ORM 与 app factory 仍是主要耦合点
|
||||
已完成部分:
|
||||
- 共享 ORM(原最强耦合点)按模块拆分:base / identity(shared)+ moldinsight/models + inventory/models;跨模块只允许裸 FK,单模块部署 mapper 可独立配置(详见 §2.8 与 [ARCHITECTURE.md](ARCHITECTURE.md) §6.1)
|
||||
- 旧 `shared/models/database.py` 物理删除,无兼容 facade;归属边界由 [tests/test_model_ownership.py](../tests/test_model_ownership.py) 锁定
|
||||
|
||||
影响:
|
||||
- 模块边界认知成本较高
|
||||
- 新增逻辑容易继续堆入 shared
|
||||
仍保留的收敛方向(低优先级,随实际重构推进):
|
||||
- [app_factory.py](../src/shared/app_factory.py) 组合职责偏重(ARCHITECTURE §6.2)
|
||||
- identity / platform 的边界语义(ROADMAP §2.1)
|
||||
|
||||
建议:
|
||||
- 继续从文档、目录语义、职责边界上推进收敛
|
||||
- 在后续实际重构中优先避免把业务逻辑继续沉入 shared
|
||||
|
||||
优先级:**P2**
|
||||
优先级:**P3**(剩余部分)
|
||||
|
||||
### D4. 文档现状 / 规划 / 历史混放
|
||||
|
||||
@@ -171,19 +173,17 @@
|
||||
|
||||
~~原现状 / 影响~~:各存储方法内部自行 commit,型腔保存失败留半成品数据且任务仍 completed。
|
||||
|
||||
### D10. OCC 全局单线程串行 + 超时重建泄漏线程
|
||||
### D10. OCC 全局单线程串行 + 超时重建泄漏线程 —— 泄漏治理已落地,吞吐方案设计先行(2026-09-17,批次 4)
|
||||
|
||||
现状:
|
||||
- 所有 OCC 操作经 `max_workers=1` executor 串行([processing_service.py](../src/moldinsight/services/processing_service.py)),celery 并发无法扩展 OCC 吞吐
|
||||
- 超时重建 executor 每次泄漏 1 个线程,长期运行只涨不降
|
||||
已完成:
|
||||
- `_reset_occ_executor` 补 `cancel_futures=True`:排队任务随重建丢弃——旧实现下"慢恢复"的旧线程会继续消化旧队列,与新 executor 并发操作非线程安全的 OCC;修复后残留收敛为"运行中线程滞留 1 个"(C++ 栈 Python 层不可杀,属客观边界)
|
||||
- 吞吐与隔离方案定稿:[topics/performance/OCC_THROUGHPUT.md](topics/performance/OCC_THROUGHPUT.md)——短期方案 A(celery `--concurrency` 伸缩 + `--max-tasks-per-child` 进程回收兜底,零新代码,启动参数见 [OPERATIONS.md](OPERATIONS.md) §3);中期方案 B(`run_occ` 改操作名+payload 契约、常驻 OCC 进程池 kill-on-timeout 根治泄漏,待独立批次)
|
||||
|
||||
影响:
|
||||
- 一个长耗时型腔生成阻塞全部几何处理;线程随故障累积
|
||||
保留为已知约束(非待修缺陷):
|
||||
- 单进程内 OCC 串行是正确性要求(OCC 非线程安全),吞吐扩展走多进程(方案 A/B)
|
||||
- 线程级超时的滞留线程由进程边界回收,根治依赖方案 B 落地
|
||||
|
||||
建议:
|
||||
- 记录吞吐上限为已知约束;线程泄漏治理方案设计先行(见 [ROADMAP.md](ROADMAP.md) §3.1 批次 4)
|
||||
|
||||
优先级:**P2**
|
||||
优先级:**P3**(中期方案 B 实施前维持观察)
|
||||
|
||||
### D11. HTML 报告本地磁盘与 RustFS 双写双读
|
||||
|
||||
@@ -216,20 +216,21 @@
|
||||
|
||||
优先级:**P2**(剩余锁文件部分)
|
||||
|
||||
### D14. 配置漂移:弱默认 / 死配置 / 重复解析
|
||||
### D14. 配置漂移:弱默认 / 死配置 / 重复解析 —— 已清偿(2026-09-16 ~ 09-17,批次 1 / 3)
|
||||
|
||||
现状:
|
||||
- ~~RUSTFS_* 弱默认~~(2026-09-16 代码侧已去除);~~compose 侧 SECRET_KEY / ADMIN_PASSWORD 弱默认~~(2026-09-16 已去除:改用 `${VAR:?}` 强制显式配置,`create_admin_user` 对空 ADMIN_PASSWORD 显式报错)
|
||||
- MAX_FILE_SIZE 配置项未被使用([file_handler.py](../src/shared/utils/file_handler.py) 硬编码 50MB)
|
||||
- [celery_app.py](../src/celery_app.py) 重新 load_dotenv 并手拼 REDIS URL,与 settings 两份实现
|
||||
修复内容:
|
||||
- ~~RUSTFS_* 弱默认~~(批次 0 代码侧去除);~~compose 侧 SECRET_KEY / ADMIN_PASSWORD 弱默认~~(批次 1 改 `${VAR:?}` 强制显式配置)
|
||||
- ~~MAX_FILE_SIZE 死配置~~(批次 3):upload/batch 路由的 `FileHandler` 接 `settings.UPLOAD_DIR / settings.MAX_FILE_SIZE`(此前处理器硬编码 50MB;接线后默认上限变为 100MB,以 .env 为准)
|
||||
- ~~celery_app 重复拼装~~(批次 3):删除自行 load_dotenv + 手拼 REDIS URL,broker/backend 复用 `Settings.redis_url`(新增 property,连接串唯一拼装点)
|
||||
|
||||
影响:
|
||||
- 违背"关键项不兜底"硬约束;配置行为与文档不一致
|
||||
优先级:已清偿
|
||||
|
||||
建议:
|
||||
- 去掉弱默认、对齐或删除死配置、celery_app 复用 settings
|
||||
### D15. 前端 `npm run build` 因既有 TS 错误失败(批次 3 连带发现)—— 已清偿(2026-09-17,批次 4)
|
||||
|
||||
优先级:**P2**
|
||||
修复内容:
|
||||
- [vite.config.ts](../frontend/vite.config.ts) 删除未使用的回调参数 `mode`(TS6133 源头,一行修复);`vue-tsc -b` 实测通过,生产构建链路恢复
|
||||
|
||||
~~原现状 / 影响~~:`vue-tsc -b`(`npm run build` 的类型检查步)因既有 TS6133 失败,前端无法出生产包(与批次 3 改动无关的既有问题)。
|
||||
|
||||
---
|
||||
|
||||
@@ -238,14 +239,14 @@
|
||||
> 注:2026-09-15 后端设计审查后,治理**执行顺序**以 [ROADMAP.md](ROADMAP.md) §3.1 批次计划为准(批次 0–4);D5–D14 的批次归属见该表。本节保留原有优先项作为补充说明。
|
||||
|
||||
### 第一优先级
|
||||
1. `advanced_router` 拆分
|
||||
2. 高优先级接口补 Pydantic 请求模型
|
||||
1. ~~`advanced_router` 拆分~~(2026-09-17 批次 3 完成,见 D1)
|
||||
2. ~~高优先级接口补 Pydantic 请求模型~~(2026-09-17 批次 3 完成)
|
||||
3. 文档主骨架收口并减少重复说明
|
||||
|
||||
### 第二优先级
|
||||
4. 铝价模拟数据来源显式化
|
||||
5. 部署历史文档归档
|
||||
6. shared/platform 语义继续收敛
|
||||
6. shared/platform 语义继续收敛(共享 ORM 归属已于批次 4 清偿,剩余为 app_factory 组合职责等,见 D3)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# OCC 处理吞吐与隔离方案设计(OCC_THROUGHPUT)
|
||||
|
||||
> 文档定位:**OCC(PythonOCC)处理吞吐与故障隔离的专题设计文档**。
|
||||
> 本文回答"OCC 串行瓶颈与超时线程泄漏的根治路线";现状事实以 [../../../STATUS.md](../../../STATUS.md) 为准,债务归属 [../../TECH_DEBT.md](../../TECH_DEBT.md) D10。
|
||||
> 2026-09-17 随批次 4 产出:**方案设计先行**,短期项(方案 A)零代码可用,中期的接口演进与进程池实施留待后续批次。
|
||||
|
||||
---
|
||||
|
||||
## 1. 现状与硬约束
|
||||
|
||||
### 1.1 运行时事实
|
||||
|
||||
- 所有 OCC 操作(STEP 解析 / 布尔运算 / 三角化 / 倒扣检测等)统一经 [processing_service.py](../../../src/moldinsight/services/processing_service.py) 的 `run_occ` 投入 **进程内 `ThreadPoolExecutor(max_workers=1)`** 串行执行——OCC 非线程安全,串行是正确性要求,不是实现偷懒。
|
||||
- Celery worker 为 prefork 模式,`processing_service` 是模块级单例:**每个 worker 子进程各持一个串行 OCC 通道**。因此 OCC 并行度 = worker 子进程数,与 web 进程数无关(web 侧 `run_occ` 仅服务于轻量同步调用,如倒扣检测)。
|
||||
- 型腔生成超时后 `_reset_occ_executor` 重建 executor;已在运行的 C++ 线程在 Python 层**不可杀**,每次超时滞留 1 个线程(2026-09-17 起排队任务随 `cancel_futures=True` 丢弃,见 §4)。
|
||||
|
||||
### 1.2 硬约束(决定方案边界)
|
||||
|
||||
| 约束 | 含义 |
|
||||
|---|---|
|
||||
| OCC 非线程安全 | 任何方案中,**一个进程内 OCC 操作必须串行**;并行只能靠多进程 |
|
||||
| C++ 栈不可中断 | 线程级超时只能"抛弃"不能"击杀";**只有进程级 kill 是干净的故障恢复** |
|
||||
| `run_occ(fn, *args)` 传闭包/绑定方法 | 函数对象不可跨进程 pickle——进程化方案必须改接口为"操作名 + 可序列化参数" |
|
||||
| STEP 重载成本 | 进程间不共享 OCC 形状对象;跨进程方案每次调用需重新读文件/传 BRep(几秒级) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案对比
|
||||
|
||||
### 方案 A:Celery prefork 并行伸缩(短期,零新代码)
|
||||
|
||||
**做法**:承认"每子进程一个串行 OCC 通道"的既有事实,把 OCC 吞吐问题转化为 worker 进程数问题:`celery -A celery_app worker --concurrency=N`,N = 期望的并行分析数(受 CPU 核数与每进程内存约束)。
|
||||
|
||||
- **优点**:零代码改动;进程边界天然兜住线程泄漏——泄漏线程随子进程存亡,配合 `--max-tasks-per-child=M`(子进程处理 M 个任务后重启回收)可把滞留线程的存续时间限制在一个批次内。
|
||||
- **代价**:每个子进程常驻完整 Python + OCC 运行时(数百 MB),N 不能无脑调大;DB 连接按 celery 角色池(pool_size=5)随子进程倍增,PG `max_connections` 需要相应预算。
|
||||
- **不解决**:单任务超时后该子进程内的线程滞留(被 max-tasks-per-child 兜底回收);单任务无加速(串行本质不变)。
|
||||
|
||||
**结论:立即可用的推荐做法**。部署侧调整(concurrency / max-tasks-per-child)随下次镜像与 compose 评审落地,先在 [OPERATIONS.md](../../OPERATIONS.md) 记录启动参数建议。
|
||||
|
||||
### 方案 B:常驻 OCC 进程池 + kill-on-timeout(中期,推荐演进方向)
|
||||
|
||||
**做法**:在 `run_occ` 接口之下替换执行器——不再是 `ThreadPoolExecutor`,而是**常驻的单线程 OCC 工作进程池**(每进程一个事件循环:接任务 → 执行 → 回报)。超时由主进程 `terminate()` 工作进程并更换新进程补位。
|
||||
|
||||
- 接口演进:`run_occ(fn, *args)` → `run_occ(op_name: str, payload: dict)`,操作名注册表映射到模块级函数(STEP 文件路径进、JSON/BRep 文件出,杜绝 pickle 大对象);各调用点(解析、型腔、倒扣、导出三角化……)逐一迁移。
|
||||
- **优点**:超时 = 杀进程,**故障恢复干净彻底**(D10 残留泄漏根治);OCC 崩溃(segfault)不再波及 API/worker 主进程;进程池大小与 celery 并发解耦。
|
||||
- **代价**:一次明确的接口迁移(所有 `run_occ` 调用点 + 结果序列化);进程池自管理(补位、健康检查、启动预热——spawn 下 import OCC 秒级,需常驻而非按任务拉起);跨进程只传文件路径 + JSON,现有"传形状对象"的内部调用要改为落盘中转。
|
||||
- **风险**:自建进程池的运维复杂度;Windows 开发环境 spawn 语义与 Linux fork 差异需测试覆盖。
|
||||
|
||||
### 方案 C:OCC sidecar 服务(长期,视伸缩需求)
|
||||
|
||||
**做法**:OCC 能力独立成进程/容器(HTTP 或 gRPC),API 与 worker 都是客户端;STEP 按路径/对象键传入,返回 JSON 摘要 + 产物对象键。
|
||||
|
||||
- **优点**:隔离最彻底;OCC 可独立伸缩、独立发布、独立扩容 GPU/内存型节点;多语言可复用。
|
||||
- **代价**:新增一个部署单元与序列化边界(大网格/形状数据传输设计);超出当前"单 compose 栈"的部署叙事,需与 DEPLOYMENT 文档体系一起演进。
|
||||
|
||||
**结论:除非出现独立伸缩/隔离性硬需求,暂不启动。**
|
||||
|
||||
---
|
||||
|
||||
## 3. 决策与路线
|
||||
|
||||
| 阶段 | 动作 | 状态 |
|
||||
|---|---|---|
|
||||
| 短期 | 方案 A:`--concurrency` 伸缩 + `--max-tasks-per-child` 兜底回收;`cancel_futures=True` 修复重建并发风险 | ✅ 代码侧 2026-09-17 完成;部署参数随下次 compose/镜像评审落地 |
|
||||
| 中期 | 方案 B:`run_occ(op_name, payload)` 接口演进 + 常驻进程池,kill-on-timeout 根治泄漏 | 待排期(独立批次,工作量集中在调用点迁移与序列化设计) |
|
||||
| 长期 | 方案 C:sidecar,仅在出现独立伸缩需求时启动 | 暂不启动 |
|
||||
|
||||
## 4. 本次已落地的缓解(2026-09-17,批次 4)
|
||||
|
||||
`_reset_occ_executor` 的 `shutdown(wait=False)` 补 `cancel_futures=True`。这不只是卫生问题:旧实现下旧 executor 的**排队任务不会消失**——若挂死线程后来"慢恢复",旧线程会继续消化旧队列,与新 executor **并发操作非线程安全的 OCC**(数据竞争 / 崩溃风险)。补参后排队任务即被丢弃,残留问题收敛为"运行中线程滞留 1 个",由方案 A 的进程回收兜底。
|
||||
Reference in New Issue
Block a user