test: close remaining quality verification gaps
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# SendChain Service 职责拆分专项方案
|
||||
|
||||
- 方案日期:2026-08-28
|
||||
- 对应整改项:`C8 SendChain Service 职责拆分`
|
||||
- 当前范围:只制定方案,不在本轮实施发送链重构
|
||||
- 适用环境:本地和测试环境 `100.93.204.60`
|
||||
- 禁止范围:未经新授权不得部署预生产;不得用生产短信验证重构
|
||||
|
||||
## 1. 当前状态与问题
|
||||
|
||||
当前发送链已经存在一层门面 `send-submission.service.ts`,但两个实现类仍然过大:
|
||||
|
||||
| 文件 | 当前规模 | 集中职责 |
|
||||
|---|---:|---|
|
||||
| `send-inbound-entry.service.ts` | 约1852行、84.2 KiB | CMPP认证、入站持久化、长短信、Inbox claim、企业微批、配额、模板/引流匹配、风控、指标 |
|
||||
| `send-gateway-submit.service.ts` | 约1159行、52.2 KiB | BullMQ Worker、批处理、路由、签名报备、限速、Submit命令、Outbox、任务进度、资源回收 |
|
||||
| `send-submission.service.ts` | 约280行 | 对外门面、跨服务回调与编排 |
|
||||
|
||||
主要风险不是文件长度本身,而是以下行为边界在同一个类中耦合:
|
||||
|
||||
- PostgreSQL事务与 advisory lock 顺序。
|
||||
- Inbox claim、租约、重试与恢复。
|
||||
- 长短信分片幂等和完整消息恢复。
|
||||
- 计费冻结、扣费、释放与失败补偿。
|
||||
- 路由、签名报备、通道限速和主备策略。
|
||||
- Submit Outbox 的租约、发布、确认和重复消费。
|
||||
- 消息状态与批量任务进度的单飞/尾随刷新。
|
||||
|
||||
因此专项必须以“行为锁定后机械提取”为主,不允许借拆分类的机会同时重写SQL、事务或状态机。
|
||||
|
||||
## 2. 拆分目标
|
||||
|
||||
1. `SendSubmissionService` 只保留稳定门面和跨子域编排。
|
||||
2. 每个子服务只有一个主要变化原因,建议控制在500行以内;复杂持久化服务可放宽至700行,但不得继续混合Worker生命周期、业务校验和发布协议。
|
||||
3. 保持现有公开方法签名、队列名、Stream字段、幂等键、数据库状态和错误码不变。
|
||||
4. 拆分过程中不改变吞吐参数、路由权重、计费口径和定时器周期。
|
||||
5. 每一步均可独立回退,不要求一次性完成全部拆分。
|
||||
|
||||
## 3. 目标结构
|
||||
|
||||
建议新增以下内部服务,目录仍保留在 `api/src/send-chain/`,暂不建立更深目录,避免一次性路径迁移过大。
|
||||
|
||||
### 3.1 入站侧
|
||||
|
||||
| 目标服务 | 从现有文件提取的职责 | 明确不负责 |
|
||||
|---|---|---|
|
||||
| `inbound-authentication.service.ts` | `authenticateInboundApplication`、连接认证结果记录、IP白名单和接口状态检查 | 消息持久化、计费 |
|
||||
| `inbound-long-message.service.ts` | 分片收集、完成判定、超时清理、已完成结果恢复 | 普通单条业务创建 |
|
||||
| `inbound-inbox-repository.service.ts` | Inbox持久化、请求哈希、claim、租约续期/完成/失败 | 风控和模板匹配 |
|
||||
| `inbound-workflow-worker.service.ts` | pump、企业公平分组、微批调度、停止与指标刷新 | SQL细节、消息业务规则 |
|
||||
| `inbound-message-preparation.service.ts` | 应用快照、模板/签名/引流匹配、号码频控与风控输入组装 | 事务写入和队列发布 |
|
||||
| `inbound-message-persistence.service.ts` | 单条/微批消息、任务、记录、配额预留和幂等持久化 | Worker生命周期、Gateway Submit |
|
||||
|
||||
### 3.2 出站侧
|
||||
|
||||
| 目标服务 | 从现有文件提取的职责 | 明确不负责 |
|
||||
|---|---|---|
|
||||
| `send-worker-lifecycle.service.ts` | BullMQ Worker启动/停止、并发槽位、微批flush、队列指标 | 路由、计费和命令构造 |
|
||||
| `message-send-processor.service.ts` | 单条/批量消息处理、状态迁移、失败补偿编排 | Worker定时器和Outbox发布器生命周期 |
|
||||
| `channel-routing.service.ts` | 运营商/省份识别、应用路由、候选通道、签名报备检查、主备选择 | 发布命令和数据库Outbox |
|
||||
| `channel-rate-limit.service.ts` | 通道TPS等待和相关低基数指标 | 路由选择 |
|
||||
| `gateway-submit-command.service.ts` | 命令构造、开放会话ID复用、直接Stream发布契约 | Outbox claim和业务状态变更 |
|
||||
| `submit-outbox-publisher.service.ts` | Outbox claim、租约、批量发布、确认、重试和恢复 | 消息计费和路由 |
|
||||
| `task-progress-refresher.service.ts` | 真实总量/状态聚合、单飞和尾随刷新 | Submit或回执业务处理 |
|
||||
|
||||
### 3.3 共享边界
|
||||
|
||||
- `send-chain.contracts.ts`:只保留稳定输入输出契约,不放实现函数。
|
||||
- `send-chain.helpers.ts`:只保留无副作用纯函数;需要数据库、Redis、时间器或日志的逻辑必须进入Service。
|
||||
- 计费继续通过现有 `BillingService`,不得复制金额算法。
|
||||
- 所有数据库访问保持现有 Prisma 条件和事务客户端传递方式。
|
||||
- 所有跨服务调用通过构造注入,不通过全局单例或循环引用。
|
||||
|
||||
## 4. 拆分前行为锁定
|
||||
|
||||
在移动代码前补齐特征测试,测试的是当前语义,不是期望中的新设计。
|
||||
|
||||
### 4.1 入站测试
|
||||
|
||||
- 认证成功、未知账号、禁用应用、未认证企业、IP不在白名单。
|
||||
- 同一请求键并发两次只产生一份任务/消息/计费预留。
|
||||
- 请求键相同但payload不同返回冲突。
|
||||
- 长短信乱序、重复分片、缺片、超时、重启恢复。
|
||||
- Inbox claim不重复、租约超时可恢复、失败次数和最终状态一致。
|
||||
- 企业微批公平性、单企业失败不阻塞其他企业。
|
||||
- 模板、签名、引流匹配和人工审核关联不改变。
|
||||
- 配额预留不足时不产生半成品消息或费用。
|
||||
|
||||
### 4.2 出站测试
|
||||
|
||||
- 普通批量与CMPP内部任务入队语义保持不同且正确。
|
||||
- Worker微批开启/关闭结果一致。
|
||||
- 路由优先级、weighted分流、主备切换和运营商过滤不变。
|
||||
- 签名未报备时的拒绝状态和费用释放一致。
|
||||
- 通道TPS限制不会跨通道串扰。
|
||||
- Submit命令字段、Sequence_Id、Msg_Id、Src_Id和幂等键保持一致。
|
||||
- Outbox重复claim、租约恢复、发布成功/失败、Stream重复消费结果一致。
|
||||
- 计费冻结、扣费、退款和释放只发生一次。
|
||||
- 任务进度刷新保持单飞,并在运行中出现新状态时执行一次尾随刷新。
|
||||
|
||||
### 4.3 契约快照
|
||||
|
||||
拆分前固定以下非敏感契约样本:
|
||||
|
||||
- `gateway.submit.commands`
|
||||
- `gateway.submit.results`
|
||||
- `gateway.protocol.logs`
|
||||
- BullMQ `send-message` job data
|
||||
- Submit Outbox payload
|
||||
|
||||
契约测试只固定字段、类型、必填关系和幂等语义,不固定时间戳、随机ID或日志文本。
|
||||
|
||||
## 5. 分阶段实施顺序
|
||||
|
||||
### 阶段 S0:基线冻结
|
||||
|
||||
- 记录Git commit、两个文件行数/哈希、95项migration状态。
|
||||
- 运行现有SendChain专项、API全量、Gateway全量、队列契约和性能基线。
|
||||
- 在隔离数据库和Redis记录测试前快照。
|
||||
|
||||
出口:所有既有测试通过,所有临时数据带独立前缀且可清理。
|
||||
|
||||
### 阶段 S1:提取纯路由与命令构造
|
||||
|
||||
先提取 `channel-routing.service.ts` 和 `gateway-submit-command.service.ts`。这两部分事务耦合相对较低,最适合验证构造注入和门面调用方式。
|
||||
|
||||
规则:只移动代码;输入输出、查询条件、排序和错误文本保持不变。
|
||||
|
||||
### 阶段 S2:提取任务进度刷新与限速
|
||||
|
||||
提取 `task-progress-refresher.service.ts` 和 `channel-rate-limit.service.ts`,保留单飞Map、dirty尾随集合及Redis限速键格式。
|
||||
|
||||
出口:并发刷新和跨通道限速特征测试通过。
|
||||
|
||||
### 阶段 S3:提取Submit Outbox
|
||||
|
||||
提取 `submit-outbox-publisher.service.ts`,将定时器生命周期和claim/publish/ack逻辑封装在同一服务;不得改变租约所有者、批量大小、轮询周期或重试条件。
|
||||
|
||||
出口:故障注入后无丢失、无双发,Stream最终 `pending=0/lag=0`。
|
||||
|
||||
### 阶段 S4:提取Worker生命周期和消息处理
|
||||
|
||||
将BullMQ启动/停止/微批flush移入 `send-worker-lifecycle.service.ts`,业务处理移入 `message-send-processor.service.ts`。
|
||||
|
||||
出口:Worker关闭会等待当前flush;并发槽位与处理指标不变;吞吐基线不下降超过5%。
|
||||
|
||||
### 阶段 S5:提取入站认证和长短信
|
||||
|
||||
先移动认证,再移动长短信。两者均有明确入口和状态表,避免直接触碰Inbox微批主流程。
|
||||
|
||||
出口:认证审计字段、长短信幂等和超时恢复全部一致。
|
||||
|
||||
### 阶段 S6:提取Inbox Repository和Worker
|
||||
|
||||
先封装原SQL与事务为Repository,再将pump/企业分组/claim循环移入Worker。Repository方法必须接受现有事务客户端,不允许内部偷偷开启嵌套事务。
|
||||
|
||||
出口:claim锁顺序、租约、重试、企业公平性和停机等待不变。
|
||||
|
||||
### 阶段 S7:提取消息准备与持久化
|
||||
|
||||
这是入站侧风险最高的阶段,最后执行。先把纯准备结果定义为显式结构,再机械移动持久化;计费操作继续调用现有BillingService。
|
||||
|
||||
出口:消息、任务、计费、审核任务和Inbox状态在成功与每个失败点均与基线一致。
|
||||
|
||||
### 阶段 S8:清理门面
|
||||
|
||||
删除仅为旧类内部跳转存在的回调,`SendSubmissionService`仅保留公开API编排。清理必须单独提交,不能与任何业务优化合并。
|
||||
|
||||
## 6. 每阶段验证矩阵
|
||||
|
||||
| 层级 | 必须执行 |
|
||||
|---|---|
|
||||
| 静态 | API TypeScript、ESLint、Prettier、结构门禁、`git diff --check` |
|
||||
| 单元 | 新子服务定向测试、SendChain既有全套特征测试 |
|
||||
| API | 全量Jest及全源/增量覆盖率 |
|
||||
| PostgreSQL | Prisma validate、95项migration、真实事务/锁/幂等契约 |
|
||||
| Redis | BullMQ、限速键、Stream、Outbox幂等和最终pending/lag |
|
||||
| Gateway | `go test ./... -count=1`、`go vet ./...`、五份队列契约 |
|
||||
| 故障注入 | Redis短暂不可用、Gateway响应超时、DB事务失败、Worker中断恢复 |
|
||||
| 性能 | 与S0同数据和同参数,入口延迟、完整Submit吞吐、DB池等待、队列积压 |
|
||||
|
||||
任何阶段出现以下情况立即停止并回退该阶段:
|
||||
|
||||
- 重复计费、重复Submit或消息丢失。
|
||||
- 状态机出现无法收敛的新状态。
|
||||
- advisory lock顺序或事务边界变化。
|
||||
- 相同负载完整Submit吞吐下降超过5%,且无法由环境波动解释。
|
||||
- Stream pending/lag、Inbox processing或Outbox leased无法清零。
|
||||
|
||||
## 7. 提交策略
|
||||
|
||||
每个阶段至少拆成两个提交:
|
||||
|
||||
1. `test(send-chain): lock <responsibility> behavior`
|
||||
2. `refactor(send-chain): extract <responsibility> service`
|
||||
|
||||
禁止在重构提交中混入:
|
||||
|
||||
- SQL优化或索引变更。
|
||||
- 状态、错误码、队列字段变更。
|
||||
- TPS、批量大小、超时或重试参数调整。
|
||||
- 依赖大版本升级和全仓格式化。
|
||||
|
||||
## 8. 测试环境发布与回退
|
||||
|
||||
每个可部署阶段发布前都必须重新建立并校验:PostgreSQL custom dump、运行目录、环境/systemd/Nginx、Redis RDB与Stream状态、MinIO清单、部署commit和migration状态。
|
||||
|
||||
建议S1~S3合并为第一个测试版本,S4为第二个,S5~S6为第三个,S7~S8为最终版本。每个版本均使用独立恢复点,禁止复用旧备份冒充当前基线。
|
||||
|
||||
回退只允许回退本阶段部署包与对应恢复资产;如已发生业务写入,应优先代码前滚修复,不得盲目恢复数据库覆盖测试期间其他数据。
|
||||
|
||||
## 9. 工作量与决策点
|
||||
|
||||
| 阶段 | 预计工作量 | 风险 |
|
||||
|---|---:|---:|
|
||||
| S0 行为锁定 | 2~3人日 | 中 |
|
||||
| S1~S3 出站低/中耦合拆分 | 2~4人日 | 中 |
|
||||
| S4 Worker拆分 | 1~2人日 | 中高 |
|
||||
| S5~S6 入站认证/Inbox拆分 | 2~4人日 | 高 |
|
||||
| S7~S8 持久化与门面收口 | 2~4人日 | 高 |
|
||||
|
||||
总计建议按9~17人日安排,并分至少4个测试版本。开始实施前需要用户再次明确授权C8专项和测试环境发布窗口。
|
||||
|
||||
## 10. 最终关闭标准
|
||||
|
||||
- 两个超大实现类被删除或只保留不超过300行的兼容转发层。
|
||||
- 每个目标服务职责单一,构造依赖和公开方法有明确边界。
|
||||
- 所有行为锁定、故障注入、真实PostgreSQL/Redis和Gateway契约通过。
|
||||
- 任务、消息、Submit、Outbox、计费和回执语义与S0一致。
|
||||
- 同口径性能基线下降不超过5%,无新增DB池等待或队列积压。
|
||||
- 测试环境发布后服务健康、错误日志为0、Stream最终 `pending=0/lag=0`。
|
||||
- 浏览器只需验证受影响的任务/消息状态展示;不得用页面显示替代后端全链证据。
|
||||
Reference in New Issue
Block a user