ci / 质量门禁 (push) Successful in 5m25s
背景:A6 全部 WBS 与门禁完成,按文档规范同批更新实现记录与计划状态。 改动: - 新增 docs/开发计划/灵犀_A6任务中心与队列骨架_实施报告_V0.1.md:范围与完成度、交付物、门禁结果、关键口径(202 / 幂等 / 取消重试 / SSE / 状态机 / 队列物理名 / Outbox 退避 / 前端预估 / 角标)、与计划差异、已知遗留与下一步。 - 开发计划:状态行与 A6 批次置为已完成、§6.7 增加执行状态与完成情况、§7.6 增加状态行、M0 清单勾选任务中心。 - README:索引新增 A6 报告,当前进度推进到 A7。 影响面:仅文档与索引;无代码行为变化。 回滚:撤销提交恢复文档。 验证:pnpm format:check 通过;报告中的门禁数据来自本批实际执行结果。
14 KiB
14 KiB
灵犀 A6 任务中心与队列骨架 · 实施报告 V0.1
| 项 | 内容 |
|---|---|
| 批次 | A6 任务中心与队列骨架(开发计划 §6.7 / §7.6,架构 §3.6 / §3.7 / §6.4) |
| 完成日期 | 2026-11-05 |
| 状态 | ✅ 已完成(A6-BE-01 |
| 用户选定方案 | 前端采用 方案 A · 经典管理台(design/a6-ui-v1/a) |
| 代码提交 | 4a29cf8(shared 契约与数据模型)、633765f(API / SSE / 队列 / worker)、A6 前端提交见仓库 feat(web) |
| 本地依赖 | PostgreSQL 18 / Redis 7.4;pnpm check:dev 通过 |
1 范围与完成度
| 编号 | 交付 | 状态 |
|---|---|---|
| A6-BE-01 | 任务协议与九态状态机、media_tasks / media_items / task_events、提交预估、Idempotency-Key 24h |
✅ |
| A6-BE-02 | BullMQ 5 九队列(5 组)、worker 按 QUEUE 启停、重试 / 退避 / 死信 / 并发上限、Redis 锁 |
✅ |
| A6-BE-03 | SSE /media-tasks/{id}/events:心跳、Last-Event-ID 续推、进度落库、轮询兜底 |
✅ |
| A6-BE-04 | 跨类型任务列表 / 详情(客户 / 状态 / 类型 / 时间 / 我提交的 / 关键字)、取消、仅重试失败项、失败行号 | ✅ |
| A6-BE-05 | outbox_events 事务写入 + dispatcher 至少一次投递 + jobId 幂等 + 死信 + 本地结果事件审计 |
✅ |
| A6-INF-01 | /api/v1/health/queues、/api/v1/metrics(Prometheus 0.0.4)、4 条告警规则文件、worker 心跳 |
✅(未接真实 Grafana,见 §5) |
| A6-FE-01 | /material/tasks、/material/tasks/:id:列表 / 详情 / 提交演练任务 / 取消 / 仅重试失败项 / 三态 / 权限三态 |
✅ |
| A6-FE-02 | 可复用任务组件(类型 / 状态 / 进度条 / 进度环 / SSE 状态)、顶栏全局角标、待处理失败信号、完成 / 失败通知 | ✅ |
| A6-QA-01 | 幂等 / 取消 / 重试 / 断线续推 / 进程重启恢复 / 权限三态 / 浏览器 e2e | ✅ |
本批门禁对照:提交 → SSE → 完成 / 失败闭环 ✅;幂等与取消生效 ✅;五个队列组独立启停 ✅;进度落库、刷新 / 重连 / 进程重启不丢 ✅。
2 交付物
2.1 共享契约(packages/shared)
src/tasks/task-protocol.ts(+ spec):任务类型、架构 §3.6 九态与允许迁移、5 组 9 队列定义(并发 / 重试 / 退避)、Outbox 事件、SSE 事件、进度与收敛纯函数。src/schemas/tasks.ts(+ spec):提交 / 响应 / 摘要 / 详情 / 列表 query+response / 取消 / 重试 / SSE 事件载荷;body camelCase、query snake_case、列表{ items, next_cursor }。src/config/system-configs.ts:新增task分组 3 项(task.demo.max_items=20/task.estimate.demo_cost_per_item=0.02/task.estimate.demo_seconds_per_item=3);后两项置为public供前端提交前展示预估(PRD R-06),数值非敏感且演练任务不产生真实计费。
2.2 数据库(prisma/)
- 迁移
20261104125003_a6_media_tasks(expand-only,未改历史迁移与旧表):media_tasks:任务头(类型 / 状态 / 客户快照 / 计数 / 预估 / 实际 / 取消与失败信息 / 时间);media_items:任务项(seq / 状态 / 失败原因码与文案 / 尝试次数 / 时间;(task_id, seq)唯一);task_events:append-only 事件流(seq BIGSERIAL作为 SSE id;(task_id, seq)索引)。
- 关系:任务项 / 事件
onDelete: Cascade(无删除任务对外入口,级联仅用于测试夹具清理;审计留痕由 append-onlyaudit_logs承担)。
2.3 后端 API(apps/api)
modules/media/:controller / service / repository / mapper / estimator / cursor / idempotency / SSE / 结果审计消费者,共 9 个单测 + 1 个集成测试。infra/queue/:9 队列注册与生命周期、Outbox dispatcher(Redis 锁 +FOR UPDATE SKIP LOCKED+ jobId 幂等 + 退避 / 死信)、/health/queues、/metrics。- 通用改动:
AuditInterceptor支持幂等重放skipAudit(默认行为不变);AppModule接线MediaModule/QueueModule。
2.4 Worker(apps/worker)
config.ts(QUEUE=all|分组|队列名、并发 / workerId)、queues.ts、heartbeat.ts(30s TTL,10s 续写)、redis-options.ts。processors/demo.processor.ts:事务 + 任务行锁、任务项条件认领、取消语义、非末次失败回 queued 交给 BullMQ 重试、末次失败落 failed、原子计数、item/progress/done事件、收敛后写media.task.finished|failedoutbox;纯函数单测。main.ts:按QUEUE建 Worker、心跳、SIGINT/SIGTERM 优雅退出并清理心跳。
2.5 前端(apps/web,方案 A)
- 路由:
/material/tasks、/material/tasks/:id(material:read;写操作按钮按material:render渲染),侧栏「生产任务」更名「任务中心」,详情面包屑补齐。 - 页面:
pages/material/tasks/(TasksPage / TaskDetailPage / TaskFilters / TaskTable / TaskSubmitModal / TaskConfirmModal / task-filter-utils + spec)。 - 可复用组件:
components/tasks/(TaskTypeTag / TaskStatusTag / TaskItemStatusTag / TaskProgressBar / TaskMiniProgress / TaskProgressRing / TaskSseIndicator / TaskBadge / useTaskStream / task-display / task-sse + spec)。 - API / hooks:
api/media.ts(+ spec)、hooks/queries/use-media-tasks.ts、api/{endpoints,query-keys,client,query-client}.ts增量。 - 样式:
styles/tasks.css(基础组件 + 列表)、styles/task-detail.css(详情);全部使用--lx-*token。 - 交互:筛选 URL 双向同步(关键字 260ms 防抖)、游标「加载更多」、行点击进详情、提交演练任务(实时预估 + 复用幂等键 + 提交中禁用)、取消 / 重试二次确认、顶栏角标 + 待处理失败直达、完成 / 失败通知。
2.6 测试与监控
- 单元测试:shared 197、api 257、web 238、worker 27。
- 集成测试:api 92(含 media 12、outbox / 队列端点)。
- 浏览器 e2e:
e2e/a6-tasks.spec.ts6 条(真实 API + worker + PG/Redis + 浏览器,40s 左右)。 - 监控:
infra/monitoring/queue-alerts.yml(积压 > 10min、失败率 > 5%、心跳缺失、outbox 死信)+README.md(抓取地址 / promtool 校验 / 接入步骤)。
3 验证结果
| 门禁 | 命令 | 结果 |
|---|---|---|
| 格式 | pnpm format:check |
✅ |
| Lint | pnpm lint |
✅ 0 error / 0 warning |
| 类型 | pnpm typecheck |
✅ 10/10 tasks |
| 单测(shared) | pnpm --filter @lingxi/shared test |
✅ 18 files / 197 tests |
| 单测(api) | pnpm --filter @lingxi/api test |
✅ 37 files / 257 tests(lines 71%+) |
| 单测(web) | pnpm --filter @lingxi/web test |
✅ 37 files / 238 tests(lines 91.7%) |
| 单测(worker) | pnpm --filter @lingxi/worker test |
✅ 7 files / 27 tests |
| 集成(api) | pnpm --filter @lingxi/api test:int(停 dev 服务后) |
✅ 11 files / 92 tests |
| 构建 | pnpm build |
✅ 8/8 tasks |
| OpenAPI | pnpm openapi:diff |
✅ 无破坏性变更(新增 6 端点) |
| 浏览器 e2e | A6_E2E=1 pnpm exec playwright test e2e/a6-tasks.spec.ts |
✅ 6/6(约 39s,连跑多次稳定) |
e2e 覆盖:
① 提交混合模式 → 列表出现 → 顶栏角标面板 → 详情 SSE 推进到「部分失败」→ 仅重试失败项;
② 提交 20 项后立即取消 → 已取消、未开始项跳过;
③ 同 Idempotency-Key 同 body 返回同一任务、不同 body 40902(API 断言);
④ SSE Last-Event-ID 续推首帧 seq 更大、不重发历史;
⑤ 仅 material:read:不渲染提交 / 取消 / 重试,保留查看与只读提示;
⑥ 无 material:read:访问 /material/tasks → /403。
真实画面证据(真实 API + worker,1440×900 / 1024×900):design/a6-ui-v1/shots/impl/real/(列表、1024、提交弹层、角标面板、详情进行中 / 部分失败 / 待预览、重试确认、只读列表 / 详情)。独立视觉评审 7/10,按评审意见完成两轮修正:失败数进入角标与 1024 首屏、部分失败首屏头条改为「失败 N / M 项已处理」、去除工程化文案与禁用入口、状态色 / 图标统一、行高与表格撑满、重复入口与计数去重。
4 关键口径与实现说明
- 提交 = 202:按工程规范 04 §1/§6,异步任务提交返回
202;幂等重放同为 202 且返回同一taskId。取消 / 重试是同步状态迁移,返回 201。 - 幂等:
Idempotency-Key8–128 可见 ASCII;指纹sha256(actorId + 规范化 body);24h;同 key 同 body 返回首次响应且不重复写审计 / outbox / 任务行(skipAudit),不同 body 40902;并发由(scope,key)唯一约束兜底。 - 取消:仅排队 / 生成中;条件 UPDATE 认领;排队项 → 已跳过,执行中的项完成;无运行项时立即收敛为已取消,有运行项时交 worker 收敛。已成功项不受影响。
- 仅重试失败项:仅「部分失败」;失败项重置为排队、attempts 保留、失败原因清空;任务回到排队,成功项不重复执行。
- SSE:
Authorization头鉴权(前端 fetch 流,不用 EventSource);id=task_events.seq;800ms 落库轮询事件、15s 心跳、单轮 200 条;Last-Event-ID优先于last_event_id;终态补发done后关闭;前端重连 2 次失败后降级 5s 轮询(状态提示可见)。 - 状态机:完整九态与迁移表;M0 演练任务点亮排队 / 生成中 / 部分失败 / 待预览 / 失败 / 已取消;成功终态为「待预览」(B3 接入产物后点亮勾选 / 导出 / 入库)。
- 任务标题:后端生成
{类型} · {客户} · N 项快照;前端列表 / 角标去掉类型前缀(类型已有独立标签),详情标题同理。 - 队列物理名:BullMQ 5 禁止队列名 / jobId 含
:,逻辑队列名(media:verify等,用于指标 / 心跳 /QUEUEenv)与物理名(media-verify)一对一映射,协议与监控口径不变。 - Outbox 退避:
outbox_events无next_attempt_at,失败时把occurred_at重置为失败时刻作为退避起点;canceled事件直接置已投递(worker 以 DB 状态感知),finished|failed永不认领(media 本地消费者写审计)。 - 前端预估:读
/system/configs/effective的task.*(已置 public);缺失时回退 shared 定义默认值做展示兜底,后端仍最终校验与计算。 - 全局角标:数字 = 排队 + 生成中;有待处理失败时显示红点与「待处理失败 N 项,去处理」(直达失败筛选);1024 页头保留「待处理失败」一条关键统计。
- 完整任务号:后端无独立任务编号字段,UI 展示
T-+ ULID 末 6 位,详情可复制完整 ID。
5 与计划的差异与待确认
| 编号 | 项 | 现状 / 决策 |
|---|---|---|
| A6-D-01 | A5 审计导出迁移 | 本轮未迁移(用户确认):A5 AuditExportJob 保持 setImmediate,B3 导出统一接入任务协议时迁移;A5 报告中的下一步顺延到 B3。 |
| A6-D-02 | 队列物理名 | BullMQ 5 禁止冒号 → 物理名连字符、逻辑名保留冒号;已写入代码注释、infra README 与本报告。 |
| A6-D-03 | Outbox 退避 | 复用 occurred_at 作为退避起点,无 schema 变更;代价是首次投递最多 ~1.5s 固有延迟。 |
| A6-D-04 | 状态迁移补充 | 增加 partial_failed → queued(重试)与 running → failed(全部失败直接收敛)两条工程补充,单测覆盖,不改变 §3.6 九态枚举。 |
| A6-D-05 | 重试与历史失败原因 | 任务项重试后清空 failureCode / failureMessage(详情展示最新状态);历史由 append-only task_events / audit_logs 保留。 |
| A6-D-06 | 9 队列范围 | 采用 script:generate / image:matting / image:render / video:keyframe / video:shot / video:compose / delivery:zip / export:package / media:verify;架构中 media:post / embed:index 不在本批 9 队列内(M1/M3 再启用)。 |
| A6-D-07 | 监控基建 | 本机无 Prometheus / Grafana:交付指标端点、健康检查、告警规则文件与接入说明,未接真实 Grafana。 |
| A6-D-08 | 任务中心路由 | 采用工程规范 11 的 /material/tasks(计划 §7.6 /tasks 视为简写);权限复用 material:read / material:render(DP-01,不新增 task:*)。 |
| A6-D-09 | 单任务项重试 | 本批只提供「仅重试失败项」批量入口,不提供单项重试(计划未要求;B3 快筛 / M1 重试可按需追加)。 |
| A6-D-10 | 任务提交演练入口 | M0 真实任务未建设,按用户确认提供「提交演练任务」(material:render);B3 / M1 接入真实任务后下线演练入口。 |
6 已知遗留
- 真实任务类型:图片 / 视频 / 脚本 / 导出 / 校验任务与产物、勾选、导出、入库由 B3 / M1 / M2 接入;当前九态中「已勾选 / 已导出 / 已入库」仅契约保留。
- 审计导出迁移:A5 审计导出仍走
setImmediate,B3 统一迁移到 A6 任务协议(含对象存储与并发限流)。 - 可观测:OTel SDK(新增依赖待评审)未接入;Grafana 告警规则文件未在本机加载验证;任务成功率 / 成本看板在 M2 / M4。
- SSE 扩展性:当前每连接 800ms DB 轮询 + 15s 心跳,100 人规模足够;若任务事件量增大应改为 Redis pub/sub 或 Postgres LISTEN/NOTIFY。
- 任务数据清理:
task_events/media_tasks无归档与保留期策略(后续与 A5 保留策略一并定)。 - 单项重试 / 批量取消:本批未提供(见 A6-D-09)。
- 演练任务成功终态:M0 演练任务成功收敛到「待预览」,不含产物与快筛;接口与 UI 已注明快筛后续开放。
7 下一步
- 按计划进入 A7 AI 配置与调用底座(A6 的 Outbox / 任务协议 / 审计可直接复用)。
- B3 导出接入时:迁移 A5 审计导出、接通对象存储签名 URL、补充导出任务类型与进度。
- M1 接入图片 / 视频真实任务:产物表、快筛勾选、单镜替换、断点续跑在既有任务协议上扩展。
- 运维:Prometheus / Grafana 接入
infra/monitoring/queue-alerts.yml,补齐队列积压与失败率看板。