# M1:骨架管线 — The Skeleton Pipeline > **所属:** 工具 3 全文智能提取工作台 V2.0 > **架构总纲:** `08-工具3-全文智能提取工作台V2.0开发计划.md` > **代码手册:** `08d-工具3-代码模式与技术规范.md`(所有代码模式均在此手册中,开发时按需查阅) > **建议时间:** Week 1(5.5-6.5 天,含 v1.6 Sweeper 清道夫 0.5 天) > **核心目标:** 证明 "PKB 拿数据 → Fan-out 分发 → LLM 盲提 → 数据落库 → 前端看到 completed" 这条管线是通的。 --- ## Demo 形态 用户在前端点击按钮,系统后台静默跑完流程,前端 `useTaskStatus` 轮询到 `status = completed`,数据库能查到 JSON 提取结果。前端只需一个极简列表。 **关键妥协:M1 不接 MinerU,不做审核抽屉,不做 SSE 日志流。** --- ## 任务清单 ### M1-1:Prisma 数据模型 + Migration + Seed(1 天) **做什么:** - 新增 `AslExtractionTemplate`、`AslProjectTemplate`、`AslExtractionTask`、`AslExtractionResult` 四张表 - 运行 `npx prisma migrate dev --name add_extraction_template_engine` - Seed 脚本注入 3 套系统内置模板(RCT / Cohort / QC) **验收标准:** - [ ] `npx prisma migrate deploy` 成功 - [ ] `npx prisma db seed` 后数据库有 3 套模板记录 - [ ] `AslExtractionTask` 含 `pkbKnowledgeBaseId` 字段 - [ ] `AslExtractionResult` 含 `snapshotStorageKey` + `snapshotFilename` 快照字段(v1.5) - [ ] `AslExtractionResult` 含 `pkbDocumentId` 字段、`status` 含 `extracting` 状态值 > 📖 Schema 详情见架构总纲 Task 1.1 --- ### M1-2:模板 API + 提取任务 API — 仅基座模板(1.5 天) **做什么:** - `TemplateController.ts`:GET 模板列表、GET 模板详情、POST 克隆到项目 - `ExtractionController.ts`:POST 创建任务、GET 任务状态(**React Query 轮询用**)、GET 结果列表 - 创建任务时:锁定模板 → 批量创建 `AslExtractionResult`(status=pending)→ `pgBoss.send('asl_extraction_manager', { taskId })` **不做什么:** - 不做自定义字段 CRUD API(M3) - 不做 SSE 端点(M2) - 不做 Excel 导出(M2) **验收标准:** - [ ] `POST /api/v1/asl/extraction/tasks` 能创建任务并入队 - [ ] `GET /api/v1/asl/extraction/tasks/:taskId` 返回 `status`、`successCount`、`totalCount` - [ ] `GET /api/v1/asl/extraction/tasks/:taskId/results` 返回提取结果列表 > 📖 端点完整列表见架构总纲 Task 1.3 + Task 2.4 --- ### M1-3:PKB ACL 防腐层 + Fan-out 调度核心(2 天)⚠️ 本里程碑最关键 **做什么(按顺序):** **Step A — PKB 侧 ACL(0.5 天):** - `PkbExportService.ts`(PKB 模块维护):`listKnowledgeBases()`、`listPdfDocuments()`、`getDocumentForExtraction()` 返回 DTO - 通过依赖注入暴露给 ASL **Step B — ASL 侧桥接(0.5 天):** - `PkbBridgeService.ts`:调用 `PkbExportService`,代理所有 PKB 数据访问 **Step C — Fan-out Manager + Child Worker(1 天)⚠️ 核心战役:** - `ExtractionManagerWorker.ts`:读取任务 → 🆕 **v1.6 空集合守卫**(`results.length === 0` → 直接 completed) → ⚠️ v1.5 批量快照 PKB 元数据(`snapshotStorageKey` + `snapshotFilename`)冻结到 `AslExtractionResult` → 为每篇文献 `pgBoss.send('asl_extraction_child', ...)` → 退出(Fire-and-forget) - `ExtractionChildWorker.ts` 完整逻辑: 1. **乐观锁抢占**:`updateMany({ where: { status: 'pending' }, data: { status: 'extracting' } })` 2. **纯文本降级提取**:从 PKB 读 `extractedText` + 写死 RCT Schema → 调用 DeepSeek 3. **原子递增**:事务内 `update Result + increment Task counts` 4. **Last Child Wins**:`successCount + failedCount >= totalCount` → 翻转 `status = completed` 5. **错误分级路由**:致命错误 return / 🆕 **v1.6 临时错误 throw 前释放乐观锁(回退 status → pending)** **Step D — 🆕 Sweeper 清道夫注册(0.5 天)(v1.6 新增):** - `asl_extraction_sweeper`:pg-boss 定时任务,每 10 分钟扫描 `processing` 且 `updatedAt > 2h` 的任务,强制标记 `failed` - 使用 `updatedAt`(最后活跃时间)判断卡死,禁止用 `startedAt`(防误杀健康的超大批量任务) **Worker 注册(遵守队列命名规范):** ``` jobQueue.work('asl_extraction_child', { teamConcurrency: 10 }, handler) ``` **M1 阶段简化:不注册 `asl_mineru_extract` 子队列(M2 才接 MinerU)。** **验收标准:** - [ ] PkbExportService 能返回知识库列表和文档详情(DTO) - [ ] Manager 派发后 `AslExtractionResult.snapshotStorageKey` 和 `snapshotFilename` 已填充(v1.5 快照验证) - [ ] 手动删除 PKB 文档记录后,Child Worker 仍能通过 `snapshotStorageKey` 从 OSS 获取 PDF(v1.5 一致性验证) - [ ] Manager 能为 N 篇文献派发 N 个 Child Job - [ ] Child Worker 乐观锁正确:并发重试不会双倍处理 - [ ] Child Worker 原子递增:10 篇并发提取后 `successCount = 10` - [ ] Last Child Wins:最后一个 Child 翻转 Task status = completed - [ ] 致命错误(PKB 文档不存在)→ 该篇标 error + 不重试 + 不阻塞其他篇 - [ ] 临时错误(429)→ pg-boss 指数退避重试 - [ ] 🆕 临时错误 throw 前回退 `status → pending`:模拟 429 重试后乐观锁仍能抢占成功(v1.6 乐观锁释放验证) - [ ] 🆕 Manager 空集合守卫:`results.length === 0` 时 Task 直接标记 `completed`(v1.6 边界验证) - [ ] 🆕 Sweeper 清道夫已注册:`asl_extraction_sweeper` 定时任务在 pg-boss 中可查到(v1.6) - [ ] 🆕 Sweeper 判定条件为 `updatedAt > 2h`,而非 `startedAt`(v1.6 防误杀验证) > 📖 Fan-out 架构图、Worker 代码模式、研发红线见架构总纲 Task 2.3 > 📖 ACL 防腐层设计见架构总纲 Task 3.3b > 📖 Sweeper、乐观锁释放、空集合守卫代码见 08d §4.2 / §4.3 / §4.6 --- ### M1-4:前端极简 Step 1 — 选模板 + 选 PKB 文献(1 天) **做什么:** - `ExtractionSetup.tsx`:左栏模板下拉(只读,默认 RCT)+ 右栏 PKB 知识库下拉 + 文献 Checkbox 列表 - `PkbKnowledgeBaseSelector.tsx`:调用 PKB API 加载知识库和文献 - 底部 "确认并开始提取" 按钮 → 调用 `POST /api/v1/asl/extraction/tasks` **不做什么:** - 不做自定义字段 UI(M3) - 不做基座字段标签云展示(M2 附带做) **验收标准:** - [ ] 能选择 PKB 知识库并展示 PDF 文档列表 - [ ] 能勾选文献并提交创建任务 - [ ] 空知识库时显示引导提示 + PKB 跳转链接 --- ### M1-5:前端极简 Step 2 + Step 3 — 轮询进度 + 极简列表(1 天) **做什么:** - `ExtractionProgress.tsx`:`useTaskStatus` 轮询(3s)驱动进度条 + 检测 `completed` 跳转 - `ExtractionWorkbench.tsx`:极简表格展示提取结果(Study ID、状态) - `ExtractionPage.tsx`:状态驱动路由(pending→Step1 / processing→Step2 / completed→Step3) - 路由注册(前端 + 后端) **不做什么:** - 不做 SSE 日志终端(M2) - 不做审核抽屉(M2) - 不做 Excel 导出按钮(M2) **验收标准:** - [ ] 进度条从 0% 推进到 100%(React Query 轮询驱动) - [ ] `status = completed` 后自动跳转到 Step 3 - [ ] Step 3 能看到提取结果列表(状态列展示 completed/error) - [ ] 关闭浏览器重新打开 → 恢复到正确步骤(断点恢复) --- ## M1 研发红线(全员必须背诵) | # | 红线 | 违反后果 | |---|------|---------| | 1 | 队列名用下划线(`asl_extraction_child`),禁止点号 | pg-boss 路由截断 | | 2 | Child Worker 用 `updateMany` 乐观锁,禁止 `findUnique → if` | 并发穿透,算力翻倍 | | 3 | Last Child Wins 终止器,成功和失败路径都要检查 | Task 永远卡在 processing | | 4 | `teamConcurrency: 10`,禁止无限拉取 Child Job | Node.js OOM | | 5 | Job Payload 仅传 ID(< 200 bytes),禁止塞 PDF 正文 | pg-boss 阻塞 | | 6 | ACL 防腐层:ASL 不 import PKB 内部类型 | 模块耦合蔓延 | | 7 | Manager 必须快照 `snapshotStorageKey` + `snapshotFilename`,Child 禁止运行时回查 PKB 获取 storageKey(v1.5) | 提取中 PKB 删文档 → 批量崩溃 | | 8 | 🆕 临时错误 `throw` 前必须 `update({ status: 'pending' })` 释放乐观锁(v1.6) | 重试时被"幂等跳过",计数永远缺一票,Task 永久卡死 | | 9 | 🆕 Manager 必须检查 `results.length === 0` 并直接 completed(v1.6) | 空文献 → 无 Child → Last Child Wins 死锁 | | 10 | 🆕 必须注册 `asl_extraction_sweeper` 清道夫(`updatedAt > 2h`,禁止用 `startedAt`)(v1.6) | 进程 OOM/SIGKILL 后 Task 永久挂起 | --- ## M1 结束时的状态 ``` ✅ Prisma 表 + 3 套 Seed 模板 ✅ PKB ACL 防腐层 → PkbExportService + PkbBridgeService ✅ Fan-out 全链路:Manager → N × Child → Last Child Wins → completed ✅ 乐观锁 + 原子递增 + 错误分级路由 — 所有并发 Bug 已验证 ✅ 🆕 Sweeper 清道夫注册(v1.6 防 OOM/SIGKILL 卡死) ✅ 🆕 乐观锁释放 + 空集合守卫 + pg_notify 参数化(v1.6 全量代码级同步) ✅ 前端三步走:选模板/选文献 → 轮询进度 → 极简结果列表 ❌ 无 MinerU(纯文本降级) ❌ 无 SSE 日志流 ❌ 无审核抽屉 ❌ 无自定义字段 ❌ 无 Excel 导出 ``` > **M1 的核心价值:** 所有分布式 Bug(并发死锁、幂等穿透、终点丢失、背压 OOM)在第一周就被逼出来。M2 加特性时地基是稳的。