收口七月卡状态回调与系列授权兼容契约
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

完成运营商实名回调、业务事件观测序列与受控配置装配,同时恢复 UR43 已交付的 packages[].remove 字段及旧响应兼容,统一更新 OpenSpec、OpenAPI 和交付文档。

Constraint: 七月测试环境里程碑不新增或运行自动化测试

Rejected: 以必填 operation_type 替换 packages[].remove | 会破坏已交付前端契约

Confidence: high

Scope-risk: broad

Directive: 后续修改系列套餐管理接口必须保持 packages[].remove 和 ShopSeriesGrantResponse 兼容

Tested: go run ./cmd/gendocs;go build -buildvcs=false ./...;openspec validate complete-july-iteration-test-release --strict;git diff --check

Not-tested: 按本 Change 约定未运行 go test,真实运营商与 Gateway 联调延期
This commit is contained in:
2026-07-24 19:59:24 +08:00
parent a18ed8bc8d
commit 5c4d17e9fc
79 changed files with 4341 additions and 478 deletions

View File

@@ -2,7 +2,87 @@
## 当前完成范围
任务 2.33 已完成运营商回调启用前的 ICCID 精确唯一性门禁,任务 2.342.35、2.36、2.37 已分别交付实名、流量、网络观测公共写入闭环三个轮询入口切换观测序列和运营商回调仍按 2.382.45 继续实施,不能因当前能力已落地而提前开放回调入口
任务 2.33 已完成运营商回调启用前的 ICCID 精确唯一性门禁,任务 2.342.44A 已交付实名、流量、网络观测公共写入闭环三个轮询入口切换、通用观测序列、业务入口触发以及电信实名、移动实名、联通实名成功和联通解除实名四个回调防腐层。UR#94 整体发布检查仍按 2.45 继续实施,不能因四条路由已接入而提前宣称整体生产验收完成
## 联通实名成功回调
新增来源材料 `docs/7月迭代/来源材料/realname.go` 补齐了此前缺失的联通实名成功报文,因此任务 2.44A 新增 `POST /api/callback/carriers/cucc/realname`。协议与联通解除实名一致:外层 `data` 必须是字符串,再解析内层非空 `iccid/dateChanged`
- 合法 ICCID 只按 19/20 位对应列精确查询;不复制旧代码的 20 位截 19 位,不跨列降级,也不任取多匹配卡。
- 使用 `ICCID + dateChanged` 的安全摘要作为语义幂等键;解析失败使用正文摘要,重复、冲突和中断恢复沿用 Integration Log 租约规则。
- 唯一命中后调用公共 `ApplyCardObservation(verified=true)`,实名事实变化时由公共用例写 Outbox并尽力提前完成同卡实名观测序列。
- 不调用旧 `inner_callback`、第三方推送、旧平台登录、`ModifyDate` 或 Gateway 二次确认;`dateChanged` 只作为上游变更时间和幂等语义留痕,不直接改写本地业务时间。
- 无论开关状态、报文结果或内部处理结果,均返回入口时间对应的 HTTP 200 固定 JSON 应答,避免运营商不可控重推。
## 运营商回调独立开关
四条回调复用公共 `system_config`,模块名为 `carrier_callback`,默认全部关闭:
- `carrier_callback.ctcc_realname.enabled`
- `carrier_callback.cmcc_realname.enabled`
- `carrier_callback.cucc_realname.enabled`
- `carrier_callback.cucc_realname_removal.enabled`
路由始终保留。开关关闭时不解析报文、不定位卡、不调用公共观测,只按正文摘要幂等记录 `ignored` Integration Log 并返回固定成功应答;配置缺失、非法或读取失败时失败关闭,同样不执行业务写入。配置读取以 PostgreSQL 为事实来源并复用 Redis 五分钟缓存。
超级管理员可通过 `GET /api/admin/system-configs?module=carrier_callback` 查询四个已注册开关,并通过 `PUT /api/admin/system-configs/:key` 更新布尔值。全局 Audit Event 已按用户决策取消Audit Writer 改为可选;若未来装配 Writer审计仍与配置更新同事务执行未装配时不会再阻塞受控配置更新也不会用 Integration Log 冒充配置审计。
## 联通解除实名留痕回调
任务 2.44 新增 `POST /api/callback/carriers/cucc/realname/remove`。Adapter 强制要求外层 `data` 为 JSON 字符串,再解析内层 `iccid` 与非空 `dateChanged`;任何情况下都返回入口时间对应的固定 JSON 成功应答。
- 空正文、外层或内层 JSON 失败、`data` 非字符串、ICCID 非法及变更时间为空统一记录 `invalid_payload`
- 合法 ICCID 仅按 19/20 位对应列精确定位;未找到、多匹配和数据库错误分别记录 `not_found``conflict``failed`,不会跨列降级或任取卡。
- 合法且唯一识别的解除通知统一终结为 `ignored`;不调用 `ApplyCardObservation`、观测序列、Gateway、旧 `DelRealName` 或第三方推送,不修改实名状态、首次实名时间、检查时间和逆转计数。
- 解析成功时使用 `ICCID + dateChanged` 语义摘要防重JSON 空白或字段顺序变化保持同一幂等语义;同语义不同正文另记 `conflict`解析失败则以完整正文摘要防重pending 记录支持租约恢复。
- Access Log 继续使用运营商回调摘要策略Integration Log 只保存正文长度、哈希、Content-Type 和哈希资源键;未新增数据库迁移或自动化测试。
## 移动实名成功回调防腐层
任务 2.43 新增 `POST /api/callback/carriers/cmcc/realname`,只解析已知 JSON 结构,并使用请求入口捕获时间返回 `code=200``msg=success``YYYY-MM-DD HH:mm:ss` 格式时间戳。
- 只有 `status="0"``message="正确"`、首条 `result.regStatus="00000"` 且 ICCID 合法时生成已实名观测;空 ICCID、失败状态、空结果和无法解析的 JSON 均记录 `invalid_payload`
- 优先使用 `busiSeq` 的安全摘要作为稳定幂等语义,缺失时使用正文摘要;同事务不同正文另记 `conflict`pending 处理超过一分钟后允许原子认领恢复。
- ICCID 仅按 19/20 位精确列定位,不按 MSISDN 补查,不登录旧管理平台,不保存账号密码或 Cookie也不调用旧第三方推送、修改到期时间或 Gateway 二次确认。
- 成功结果复用公共 `ApplyCardObservation` 并尽力提前完成同卡实名序列;未找到、多匹配和内部错误分别终结为 `not_found``conflict``failed`,不会任取卡写入。
- Access Log 复用运营商回调正文摘要策略Integration Log 不保存完整 JSON 或明文 ICCID仅保存长度、摘要、Content-Type 和哈希资源键。
- 新 Handler 已接入生产组合根、真实路由、OpenAPI Handler 构造和两个文档生成器。本任务按测试延期约定未新增或运行自动化测试。
## 电信实名回调防腐层
任务 2.42 新增 `POST /api/callback/carriers/ctcc/realname`,按已知 `ContractRoot` XML 协议解析电信实名结果,并始终返回运营商约定的 JSON 成功应答。
- 只有 `RESULTMSG=成功``ACCEPTMSG` 包含“已完成实名信息补录”时生成已实名观测;实名信息清除和其他合法业务结果仅记录 `ignored`,不查询或修改卡。
- ICCID 去除首尾空白后复用公共格式校验,仅接受 19 或 20 位值,并分别精确查询 `iccid_19``iccid_20`;查询不应用登录账号数据范围,未找到和多匹配分别记录 `not_found``conflict`
- 入站先以 `GROUP_TRANSACTIONID` 的安全摘要建立稳定幂等语义;同一外部事务的重复载荷直接成功返回,不重复首次实名、套餐激活或领域事件,不同载荷另记 `conflict`。缺少外部事务号时以完整正文摘要防重;若首次处理在 `pending` 阶段中断,超过一分钟租约后的重复回调可原子认领并恢复处理。
- 实名成功复用公共 `ApplyCardObservation` 事务闭环,并在成功后尽力提前完成同卡未执行实名观测序列;内部处理失败尽力把 Integration Log 终结为 `failed`
- Access Log 对 `/api/callback/carriers/` 强制使用敏感正文摘要策略,不记录完整 XML 或明文 ICCIDIntegration Log 只保存正文长度、SHA-256 摘要和哈希资源键。
- 迁移 `000181` 为 Integration Log 增加 `conflict` 终态发布时必须先执行迁移再开放路由down 迁移会先把已有 `conflict` 归并为 `failed`
- 新 Handler 已接入真实路由、生产组合根、OpenAPI Handler 构造以及 `cmd/api/docs.go``cmd/gendocs/main.go` 两个文档生成器。
## 读取与实名入口的 Best Effort 观测触发
任务 2.39 在不迁移 Query、不新增接口、不改变响应结构的前提下接入了 C 端资产详情、后台资产实时状态、OpenAPI 卡/设备流量、OpenAPI 卡网络/实名查询,以及 C 端和后台实名链接入口。
- 读取型入口使用稳定且调用方无关的场景码,按实际卡资源和同步类型创建无预期序列;同一卡连续 OpenAPI 查询通过 2.38 的场景合并键复用未结束序列,不会按账号或每次请求追加三任务。
- C 端和后台设备资产详情按绑定卡分别触发实名、流量、网络观测OpenAPI 设备流量同样按绑定卡触发流量观测,卡标识解析到设备时仍以实际绑定卡为资源。
- C 端实名链接和后台 Gateway 实名链接仅在运营商 `realname_link_type != none` 且原有链接响应成功后触发 `expected=verified` 的实名序列;不支持在线实名的运营商不会建序列。
- 读取入口只把结构化请求放入容量受控的进程内分发队列,不等待 Redis、Asynq 或 Gateway也不把后台序列结果写入当前响应队列满载或 Redis/Asynq/Integration Log 失败只输出中文安全日志并保留 scene、resource、sync、series 和 request_id 关联信息。
- 原有后台与 C 端 `refresh` 手动刷新路径未注入分发器,仍直接执行一次同步,不生成 0/3/5 序列。OpenAPI、资产查询和实名入口的现有错误码、响应字段与业务结果保持不变。
## 0/3/5 卡观测事件序列
任务 2.38 已交付内部 `SeriesTrigger`、固定阶梯 Asynq 调度器和 Worker 执行闭环,但未修改任何查询、停复机、购包、套餐或设备控制入口;入口接入仍由 2.392.41 分别完成。
- 每个新序列以稳定 `series_id` 创建立即、3 分钟、5 分钟三个结构化任务,任务 ID 使用 `series_id + attempt``MaxRetry(0)`,单次失败不会删除或取消后续两个独立任务。
- Redis 合并键严格包含 `scene + resource_type + resource_id + sync_type`,只覆盖最后一次计划任务和短暂缓冲。首触发的预期、来源、请求/关联 ID 与基准时间被原子保存;重复触发不刷新上下文或 TTL补齐首次局部入队失败时仍使用首触发上下文同时避免重复生成第二组三任务。缺失请求/关联 ID 时生成同一稳定 UUID 贯穿三任务。
- 尝试执行前读取本地权威快照。明确预期已满足时,当前及剩余尝试立即写 `completed` Integration Log 并标记幂等完成,不再访问 Gateway`CompleteResourceSeries` 为可信实名回调提前结束同卡实名序列提供扩展点。
- 实际 Gateway 请求使用 `provider + sync_type + resource_id` Redis 互斥16 分钟 TTL 覆盖配置允许的 300 秒单次超时、两次网络重试和安全余量;流量同步复用既有 `traffic:sync:lock:card:{id}` 卡级锁,避免事件、轮询和手动刷新并发读取同一上游读数。互斥命中只把当前尝试记录为 `ignored`,后续阶梯任务保持不变。
- 运营商接入与同步类型的默认最小请求间隔为 10 秒。任务在持有本次请求互斥期间只等待剩余间隔,不建立全局五分钟冷却,也不创建 30/60/120 秒退避Gateway 超频只把当前 Integration Log 终结为 `rate_limited`
- 实名、流量、网络实际响应全部复用 `ApplyCardObservation``ApplyTrafficObservation``ApplyNetworkObservation`,没有新增第二套状态写入。设备信息同步类型已在 Application 契约中预留,具体执行适配器随 2.41 交付。
- 合并、互斥、最小间隔取消、预期提前完成和每次实际 Gateway 请求统一写 `tb_integration_log`,传播请求 ID、关联 ID、序列 ID、尝试序号、场景、资源、结果、耗时和是否变化请求摘要只保存卡 ID 与同步类型,不保存完整 ICCID。
本任务按 Change 测试延期约定未新增自动化测试,也未新增数据库迁移或同步运行表。序列运行态、幂等键和短时互斥属于 Redis 协调事实,三次可查询结果继续使用公共 Integration Log。
## 流量观测公共写入闭环
@@ -108,7 +188,9 @@ ORDER BY id;
## 回滚
down 迁移只把 `idx_iot_card_iccid_19/20` 恢复为 `000131` 的普通部分索引,不删除双列、不修改卡数据,也不触碰其他表索引。若回调已经开放并依赖精确唯一语义,应先关闭回调入口并确认没有并发写入,再评估回滚。
优先通过四个 `system_config` 开关分别停止业务处理,路由继续固定成功应答。down 迁移只把 `idx_iot_card_iccid_19/20` 恢复为 `000131` 的普通部分索引,不删除双列、不修改卡数据,也不触碰其他表索引。若回调已经开放并依赖精确唯一语义,应先关闭相关开关并确认没有并发写入,再评估回滚。
迁移顺序固定为 `000178`ICCID 唯一性)→ `000179`(实名逆转窗口)→ `000180`(副作用进度)→ `000181`Integration Log conflict 终态。一旦已经产生卡状态、Outbox、Integration Log 或副作用进度事实,不清表、不删除业务事实,也不恢复旧 Writer暂停对应开关和生产者后采用前向修复。
## 验证状态
@@ -116,4 +198,12 @@ down 迁移只把 `idx_iot_card_iccid_19/20` 恢复为 `000131` 的普通部分
- 已静态核对 `000179` up/down 成对、字段注释和 CHECK 约束一致,领域规则与持久化字段没有 Redis 事务依赖。
- 已静态核对 `000180` up/down 成对、副作用状态 CHECK 与常量一致,流量用例不再直接覆盖日流量落盘表。
- 已执行 `gofmt``go build ./...`;构建退出码为 0。
- 已静态核对观测序列任务固定为 0/3/5 分钟、`MaxRetry(0)`、结构化载荷、同场景合并键、`series_id + attempt` 幂等键、流量共享锁和 10 秒默认最小间隔;任务 2.38 未修改业务入口。
- 已静态核对任务 2.39 只接入 issue 07 指定读取与实名链接入口,稳定 OpenAPI 场景不包含账号身份,`realname_link_type=none` 不触发,后台/C 端手动刷新路径没有分发调用。
- 已静态核对任务 2.42 的 XML 根结构、19/20 位精确查询、系统级资源定位、稳定外部事务幂等、载荷冲突留痕、固定成功应答、Access Log 摘要策略及 Handler/路由/文档生成器装配。
- 已静态核对新增联通实名成功回调的双层 JSON、非空变更时间、19/20 位精确查询、语义幂等、公共实名观测、固定成功应答及四个受控系统配置开关。
- 任务 2.45 已完成四个回调 Handler、免认证路由、生产组合根、两个文档生成器、OpenAPI 产物、观测 Worker、Outbox Consumer、API/Worker Gateway Client 和迁移顺序的静态装配检查。
- `docs/admin-openapi.yaml` 已生成电信实名、移动实名、联通实名成功和联通解除实名四条路径;四个开关均通过后台受控 `system_config` 注册,默认关闭。
- 已执行 `gofmt``git diff --check` 和离线 `go build ./...`,构建退出码为 0。按用户要求保留既有 Gateway 与部署 YAML 配置,本任务未修改其配置来源或部署值。
- 当前状态只能标记为“后端代码与测试环境装配完成、真实验证延期”;真实运营商样例、真实 Gateway、19/20 位命中、逐个启停和异常恢复统一转 6.4。
- 按七月测试环境豁免,本轮未连接真实 PostgreSQL、未运行迁移测试无冲突升级、19/20 位冲突、软删除重复和 down/up 重放验证延期到 6.1、6.3,不能标记为生产验收通过。