Files
lingxi/apps/api/src/infra/queue/queue.constants.ts
T
Stev_Wang 633765f10f
ci / 质量门禁 (push) Successful in 6m4s
feat(media): A6 任务中心 API、SSE 进度与队列骨架
背景:A6-BE-01..05 与 A6-INF-01,打通「提交 → 队列 → SSE 进度 → 完成/失败 → 取消 / 仅重试失败项」闭环,为 B3 / M1 的真实任务提供统一协议。
改动:
- API media 模块:POST/GET /media-tasks、详情、SSE(Last-Event-ID 续推 + 15s 心跳 + 800ms 落库轮询)、取消(只影响未开始项)、仅重试失败项;Idempotency-Key 24h(异步提交 202;同 key 同 body 返回同一任务且不重复审计,不同 body 40902);范围外统一 404;
- 队列骨架:BullMQ 5 九队列(5 组独立启停)、Outbox dispatcher(Redis 锁 + FOR UPDATE SKIP LOCKED + jobId 幂等 + 指数退避/死信)、worker 按 QUEUE 启停、demo 演练处理器(事务 + 行锁 + 取消/重试/幂等)、优雅退出与心跳;
- 监控:GET /api/v1/health/queues、GET /api/v1/metrics(Prometheus 0.0.4)、infra/monitoring 告警规则与接入说明;
- 通用:AuditInterceptor 支持幂等重放 skipAudit(默认行为不变)、AppModule 接线、.env.example 的 QUEUE 语义、bullmq 5.81.5 / worker @prisma/client / worker 覆盖率依赖。
影响面:OpenAPI 新增 6 个端点(openapi:diff 无破坏性变更);API 进程新增 outbox dispatcher 与 metrics/health 路由;worker 新增 BullMQ 消费;未改旧审计导出链路与既有接口。
回滚:回滚提交并停止 worker 新队列消费;task/outbox 新表数据不影响旧功能,可按需保留。
验证:api 单测 257、集成 92(连跑 2 次)、worker 27、shared 197;typecheck / lint / format:check / build / openapi:diff 全绿;本地真实 worker 启用 9 队列心跳,/health/queues、/metrics 实测,/media-tasks 未登录 401。
2026-11-04 21:52:35 +08:00

41 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 队列 / Outbox 投递协议常量(技术架构 §2.4 / §3.7,A6-BE-02 / A6-BE-05 / A6-INF-01)。
*
* key 统一 `lingxi:` 前缀;心跳 key 与 worker 侧(apps/worker/src/heartbeat.ts)保持一致,
* 跨 app 契约变更时必须两端同步。
*/
/** Outbox 投递分布式锁:同一时刻只允许一个 dispatcher 抢到本轮投递权 */
export const OUTBOX_DISPATCH_LOCK_KEY = 'lingxi:lock:outbox-dispatch';
/** 锁 TTL:既覆盖单轮投递耗时,又保证实例崩溃后 5s 内可被其他实例接管 */
export const OUTBOX_DISPATCH_LOCK_TTL_MS = 5_000;
/** 单轮最多认领的事件数(避免一轮占满连接 / 内存) */
export const OUTBOX_DISPATCH_BATCH_SIZE = 20;
/** 死信阈值:投递尝试达到该次数后标记 dead_lettered,避免单条事件永久阻塞 outbox 表 */
export const OUTBOX_MAX_ATTEMPTS = 10;
/** 默认调度间隔(可被环境变量 OUTBOX_DISPATCH_INTERVAL_MS 覆盖) */
export const OUTBOX_DISPATCH_DEFAULT_INTERVAL_MS = 1_000;
/** 死信标记:写入 outbox_events.last_error,便于监控与人工排查 */
export const OUTBOX_DEAD_LETTER_MARKER = 'dead_lettered';
/** worker 心跳 key 前缀:`lingxi:worker:heartbeat:<queueName>` */
export const WORKER_HEARTBEAT_KEY_PREFIX = 'lingxi:worker:heartbeat:';
/** worker 心跳 TTL(秒);worker 每 10s 续写一次,超过 TTL 视为心跳缺失 */
export const WORKER_HEARTBEAT_TTL_SECONDS = 30;
/** 心跳年龄超过 TTL 的倍数视为陈旧(用于健康检查降级判定) */
export const WORKER_HEARTBEAT_STALE_MULTIPLIER = 2;
/** 单轮投递 / 单次队列命令的超时,避免 Redis 异常时无限挂起 */
export const QUEUE_OPERATION_TIMEOUT_MS = 5_000;
/** Lua:仅当锁 value 与 token 相等时释放,避免误删其他实例持有的锁 */
export const RELEASE_LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";