Files
lislgosms/docs/downstream-requeue-task-design-20260812.md
T

21 KiB
Raw Blame History

下游投递后台批量重投任务设计与实现符合性审计

版本:V1.1
需求确认日期:2026-08-12
文档整理日期:2026-08-13;业务口径更新:2026-08-14
适用页面:运营端 → 下游投递记录
审计基线:当前工作区 HEAD=67fee216162e638ba21004fcf87e7711facefb91;本功能相关文件相对 HEAD 无未提交修改
本文目的:还原 2026-08-12 已确认的设计口径,并将当前实现逐条映射到设计,不能以“已有代码”代替“符合设计”的结论。

1. 背景与目标

下游投递记录原有两种人工操作:单条重投、勾选当前页后批量重投。它们适合少量记录,但不适合事故期间处理跨页、跨应用的大批量积压。

新增“按当前筛选条件创建后台重投任务”的目标是:

  1. 后端按当前真实筛选条件确定范围,不受页面分页影响;
  2. 创建时冻结任务快照,避免执行期间不断卷入新记录;
  3. 后台限速执行,客户离线或 ACK 异常时不得形成重投洪峰;
  4. 每条任务项可追踪、可恢复、可审计,成功确认后不得重复投递;
  5. 运营人员能够预检、创建、暂停、继续、终止和查看完整结果。

本功能不替代单条重投和当前页勾选重投,三种入口应同时保留。

2. 已确认的第一版业务口径

以下七条是 2026-08-12 已写入正式需求文档和测试用例的确认口径:

  1. 保留单条重投、当前页勾选批量重投,新增“按筛选条件重投”;投递记录分页支持每页 10/25/50 条。
  2. 后台任务使用企业、应用、投递类型、状态、创建日期、关键词组成的筛选快照;页码和每页条数不属于任务范围。预检生成 snapshotAt,创建任务后产生的新记录不进入该任务。
  3. 后台任务允许 pendingfailedunconfirmedrejecteddelivered。客户端已确认的 delivered 可按筛选快照再次投递,但必须醒目提示可能造成客户端重复处理;处于 awaiting_ack 的记录不得并发重投。创建前展示真实命中数、可重投数、规则跳过数、状态分布,任务原因必填。
  4. 任务按企业应用分批执行,默认每个应用 10 条/秒。单条失败不阻断整批;连续失败达到 10 条,或 ACK 超时/拒绝达到安全阈值时,自动暂停。客户离线、已有链路等待 ACK 属于“等待”,不能记作“跳过”。
  5. “跳过”严格表示本任务没有调用 Gateway。跳过原因包括:执行前状态变化、创建任务后才被客户确认、已被其他任务处理、本任务已成功处理、不属于任务快照、应用或投递能力已停用、投递数据不完整、缺少原 Submit 映射。只有任务项冻结的原状态已是 delivered 时,才允许按已确认记录重投;其他状态在执行前收到迟到成功 ACK 时必须跳过。
  6. 任务支持列表、详情、暂停、继续、终止。终止只影响尚未发送的任务项。任务项以 taskId + deliveryId 幂等;执行前原子认领并复核当前状态;API 重启后继续执行;已获得成功 ACK 的任务项不得再次发送。
  7. 创建、暂停、继续、终止、自动暂停都必须写操作日志。任务必须使用真实 PostgreSQL、Gateway 和客户 ACK,不得使用 mock、静态数据或 localStorage。

3. 用户交互设计

3.1 投递记录筛选区

筛选条件应包含:

条件 说明
企业 全部企业或指定企业
企业应用 受企业条件联动;全部应用或指定应用
投递类型 全部、状态回执、上行短信
状态 全部或单一状态
创建日期 开始日期、结束日期,按北京时间自然日
关键词 消息 ID、客户账号、手机号、最后错误、企业名称、应用名称

“按筛选条件重投”使用上表条件,但明确排除页码、每页条数和当前页勾选状态。

3.2 创建任务流程

  1. 用户设置筛选条件,点击“按筛选条件重投”。
  2. 后端在同一时点生成预检快照,返回:筛选条件、snapshotAt、筛选命中数、可重投数、规则跳过数、状态分布、涉及应用数、最早记录时间。
  3. 弹窗明确告知允许 pending/failed/unconfirmed/rejected/delivered、禁止 awaiting_ack,并提示已确认记录再次投递可能造成客户端重复处理。
  4. 用户选择执行速度,填写不少于 5 个字的事故原因、工单号或处理说明。
  5. 用户确认后,后端必须重新按预检的筛选快照和 snapshotAt 物化任务项,而不是使用前端传入的记录 ID 列表。
  6. 创建成功后关闭弹窗,任务出现在任务列表,状态为“排队中”。

预检结果必须严格遵守当前筛选条件。例如当前状态选择“待投递”,命中数、可重投数和状态分布都只能基于“待投递”记录,不能擅自扩展为全部状态。

3.3 任务列表

任务列表至少展示:任务号、创建时间、企业/应用范围、任务原因、中文状态、总数、成功数、失败数、跳过数、等待数、创建人。

列表应支持分页和状态筛选,保证历史任务可查询,不能只展示固定数量的最近任务。

可执行操作:

当前状态 可执行操作
排队中、执行中 查看详情、暂停、终止
已暂停 查看详情、继续、终止
已完成、部分完成、已终止 查看详情

3.4 任务详情

任务详情分为三部分:

  1. 任务信息:筛选快照、快照时间、原因、创建人、速度、安全阈值、开始/暂停/完成时间;
  2. 结果汇总:排队、处理中、等待连接、等待 ACK、成功、失败、跳过、未处理;
  3. 完整任务项:支持分页及按结果状态、消息 ID、错误/跳过原因查询。

任务项必须使用中文状态和明确原因,不能只返回内部英文枚举,也不能只展示最近 50 项而使其余项目不可查询。

4. 后端数据设计

4.1 任务主表

DownstreamRequeueTask 保存:

  • 任务号、企业范围、应用范围;
  • 完整筛选快照及 snapshotAt
  • 原因、执行速度、失败安全阈值;
  • 任务状态和各结果计数;
  • 创建人、开始时间、暂停时间、完成时间、最近错误;
  • 创建时间、更新时间。

4.2 任务项表

DownstreamRequeueTaskItem 保存:

  • taskIddeliveryIdapplicationId
  • 创建任务时的原状态;
  • 当前任务项状态;
  • 跳过原因、失败信息;
  • 认领时间、完成时间、创建时间、更新时间。

数据库必须有 taskId + deliveryId 唯一约束。任务项不能级联删除原下游投递记录,历史下游投递和 ACK 证据必须保留。

5. 状态机设计

5.1 任务状态

状态 含义 进入条件
queued 排队中 创建成功或人工继续
running 执行中 扫描器开始处理
paused 已暂停 人工暂停或触发安全阈值
completed 已完成 所有任务项完成且没有失败
partial_completed 部分完成 所有任务项结束但存在失败
terminated 已终止 人工终止;未认领项不再执行

5.2 任务项状态

状态 是否调用过 Gateway 说明
queued 等待认领
processing 尚不确定 已原子认领,正在复核并准备调用
waiting_connection 否或尚未写出 客户离线,等待可用连接,不算失败、不算跳过
waiting_external_ack 其他链路已写出,等待其 ACK 结论
waiting_ack 本任务已写出,等待客户 ACK
success 客户返回可关联原消息的成功 ACK
failed 是或调用失败 Gateway 调用失败、ACK 超时或 ACK 拒绝
skipped 复核后确认本任务不应调用 Gateway
unprocessed 任务终止时尚未开始

“写出成功”不能直接记为 success,只有客户有效 ACK 才是成功。

6. 执行与并发控制

  1. 扫描器只处理 queued/running 任务。
  2. 多实例扫描时必须先原子认领任务项;同一任务项只能有一个执行者。
  3. 执行前重新读取下游投递及应用状态,再判断等待、跳过或调用 Gateway。
  4. 限速维度是“企业应用”,不是任务总量。一个跨 3 个应用的任务配置 10 条/秒时,每个应用各自最多 10 条/秒,并且各应用互不阻塞。
  5. 限速必须使用时间窗口或分布式令牌,不能依赖“定时器大约每秒执行一次 + 每轮取 N 条”,否则扫描重叠或执行耗时变化会突破或降低速率。
  6. 客户离线时任务项进入等待连接;客户恢复后继续,不应消耗连续失败阈值。
  7. 本任务写出后进入 waiting_ack;成功 ACK 转 success;ACK 超时、拒绝、无效 Msg_Id 转 failed 并计入安全阈值。
  8. 连续失败只能在真正成功 ACK 后清零,不能在“写出并开始等待 ACK”时提前清零。
  9. processing 必须有租约/超时恢复。API 进程中断后,超过认领租约的项目恢复为 queued 并重新复核,才能满足重启续跑。
  10. 暂停或终止与执行器并发时,执行器在认领下一项和调用 Gateway 前都必须复核任务状态。

7. 跳过、等待和失败判定

7.1 跳过

跳过意味着本任务没有调用 Gateway:

原因 判定
执行前状态已变化 已不属于允许重投状态,且不是等待 ACK
创建任务后才被客户确认 任务项冻结原状态不是 delivered,执行前收到有效成功 ACK;避免把迟到确认变成未授权重复投递
已被其他任务处理 其他任务已认领、等待 ACK 或成功
本任务已成功处理 同任务项已有成功结果,重复扫描不得再调用
不属于任务快照 创建时间晚于 snapshotAt 或不再满足冻结范围
应用或投递能力已停用 应用状态或接口能力不允许投递
投递数据不完整 缺少可重放 payload 或投递类型非法
缺少原 Submit 映射 无法形成可关联原短信的安全回执

7.2 等待

  • 客户离线、没有可用连接:waiting_connection
  • 其他链路已经写出且仍在 ACK 窗口:waiting_external_ack
  • 本任务已经写出:waiting_ack

等待项不计入跳过数或失败数。等待结束后根据真实状态继续、成功或失败。

7.3 失败

  • 调用 Gateway 发生非等待型错误;
  • Gateway 明确拒绝或无法安全投递;
  • 本任务写出后 ACK 超时;
  • 客户 ACK 非零;
  • 客户 ACK 无法关联原消息。

失败原因必须保留原始错误,同时归一为可统计的失败类别。

8. 安全阈值与自动暂停

默认安全策略:

  • 每应用默认 10 条/秒;
  • 连续失败阈值默认 10 条;
  • ACK 超时和 ACK 拒绝纳入失败阈值;
  • 达到阈值时,在认领下一条之前将任务原子改为 paused
  • 写操作日志,记录任务号、失败类别、连续失败数、阈值和暂停时间;
  • 页面展示明确的自动暂停原因;
  • 人工继续后从待处理/等待项继续,不重放已成功项。

如果多个应用同时执行,连续失败计数至少应按应用隔离,避免一个客户应用故障暂停其他正常应用;第一版若选择整任务暂停,也必须在设计和页面中明确,且仍要保存触发暂停的应用。

9. API 设计

方法 路径 用途
POST /admin/operations/downstream-requeue-tasks/preview 按当前筛选条件生成预检快照
POST /admin/operations/downstream-requeue-tasks 使用预检快照创建任务
GET /admin/operations/downstream-requeue-tasks 分页查询任务列表,支持状态筛选
GET /admin/operations/downstream-requeue-tasks/:id 查询任务汇总
GET /admin/operations/downstream-requeue-tasks/:id/items 分页查询完整任务项及原因
POST /admin/operations/downstream-requeue-tasks/:id/pause 暂停
POST /admin/operations/downstream-requeue-tasks/:id/resume 继续
POST /admin/operations/downstream-requeue-tasks/:id/terminate 终止

创建接口不得仅信任前端回传的筛选条件和时间。建议预检生成短期有效、服务端签名的 previewToken,绑定筛选条件、snapshotAt 和操作人;创建时校验令牌,防止绕过页面篡改范围。

10. 验收用例

编号 验收点 预期
DRQ-001 筛选快照 企业、应用、类型、状态、日期、关键词均生效;分页无关;快照后新记录不进入
DRQ-002 预检口径 命中、可重投、跳过和状态分布严格基于当前筛选条件
DRQ-003 状态白名单 物化 pending/failed/unconfirmed/rejected/delivered;拒绝 awaiting_ack;仅冻结原状态为 delivered 的任务项可按已确认记录重投
DRQ-004 每应用限速 多应用任务中每个应用独立达到配置速度,任意扫描重叠都不超速
DRQ-005 客户离线 进入等待连接,不记失败或跳过,恢复连接后继续
DRQ-006 ACK 闭环 写出只进入等待;有效 ACK 成功;超时、拒绝、无效 Msg_Id 失败
DRQ-007 自动暂停 立即失败与 ACK 失败均计入阈值;达到阈值自动暂停并审计
DRQ-008 并发幂等 多实例同时扫描,同一投递最多调用一次 Gateway
DRQ-009 重启恢复 processing、等待连接、等待 ACK 三种阶段重启 API,任务均可继续且不重复成功项
DRQ-010 人工控制 暂停、继续、终止与执行并发时状态正确;终止不撤回已写出项
DRQ-011 完整可查 任务列表和任务项均可分页查询全部历史数据,不限最近 10/50 条
DRQ-012 操作审计 创建、暂停、继续、终止、自动暂停均有操作人/触发源和完整上下文

11. 当前实现符合性审计

当前实现的主要文件:

  • api/src/send-chain/send-downstream-requeue-task.service.ts
  • api/src/send-chain/send-downstream-state.service.ts
  • api/src/operations/admin-operations.controller.ts
  • api/prisma/schema.prisma
  • src/apps/admin/AdminDownstreamDeliveriesPage.tsx
  • api/src/send-chain/send-downstream-requeue-task.service.spec.ts

11.1 已符合或基本符合

设计项 结论 当前证据
真实持久化 符合 已有任务表、任务项表和 migration,不使用前端本地状态代替任务
快照时间上限 基本符合 创建时按 snapshotAt 限制 createdAt,快照后记录不物化
后台状态白名单 符合 REPLAYABLE_STATUSESpending/failed/unconfirmed/rejected/delivered
禁止后台任务处理等待 ACK 筛选 符合 创建接口明确拒绝 awaiting_ackdelivered 按已确认重复投递风险口径放行
原因校验 符合 少于 5 个字拒绝
任务项幂等 符合 数据库唯一约束 taskId + deliveryId
执行前任务项认领 基本符合 通过 status=queued 的条件更新认领为 processing
执行前复核 基本符合 重新查询投递、应用、payload 和其他任务状态
成功 ACK 不再误发送 基本符合 delivered 快照项执行前才收到成功 ACK 时跳过;其他任务 success 也会阻止调用;冻结原状态为 delivered 的项目属于运营明确授权的再次投递
人工控制 基本符合 已有暂停、继续、终止接口和页面按钮
核心操作日志 基本符合 创建、暂停、继续、终止、自动暂停均写日志
投递记录分页 符合 页面支持每页 10/25/50 条并回到第一页

11.2 明确偏差与缺口

优先级 偏差 当前实现 与设计冲突及风险
P0 API 重启后 processing 项无法恢复 只扫描 queued,没有 processing 认领租约或超时回收 进程在认领后中断会使任务项永久卡住,任务永久 running,不满足重启续跑
P0 ACK 失败不参与自动暂停阈值 reconcileWaiting把 ACK 超时/拒绝改为 failed,但不增加consecutiveFailures;进入 waiting_ack 时反而立即清零 客户持续拒绝或 ACK 超时不会触发安全暂停,可能持续向故障客户重投
P0 限速不是按应用,且可能被并发扫描突破 每个任务每轮最多取 min(10, ratePerSecond);没有按applicationId分组,也没有分布式速率令牌;定时器可能重叠执行 不符合“每应用10条/秒”;配置20实际单轮最多10,多扫描器/多实例又可能超过配置
P0 客户离线被记为任务项失败 Gateway未写出时,底层通常返回pending/failed,任务层将非awaiting_ack/delivered结果直接记为failed 不符合“客户离线属于等待”;会错误增加失败数并可能自动暂停
P1 预检不严格遵守当前状态筛选 计算matchedCount/statusCounts时强制把状态改成all 用户选择“待投递”时,命中数和状态分布仍可能包含其他状态;预检范围表达失真
P1 页面缺少企业筛选 后端类型支持tenantId,但页面只有应用筛选,currentTaskFilter不传企业 未完整实现已确认的“企业 + 应用”筛选范围
P1 任务列表不可完整查询 页面固定请求第1页、每页10条,没有任务分页和状态筛选 第11个以后历史任务在页面不可达,不满足任务列表要求
P1 任务详情不可完整查询 详情接口固定返回最近50个任务项,没有任务项分页接口 大任务无法核对全部失败、跳过和等待项,难以验收和审计
P1 扫描器缺少任务级/应用级分布式锁 setInterval直接调用扫描,runScan可被重叠触发,多实例也会同时处理同一任务 虽有任务项条件认领可降低单项重复,但无法保证整体限速和连续失败统计一致
P1 连续失败清零时点错误 Gateway写出进入waiting_ack即把连续失败清零 写出不是业务成功;应等有效 ACK 后再清零
P1 预检与创建没有不可篡改绑定 创建接口信任前端回传的filter + snapshotAt,没有预检令牌或服务端预检记录 可绕过页面修改筛选范围;虽仍受状态白名单和10万条上限保护,但不等于复用原预检结果
P1 自动化覆盖远低于设计风险 专项仅4个测试:预检计数、参数拒绝、活动任务冲突、已确认跳过 未覆盖限速、多应用、离线等待、ACK阈值、重启恢复、并发扫描、完整分页及所有审计动作
P2 状态和详情表达偏内部化 任务列表/详情直接展示英文状态;任务详情只展示汇总和最近项 运营人员不易区分等待连接、等待外部ACK、任务写出等待ACK等状态
P2 终止后的未处理数未进入常规汇总 终止把queued改为unprocessed,但任务汇总只保存成功/失败/跳过/等待 任务进度分子可能小于总数,页面没有单独解释未处理数量
P2 外部 ACK 跳过原因存在口径混用 waiting_external_ack最终由其他链路成功后记为“跳过:已由其他投递链路完成” 可以接受为“本任务未调用Gateway”,但详情必须明确这是外部链路成功,不应让用户误以为业务未处理;这与冻结原状态为 delivered 的主动再次投递是两种情形

11.3 综合结论

当前实现完成了数据库模型、基本预检、任务创建、任务项认领、Gateway调用、ACK结果回看和人工控制的骨架,但不能判定为严格按照 2026-08-12 方案完成。

尤其以下四项属于设计中的安全核心,当前存在实质缺口:

  1. processing 任务项缺少重启恢复;
  2. ACK 超时/拒绝未进入自动暂停阈值;
  3. 每应用限速没有实现,且扫描重叠可能突破限速;
  4. 客户离线没有作为等待状态处理。

在上述 P0 修复并通过真实 PostgreSQL、Gateway、客户 ACK 链路测试前,不应把该后台任务认定为完整满足事故批量恢复方案。

12. 建议整改顺序

  1. 先补 processing 租约恢复、扫描分布式锁和每应用速率令牌;
  2. 重构任务项等待/失败状态,让离线、外部 ACK、本任务 ACK 三类等待分开;
  3. 将 ACK 超时、拒绝、无效 ACK 纳入按应用安全阈值,并把清零时点改为有效 ACK;
  4. 修正预检状态口径,增加企业筛选,并用服务端令牌绑定预检与创建;
  5. 增加任务列表和任务项分页、中文状态及完整原因查询;
  6. 补齐 DRQ-001DRQ-012 自动化,并使用真实 PostgreSQL、Redis、Gateway 与本地客户连接完成专项验收。

整改应作为独立需求进行,不在未经授权的情况下直接修改或发布现有批量重投逻辑。