feat: harden platform workflows and UI governance
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
2. 依次应用 `20260720110000_add_receipt_identity`、`20260720113000_add_report_business_metrics` 与 `20260720114500_add_receipt_phone_number`。三者为历史回执生成不冲突的 `receiptKey`,增加回执文本、目的号码和匹配索引,并增加报表失败数及利润退款金额列。历史目的号码先从原关联主记录回填;对已知错绑记录仍须依据生产提交记录、通道和原始 Gateway 日志专项复核,不能仅靠该回填自动改绑。
|
||||
3. 先重启 Gateway,再重启 API;检查 Redis Stream、活动通道、TPS key、API/Gateway health 和近期错误日志。
|
||||
4. 对 T-4 至 T-1 及需修复的历史日期重复执行报表重算,核对查询与 CSV 的发送、成功、失败、收入、退款、成本、利润和到达时长。
|
||||
5. 追加应用 `20260721150000_backfill_misattributed_delivery_receipts`。该迁移只处理“回执已关联主记录,且主记录 + 上游 Msg_Id + 目的号码只能命中一条提交记录”的成功回执:先把历史错误通道改为真实提交通道,再将成功状态聚合到主记录。零匹配或多匹配记录保持不变。迁移可重复执行且不会创建回执、计费或客户下游投递。
|
||||
6. 新迁移完成并启动 API 后,确认启动时 T-4 至 T-1 重算覆盖受影响日期;若发布日期已使目标历史日超出滚动窗口,必须在受控维护命令中显式重算对应日期,不能只修改报表表格或手工填写成功数。
|
||||
|
||||
## 回滚
|
||||
|
||||
应用代码可回滚到上一版本,但新增列和索引默认保留,避免丢失已接收的回执身份、错误文本和重算结果。若确认不存在新版本写入且必须做结构回滚,应先备份,再依次删除两个报表新增列、回执新增列及索引;删除 `receiptKey` 唯一约束前必须确认旧代码不会再次以模糊条件消费回执。生产禁止未经审批直接执行破坏性回滚。
|
||||
|
||||
`20260721150000_backfill_misattributed_delivery_receipts` 是数据纠正迁移,没有安全的自动逆向迁移。回滚应用代码时保留已纠正的回执和主记录;若业务要求恢复迁移前值,只能从发布前 PostgreSQL 备份按明确记录 ID 定向恢复,禁止用账号或日期范围批量反向覆盖。
|
||||
|
||||
@@ -280,10 +280,12 @@
|
||||
- 已实现上游连接断开时的 pending submit 状态补偿第一版:如果某条上游 CMPP 连接在收到 submit resp 前断开,Gateway 会立即唤醒该连接上等待中的 pending submit,请求返回 `timeout/CONNECTION_LOST`,由 NestJS 进入既有补发或释放冻结逻辑,不再只依赖固定超时。
|
||||
- 已实现“上游可能已受理但 submit resp 丢失”场景的保守补偿第一版:Gateway 在 receipt 事件中补充手机号;NestJS 对无法按 `messageId/gatewayMessageId` 精确命中的回执,只在“同通道、同手机号、72 小时窗口内、且仅存在 1 条 `timeout + gatewayMessageId=null` 的 submit 记录”时才回填并接收该回执,避免误绑到其他短信。
|
||||
- 已实现 Gateway 提交异常治理:Go Gateway 对多次处理仍失败的 `SubmitCommand` 不再无限滞留在 PEL,而是按阈值写入 NestJS 真实 `GatewaySubmitDeadLetter` 表(数据库表名和内部接口保留技术兼容名,页面统一称“Gateway提交异常”)。运营端 `/admin/gateway-submit-exceptions` 提供真实分页、筛选、汇总、脱敏详情和单条重新入队;原始 payload、密码、密钥不得返回浏览器。重新入队必须要求近期认证、填写原因、勾选“已确认上游未受理”,并校验短信尚未 accepted/submitted/delivered/unknown、通道 active 且 connected、人工次数小于 3;服务端以 pending 到 requeueing 的原子状态抢占防止重复点击,成功写回 Redis Stream 后记录操作人、原因、Stream ID 和时间。收到后续 SubmitResult 时必须将对应异常记录闭环为 resolved。
|
||||
- Gateway 提交异常重新入队必须使用“异常记录 ID + 下一次人工次数”的稳定幂等键,通过 Redis Lua 原子完成“检查幂等键、XADD、保存 Stream ID”;API 在 XADD 成功后宕机或数据库落账失败时,超时恢复扫描必须复用同一幂等键完成落账,不能再次产生 Stream 消息。Gateway 重复上报同一个原始 Stream 异常不得把 `requeued/resolved` 回退成 `pending`。
|
||||
- 已实现 Gateway 通道级 Redis 限速:NestJS 入队前保留业务层通道限速,Go Gateway 在真正调用上游 Submit 前再次按通道 ID 预约发送时隙;连接命令把权威 TPS 写入 Redis,提交按权威值与消息值的较小者执行。普通 Stream 消息在等待期间不 ACK、不转失败,多实例共同使用同一限速状态;worker 对同批消息并发调度,低 TPS 通道等待不阻塞其他通道。
|
||||
- 已实现 Gateway 重启后的 active 上游通道恢复:部署先重启 Gateway 再重启 API;API 启动后从 PostgreSQL 读取 active 通道,重新下发连接命令,恢复 Gateway 内存连接池、真实连接状态和 Redis 权威 TPS key,不得继续沿用重启前的 connected 状态。
|
||||
- 已实现客户侧下游投递重试第二版:客户系统负责断线后重连;平台在客户离线或投递失败时把 Deliver Receipt/上行 Deliver 保留在 `CmppDownstreamDelivery`,客户 bind 成功后立即拉取 pending,且 Gateway 会对当前在线账号周期补投;超过重试上限后转 `failed` 并写失败审计。
|
||||
- 已实现下游投递失败审计与人工重投第一版:运营端后端与页面可分页查看 `CmppDownstreamDelivery` 的 pending/awaiting_ack/failed/unconfirmed/rejected/delivered 记录,支持按状态、类型、应用和关键字筛选,并可对非 `awaiting_ack` 记录执行人工重投,真实调用 Gateway `/downstream/receipt` 或 `/downstream/uplink`。主记录必须分开保存自动重试次数 `retryCount`、人工重投次数 `manualRetryCount` 和最近人工重投时间 `lastRetriedAt`,操作日志保留重投前状态与自动重试次数。
|
||||
- 同一下游投递的人工重投必须使用 `id + status + updatedAt` 条件更新原子认领;认领后先进入 `manual_requeueing`,避免运营并发请求或 Gateway pending 恢复扫描同时双发。调用完成后进入 `awaiting_ack` 或失败状态;进程中断超过默认 2 分钟后自动转回 `pending`,由 Gateway 单一路径恢复投递。阈值可通过 `CMPP_DOWNSTREAM_MANUAL_REQUEUE_STALE_MS` 调整。
|
||||
- 已实现下游投递批量重投第一版:运营端可在当前页勾选多条 `pending/failed` 下游投递记录,调用真实批量接口逐条重投并返回成功/失败汇总,不允许用前端循环假装成功。
|
||||
- 下游投递的 `pending` 展示必须结合真实尝试字段:自动与人工次数均为 0 时显示“待首次投递”,`retryCount > 0` 时显示“等待自动重试”,`manualRetryCount > 0` 时显示“人工重投排队中”。人工重投可重置新一轮自动重试预算,但不得把记录伪装成从未投递。
|
||||
- 下游投递不得无限停留在 `pending`:Gateway 对未写出的结果必须返回明确的 `retryable/reasonCode/errorMessage`。状态回执在原消息映射已经丢失且缺少 `submitSequenceId` 时属于不可恢复错误,立即转为 `failed` 并保留失败原因和操作日志;客户端暂时离线属于可恢复错误,按既有重试次数与指数退避处理。所有 pending 从创建时间或最近人工重投时间起最多保留 72 小时,可通过 `CMPP_DOWNSTREAM_PENDING_TIMEOUT_HOURS` 调整,超时后自动终结为 `failed`。
|
||||
@@ -1096,6 +1098,8 @@
|
||||
### 14.2 发送能力
|
||||
|
||||
- 需要支持定时发送。
|
||||
- API 实例启动后必须自动扫描并派发到期任务,管理端手工触发仅作为运维补偿入口,不能作为正常发送的前置操作。
|
||||
- 多 API 实例只能有一个实例成功认领同一定时任务;认领后进程退出或 Redis 短暂不可用时,任务超时后必须可恢复,且不得重复冻结余额或重复创建队列作业。0 元短信同样适用恢复和幂等要求。
|
||||
- 导入号码文件格式支持 CSV、TXT。
|
||||
- 导入文件最大 20 MB。
|
||||
- 单任务最大号码数默认 100 万条。
|
||||
@@ -1563,6 +1567,7 @@
|
||||
5. HTTP 单发公开契约使用 `mobile`、`content` 和可选 `clientMessageId`,不要求内部签名或模板 ID;服务端按 CMPP 同一规则识别已审核签名、模板及变量,复用风控、余额、计费、路由和队列。成功返回可查询 messageId,业务拒绝返回对应 4xx,不得在已创建记录后返回“批次不存在”。
|
||||
6. 客户发送候选只返回 approved 签名和模板,管理视图可查看历史状态。模板变量必须拒绝空变量、未闭合、中文或非法名称、重复名称及超长名称;客户端签名视图仅返回必要报备汇总状态。
|
||||
7. 报备资料提供官方 XLSX 模板和按当前筛选导出;导入拒绝空文件、错误扩展名、超限文件以及公式/脚本单元格。分析、提交和导出日志记录操作人、文件名、筛选条件、成功数、失败数和 IP,不保存密钥或完整请求体。
|
||||
8. 下游客户连接超时清理必须同时覆盖“最后心跳早于阈值”和“最后心跳为空但连接建立时间早于阈值”;刚建立且尚未超过阈值的空心跳连接不得误删。历史共享供应商账号导致的错归回执,只能在同一主记录、同一通道消息号、同一目的号码且唯一提交记录可证明时改绑并聚合;存在歧义时必须保留原数据供人工核查。
|
||||
|
||||
## 2026-07-20 客户端用户管理移动操作可达性要求
|
||||
|
||||
@@ -1570,3 +1575,58 @@
|
||||
2. 390×844和375×667下四项操作必须全部可见、可聚焦、可命中,触控热区高度至少44px;操作组应具有包含目标用户名称的可访问名称。
|
||||
3. 删除仍必须走真实客户端用户API与确认流程,不得通过前端隐藏或静态数据冒充;页面验收只打开并取消确认时,不得产生DELETE请求或数据库状态变化。
|
||||
4. 1440×900、1366×768和768×1024必须同步回归。1366桌面宽表若仍需内部横向滚动,滚动条必须可发现且操作可到达;固定操作列和邮箱列宽另按全站Table整改治理。
|
||||
|
||||
## 2026-07-21 双门户会话隔离与深链恢复要求
|
||||
|
||||
1. 运营端和客户端必须分别使用独立的浏览器存储键、跨标签广播频道和 HttpOnly Cookie;后端只允许目标门户的 Cookie 认证对应路由,不能依赖前端隐藏或跳转实现隔离。
|
||||
2. 受保护路由必须先向真实会话接口完成初始化,再决定渲染或跳转;刷新和直接打开深链时不得先跳登录页,失效后重新登录必须回到同站点、同门户白名单内的原目标。
|
||||
3. 同一浏览器可以同时保持运营端和客户端登录。锁定、解锁、会话过期和主动退出事件仅作用于当前门户;退出一端不得删除、广播或撤销另一端会话。
|
||||
4. 会话锁定必须保留目标页面,显示锁定原因、恢复说明和剩余策略;锁定状态下暂停首次业务路由请求,解锁后在原 URL 重新挂载并从真实 API 加载数据。当前标签页内已加载的非敏感草稿不得因跨门户事件被清除。
|
||||
5. 旧共享 Cookie 和旧共享 localStorage 只允许做一次同门户迁移或清理,不得继续作为双门户认证来源。生产发布会使旧共享 Cookie 失效时,必须在发布说明中明确需要重新登录。
|
||||
|
||||
## 2026-07-21 UI/UX A2安全上传与日志导出补充
|
||||
|
||||
- 客户端文件上传和下载必须使用`/api/client/files`专用接口;后端从当前会话用户反查企业,不接受请求头指定文件归属。客户端仅允许企业认证、签名报备、引流报备三类用途及对应安全目录,跨企业文件统一不可见。
|
||||
- 运营端和客户端系统日志导出必须读取PostgreSQL真实筛选结果,具备提交中防重复、结构化成功结果、操作单号、文件下载、失败原地重试和会话恢复后的筛选保留。客户端导出不得包含详情JSON、IP、User-Agent等内部字段。
|
||||
- CSV导出最多10000条并明确截断状态;对`= + - @`开头单元格做公式注入防护。瞬时恢复状态可使用按门户隔离的`sessionStorage`,不得作为业务数据源。
|
||||
|
||||
## 2026-07-21 UI/UX A3审核风险治理补充
|
||||
|
||||
- 运营端签名、模板“通过”必须先调用后端资格预检,确认层展示对象名称、唯一ID、企业、应用、资料完整度、阻断原因和后续影响;阻断项存在时后端和前端均不得批准。
|
||||
- 审核决定必须使用当前登录会话审核人、客户端生成并重试复用的幂等键,以及对象`updatedAt`状态版本。后端仅允许`pending`对象在Serializable事务中原子变更,版本不一致返回409且不得覆盖其他审核员结果。
|
||||
- 审核成功必须返回持久化`AuditRecord.id`作为操作单号;同对象同幂等键重试返回原结果且不得重复更新或重复审计。旧的签名/模板批准接口也必须进入同一治理服务,不得保留绕过入口。
|
||||
|
||||
## 2026-07-21 UI/UX A4报备生成风险治理补充
|
||||
|
||||
- 待报备资料必须由后端预检应用启用状态、资料审核与待报备状态、资料版本、启用路由/通道、通道字段配置和必填资料;不合格资料在列表中不可选择,并返回可执行的阻断原因。
|
||||
- 生成接口提交时必须重新预检,不能信任列表时的前端状态。零个可生成通道组合必须返回业务4xx,不得先创建空批次或显示成功。
|
||||
- 业务去重键必须覆盖资料类型、资料ID、资料版本、应用、通道及适用运营商;历史已生成组合返回跳过及既有批次,不得无提示覆盖旧任务或重复导出。
|
||||
- 每次生成要求8至128位客户端幂等键。后端通过PostgreSQL事务锁认领操作,相同键同一请求返回原操作单及`replayed=true`,相同键用于不同范围返回409;生成结果按成功、跳过、失败分项返回并提供操作单号。
|
||||
|
||||
## 2026-07-21 UI/UX A5删除治理补充
|
||||
|
||||
- 通道、签名和模板删除前必须由后端返回对象身份、活动依赖数量与对象摘要、影响范围、`allowedActions`、`blockedReasons`、状态版本和可恢复说明;前端不得自行推断或只显示通用风险文案。
|
||||
- 删除提交必须包含预检版本、8位以上幂等键和至少4字符原因。后端在Serializable事务中重新以`updatedAt`和未删除状态做条件更新,并写入包含原因、依赖、影响及幂等键的`OperationLog`,返回操作单号和重放标识。
|
||||
- 客户端只能预检和删除当前会话企业的签名/模板,不得删除运营通道;通道被活动通道组/路由/连接/未结束报备引用,签名被模板/引流/未结束报备引用,模板被未结束发送/批量任务引用时,后端必须阻断。
|
||||
- 删除采用逻辑删除,历史发送、回执、计费、审核和审计数据继续保留;恢复需有审计依据。所有旧删除入口必须委托同一治理服务,禁止保留绕过路径。
|
||||
|
||||
## 2026-07-21 UI/UX A6人工充值治理补充
|
||||
|
||||
- 充值记录页和企业管理页必须共用同一人工充值组件。取消、右上角关闭或完成后必须销毁未提交金额、备注、预检结果和幂等键;重新打开必须是新草稿,不得自动恢复资金操作输入。
|
||||
- 最终入账前必须调用真实后端预检并重复展示企业名称、编码、唯一ID、操作方向、当前现金余额、本次变动、预计现金余额和备注;余额以PostgreSQL账户读取结果为准,不能用前端静态计算冒充资格检查。
|
||||
- 人工充值请求必须使用当前会话操作者、8至128位幂等键和账户`updatedAt`版本。相同幂等键同一请求返回原订单/操作单和`replayed=true`;不同范围复用键或账户版本变化必须返回冲突并要求重新核对。
|
||||
- RechargeOrder、TenantAccount余额增量、AccountTransaction和OperationLog必须在同一Serializable事务内原子完成;审计记录需包含前余额、变动金额、后余额、订单号、原因和幂等键。正数为充值,负数为冲正,金额精确到小数点后4位且不得为0。
|
||||
|
||||
## 2026-07-22 UI/UX A7公共Dialog契约
|
||||
|
||||
- 所有公共Dialog打开后必须把焦点送入弹窗,并将Tab/Shift+Tab约束在当前顶层弹窗;背景内容必须同时不可聚焦、不可被辅助技术读取,页面滚动必须锁定。
|
||||
- Dialog必须通过`aria-labelledby`关联可见标题;遮罩不得伪装成可聚焦关闭按钮。右上角、取消、Escape和遮罩关闭必须使用同一关闭协议,关闭后焦点返回触发控件。
|
||||
- 可编辑弹窗必须声明dirty状态。存在未保存内容时,任何关闭入口都必须先显示具名确认层;继续编辑保留草稿并恢复原焦点,只有明确放弃后才能销毁草稿。
|
||||
- 确认层作为顶层`alertdialog`管理焦点并隔离父弹窗;手机端按钮应安全堆叠,320—1440px内不得产生页面级横向溢出。
|
||||
|
||||
## 2026-07-22 UI/UX A2/A3收口补充
|
||||
|
||||
- 客户端上传必须由可见页面操作触发客户端专用接口;租户只能从当前会话用户解析,上传用途、对象目录和租户内下载均由后端校验,真实对象必须写入配置的MinIO并可回读。
|
||||
- 企业认证上传区在手机端必须完整显示长文件名;步骤条允许安全横向浏览且默认展示第一步,省市选择不得因固定宽度被裁切。
|
||||
- 客户端日志导出必须显示提交中、完成数量、操作单号、下载和失败重试;客户端CSV仅允许时间、级别、模块、操作人、动作、资源ID六列,不得包含详情、IP或嵌套内部字段。
|
||||
- 签名和模板审核通过/驳回必须共用资格预检、状态版本、幂等键、事务审计和结构化结果协议。页面必须在最终决定前显示对象唯一标识、资格和影响范围;取消确认不得改变状态或写审计。
|
||||
|
||||
@@ -1568,6 +1568,32 @@
|
||||
- 被选候选状态变为 `claimed`,其他 pending 候选变为 `rejected`,认领动作写入操作日志。
|
||||
- 认领后创建真实 `CmppDownstreamDelivery(deliveryType=uplink)`,并按现有下游投递链路在线推送或离线保留重试。
|
||||
|
||||
### TC-GW-028 下游人工重投并发认领与中断恢复
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:真实 PostgreSQL、NestJS API 和 Gateway 控制面可用;准备一条 failed/pending 下游投递记录。
|
||||
- 步骤:
|
||||
1. 两个运营请求同时重投同一记录,并让 Gateway pending 恢复扫描同时运行。
|
||||
2. 检查数据库状态、人工次数和 Gateway 控制面调用次数。
|
||||
3. 另构造一条停留在 `manual_requeueing` 且超过恢复阈值的记录,运行后台扫描。
|
||||
- 预期结果:
|
||||
- `id + status + updatedAt` 条件更新只允许一个运营请求认领;另一个返回明确冲突。
|
||||
- 认领期间状态为 `manual_requeueing`,不进入 Gateway 的 pending 拉取结果,同一轮只调用一次控制面。
|
||||
- 人工次数只增加一次;进程中断的陈旧认领自动恢复为 pending,随后由 Gateway 单一路径补投。
|
||||
|
||||
### TC-GW-029 Gateway提交异常幂等重入队与宕机恢复
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:真实 PostgreSQL、Redis Stream 和 NestJS API 可用;存在 pending Gateway 提交异常记录。
|
||||
- 步骤:
|
||||
1. 人工重新入队,在 Redis XADD 成功后、数据库写回 requeued 前模拟进程退出。
|
||||
2. 等待陈旧 requeueing 恢复扫描,再重复调用相同幂等发布。
|
||||
3. Gateway 再次上报原始 streamMessageId,并在恢复期间回传 SubmitResult。
|
||||
- 预期结果:
|
||||
- 恢复和重复调用返回同一个 Redis Stream ID,Stream 只有一个 SubmitCommand。
|
||||
- 数据库最终为 requeued 或被更早 SubmitResult 闭环为 resolved,人工次数只增加一次。
|
||||
- 重复异常报告不得把 requeued/resolved 回退为 pending,迟到恢复不得覆盖 resolved。
|
||||
|
||||
### TC-SEND-021 优先队列插队发送
|
||||
|
||||
- 优先级:P0
|
||||
@@ -2090,6 +2116,32 @@
|
||||
- 定时任务详情展示计划发送时间、创建时间、创建人、号码总数、预估费用、当前状态。
|
||||
- 到点执行后的状态变化在客户端和运营端一致。
|
||||
|
||||
### TC-SCHEDULE-007 多实例自动调度和原子认领
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:启动两个连接同一 PostgreSQL/Redis 的 API 实例,存在一条已到期 scheduled 任务。
|
||||
- 步骤:
|
||||
1. 不调用管理端手工派发接口,等待自动扫描周期。
|
||||
2. 两个实例同时扫描同一任务。
|
||||
3. 查询任务、冻结流水、短信记录和 BullMQ 作业。
|
||||
- 预期结果:
|
||||
- 到期任务自动进入 queued/sending。
|
||||
- 仅一个实例原子认领成功。
|
||||
- 每条短信只存在一个以消息记录 ID 为 jobId 的队列作业,余额只冻结一次。
|
||||
|
||||
### TC-SCHEDULE-008 调度中断、0 元任务和超时恢复
|
||||
|
||||
- 优先级:P0
|
||||
- 前置条件:存在普通计费任务和客户单价为 0 的免费任务,调度认领超时阈值可缩短用于测试。
|
||||
- 步骤:
|
||||
1. 分别在冻结后、短信转 queued 后和部分作业入队后模拟进程退出或 Redis 不可用。
|
||||
2. 恢复 API/Redis 并等待认领超时后再次扫描。
|
||||
3. 重复执行恢复扫描。
|
||||
- 预期结果:
|
||||
- 陈旧的 scheduled_dispatching/scheduled_recovering 任务能够被唯一重新认领并完成入队。
|
||||
- 已存在冻结流水时不重复冻结;0 元任务入队失败时保留可恢复状态而非错误终结。
|
||||
- BullMQ jobId 幂等阻止重复作业,最终任务和消息状态一致。
|
||||
|
||||
### TC-LOG-001 登录和登出日志
|
||||
|
||||
- 优先级:P1
|
||||
@@ -3553,3 +3605,89 @@ npm run verify:phase8
|
||||
| TC-UIUX-P0-003 | 在375视口点击删除,读取确认内容后点击取消。 | 确认层显示目标用户名;取消后用户仍在列表,PostgreSQL记录保持active且没有执行删除。 |
|
||||
| TC-UIUX-P0-004 | 依次设置768×1024、1366×768、1440×900并复核同一用户。 | 平板四项操作全部可见且热区≥44px;1440首屏完整;1366即使存在内部横向滚动,滚动后删除必须完整可达,且页面级无横向溢出。 |
|
||||
| TC-UIUX-P0-005 | 完成五视口操作后检查浏览器控制台并执行前端/API构建与用户服务回归。 | 无新增console error/warn;前端与API build通过,用户服务测试通过,`git diff --check`通过。 |
|
||||
|
||||
### 17.17 2026-07-21 下游连接恢复与历史回执回填
|
||||
|
||||
| 用例编号 | 操作 | 预期结果 |
|
||||
| --- | --- | --- |
|
||||
| TC-CMPP-DOWNSTREAM-NULL-001 | 创建一条 `status=connected`、`lastHeartbeatAt=NULL` 且 `connectedAt` 早于心跳阈值的客户接入连接,再触发新连接登记。 | 陈旧记录在连接数校验前删除,新连接不被错误的 `cmppMaxConnections` 拒绝;Gateway 能继续读取 Submit。 |
|
||||
| TC-CMPP-DOWNSTREAM-NULL-002 | 创建一条刚建立、`lastHeartbeatAt=NULL` 但 `connectedAt` 尚未超过阈值的连接并执行清理。 | 新连接保留,不因首次心跳尚未写入而误删。 |
|
||||
| TC-RECEIPT-BACKFILL-001 | 构造两个供应商通道共用账号,历史 DELIVRD 被写到错误通道,但同一主记录、Msg_Id、号码只有一条匹配提交记录,执行迁移两次。 | 回执改绑到唯一提交通道;主记录同步为 delivered 并写入回执状态、原始码、通道消息号和到达时间;重复执行结果不变。 |
|
||||
| TC-RECEIPT-BACKFILL-002 | 构造同一历史回执能匹配零条或多条提交记录的歧义样本。 | 迁移不修改回执和主记录,保留人工核查,不以账号或模糊 Msg_Id 强行归属。 |
|
||||
| TC-REPORT-BACKFILL-001 | 历史回执迁移后执行 T-4 至 T-1 重算,查询对账、利润、质量及 CSV。 | 历史成功数、收入、成本、利润和平均到达时长反映修复后的主记录与提交/回执;重复重算一致。 |
|
||||
| TC-DICTIONARY-DUPLICATE-001 | 对活动或逻辑删除的全局/企业黑名单重复创建相同手机号。 | 后端返回 HTTP 409、`BLACKLIST_DUPLICATE` 和 `phoneNumber` 字段提示,不返回 500,也不创建重复数据。 |
|
||||
|
||||
### 17.18 双门户会话隔离、深链与锁定恢复
|
||||
|
||||
| 用例编号 | 操作 | 预期结果 |
|
||||
| --- | --- | --- |
|
||||
| TC-SESSION-DEEPLINK-001 | 未登录直接打开客户端13条受保护路由,完成验证码登录后逐条刷新。 | 登录页说明将恢复目标;登录后返回原深链;13条路由刷新后 URL 和客户端身份不变,数据来自真实 API。 |
|
||||
| TC-SESSION-PORTAL-001 | 在同一浏览器先后登录运营端与客户端,并分别访问受保护页面。 | 浏览器同时持有独立 admin/client Cookie 和 localStorage;两端显示各自身份,不互相覆盖或错跳门户。 |
|
||||
| TC-SESSION-LOGOUT-001 | 两端同时登录时退出客户端,再刷新运营端;反向重复。 | 只删除和广播当前门户会话;另一门户会话、页面与 Redis 记录继续有效。 |
|
||||
| TC-SESSION-LOCK-001 | 锁定运营端会话,同时请求客户端当前会话;刷新锁定页面并输入当前密码解锁。 | 运营端返回 locked、客户端仍 active;锁定页说明解锁后返回当前页;解锁轮换当前门户 Cookie,原 URL 与业务数据恢复。 |
|
||||
| TC-SESSION-COOKIE-001 | 同时携带 admin/client Cookie 请求两端接口,并仅携带错误门户 Cookie 重试。 | 中间件只读取路径对应 Cookie;错误门户 Cookie 返回401且不会尝试认证或泄露另一门户状态。 |
|
||||
|
||||
## 2026-07-21 UI/UX A2安全上传与日志导出用例
|
||||
|
||||
- `TC-UIUX-A2-UPLOAD-001`:客户端带伪造`x-tenant-id`上传允许用途文件,数据库`FileObject.tenantId`仍等于当前会话用户企业,MinIO对象可经客户端下载接口读回。
|
||||
- `TC-UIUX-A2-UPLOAD-002`:任意用途、目录穿越和跨企业文件下载分别返回4xx,且拒绝发生在对象存储写入前。
|
||||
- `TC-UIUX-A2-EXPORT-001`:两端按当前筛选导出真实日志,按钮在请求期间禁用;成功显示记录数、截断提示、操作单号和下载入口,重复点击不产生并发请求。
|
||||
- `TC-UIUX-A2-EXPORT-002`:客户端忽略请求体租户,只导出当前企业;CSV表头不包含详情、IP、User-Agent,危险公式前缀被转义。
|
||||
- `TC-UIUX-A2-EXPORT-003`:导出失败后页面保留筛选并提供原地重试;会话跳转恢复后可读取当前门户专用瞬时恢复条件,不错跳另一门户。
|
||||
|
||||
## 2026-07-21 UI/UX A3审核风险治理用例
|
||||
|
||||
- `TC-UIUX-A3-REVIEW-001`:待审签名缺应用、企业资料或资质文件时,预检返回具体`blockedReasons`,`allowedActions`不含`approve`,直接提交批准同样返回4xx。
|
||||
- `TC-UIUX-A3-REVIEW-002`:完整待审签名/模板打开通过确认层,展示名称、ID、企业、应用、资格结果和影响;取消不改变数据库状态。
|
||||
- `TC-UIUX-A3-REVIEW-003`:确认后按钮立即进入提交中并禁止重复点击;成功返回审计操作单号,AuditRecord记录当前会话审核人、前后状态和幂等标识。
|
||||
- `TC-UIUX-A3-REVIEW-004`:相同幂等键重复请求返回同一操作单号及`replayed=true`,数据库只有一次状态变更;相同键用于不同决定返回409。
|
||||
- `TC-UIUX-A3-REVIEW-005`:两个审核员使用同一`expectedUpdatedAt`并发决策,仅一个`updateMany`成功,另一个返回`REVIEW_VERSION_CONFLICT`且不得覆盖赢家。
|
||||
|
||||
## 2026-07-21 UI/UX A4报备生成风险治理用例
|
||||
|
||||
- `TC-UIUX-A4-REPORT-001`:已审核资料未绑定应用、应用停用或没有启用路由时调用预检;返回`eligible=false`和具体阻断原因,列表复选框及生成按钮不可用。
|
||||
- `TC-UIUX-A4-REPORT-002`:应用路由到启用通道,但通道缺当前资料类型字段或资料缺必填值;预检逐通道返回缺失项,零可生成目标不得创建`ReportMaterialBatch`、任务或文件。
|
||||
- `TC-UIUX-A4-REPORT-003`:完整资料打开生成确认层;显示企业、应用、资料版本、预计通道、运营商及成功/跳过计数,取消后数据库无批次、任务和文件变化。
|
||||
- `TC-UIUX-A4-REPORT-004`:相同资料版本、应用、通道和运营商已存在成功批次时再次预检;返回既有批次并跳过。资料版本或路由运营商变化后使用新的业务键重新评估。
|
||||
- `TC-UIUX-A4-REPORT-005`:相同幂等键并发或重试生成同一范围,仅产生一次批次并返回同一操作单;相同键改换资料范围返回409;按钮在请求中禁止重复提交。
|
||||
- `TC-UIUX-A4-REPORT-006`:在1440×900、1366×768、768×1024、390×844和375×667打开确认层;页面无横向溢出,弹窗完整位于视口且成功/跳过/失败、取消和确认操作均可见,控制台无error/warn。
|
||||
|
||||
## 2026-07-21 UI/UX A5删除治理用例
|
||||
|
||||
- `TC-UIUX-A5-DELETE-001`:活动通道组引用通道时打开删除确认层;真实预检返回引用数量、组名和优先级,`allowedActions`为空,前后端均禁止删除且通道状态不变。
|
||||
- `TC-UIUX-A5-DELETE-002`:签名仍被未删除模板、引流信息或未结束报备任务引用;运营端和客户端均显示租户内依赖摘要并禁止删除,客户端不能读取其他企业对象。
|
||||
- `TC-UIUX-A5-DELETE-003`:模板存在未结束发送或批量任务时阻断;无依赖模板填写原因后逻辑删除,返回操作单号,PostgreSQL状态为`deleted`且OperationLog包含原因、依赖、影响和幂等键。
|
||||
- `TC-UIUX-A5-DELETE-004`:相同删除幂等键重试返回相同操作单号且不重复审计;旧版本并发提交返回409并要求重新预检;旧删除接口不能绕过治理规则。
|
||||
- `TC-UIUX-A5-DELETE-005`:在1440×900、1366×768、768×1024、390×844和375×667打开依赖确认层;对象、依赖、影响和底部操作可滚动到达,无页面级横向溢出,控制台无error/warn。
|
||||
|
||||
## 2026-07-21 UI/UX A6人工充值治理用例
|
||||
|
||||
- `TC-UIUX-A6-RECHARGE-001`:两个人工充值入口分别输入金额和备注后点击取消、右上角关闭,再次打开;金额、备注、预检和幂等状态均为空,不产生订单、流水或余额变化。
|
||||
- `TC-UIUX-A6-RECHARGE-002`:输入正数或负数金额进入核对;后端返回企业名称/编码/ID、当前余额、方向、变动和预计余额,页面完整展示,返回修改不入账。
|
||||
- `TC-UIUX-A6-RECHARGE-003`:最终确认后只生成一个RechargeOrder、一个AccountTransaction和一个OperationLog,账户余额等于前余额加变动金额;响应显示订单号、后余额和操作单号,操作者来自当前会话。
|
||||
- `TC-UIUX-A6-RECHARGE-004`:相同幂等键并发或重试同一请求返回相同订单与操作单且`replayed=true`;键用于不同企业/金额返回409。预检后账户版本变化,旧确认请求返回409且不产生部分数据。
|
||||
- `TC-UIUX-A6-RECHARGE-005`:在1440×900、1366×768、768×1024、390×844和375×667打开核对层;资金摘要、返回和最终确认可滚动到达,控制台无error/warn。
|
||||
|
||||
## 2026-07-22 UI/UX A7公共Dialog验收用例
|
||||
|
||||
| 用例ID | 场景 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| A7-DIALOG-001 | 打开代表Dialog | 焦点进入弹窗;dialog由可见标题命名;背景具备inert/aria-hidden;body滚动锁定;遮罩不可聚焦 |
|
||||
| A7-DIALOG-002 | 主Dialog键盘循环 | 最后一个可用控件按Tab回到第一个,首控件按Shift+Tab回到最后一个,焦点不进入背景 |
|
||||
| A7-DIALOG-003 | dirty表单按Escape/取消/关闭/遮罩 | 四种入口均打开具名alertdialog,不销毁已输入草稿,父Dialog不可交互 |
|
||||
| A7-DIALOG-004 | dirty确认层键盘与返回 | 确认层Tab循环;按Escape或继续编辑后确认层关闭、草稿保留、焦点返回原字段 |
|
||||
| A7-DIALOG-005 | 明确放弃 | 两层弹窗关闭、草稿销毁、背景隔离和滚动锁恢复、焦点返回原触发按钮 |
|
||||
| A7-DIALOG-006 | 两端五视口视觉回归 | 运营与客户端真实登录态页面在1440×900、1366×768、768×1024、390×844、375×667无裁切或横向溢出,控制台无业务error/warn |
|
||||
|
||||
## 2026-07-22 UI/UX A2/A3收口用例
|
||||
|
||||
| 用例ID | 场景 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| A23-UPLOAD-001 | 客户端企业认证页选择文件 | 请求进入`/api/client/files/upload`;FileObject租户来自会话;MinIO对象字节数与上传文件一致并可回读 |
|
||||
| A23-UPLOAD-002 | 伪造租户、非法用途/目录、跨租户下载 | 伪造租户不生效;非法用途或目录在对象存储写入前拒绝;跨租户文件返回404 |
|
||||
| A23-UPLOAD-003 | 上传成功五视口反馈 | 1440×900、1366×768、768×1024、390×844、375×667均显示完整文件名;页面无横向溢出,步骤一可见,省市选择不裁切 |
|
||||
| A23-LOG-001 | 客户端真实筛选导出 | 页面显示完成数量、操作单号和下载入口;重复点击期间按钮锁定,失败时原地重试且不跳门户 |
|
||||
| A23-LOG-002 | 客户端CSV字段安全 | 表头仅六列;任意OperationLog详情、来源IP、供应商或内部字段均不得进入导出内容 |
|
||||
| A23-REVIEW-001 | 签名/模板点击通过 | 首先显示对象名、唯一ID、企业、应用、资格检查和影响;没有最终确认不得改变pending状态或写AuditRecord |
|
||||
| A23-REVIEW-002 | 审核并发与幂等 | 使用pending+updatedAt条件更新;同键重放返回同一操作单且仅一条审计;版本变化返回冲突 |
|
||||
| A23-REVIEW-003 | 审核确认层五视口 | 签名确认层截图覆盖五视口;模板覆盖桌面截图和390px DOM尺寸测量;内容与按钮可达、无横向溢出、console无业务error/warn |
|
||||
|
||||
@@ -2060,3 +2060,107 @@ git diff --check
|
||||
- 生产运行提交为`f02c33cbb7248410c189f75502d6e497fff7b355`。`cmpp-gateway`、`cmpp-api`、Nginx、PostgreSQL、Redis和MinIO均active,`12026/17890/8090/3000/9000`监听;API/Gateway health、Redis PONG、PostgreSQL readiness、外部首页、运营登录、客户端登录、API health和Swagger JSON均通过,公网CMPP `8.160.169.106:17890`可连接。根目录/API生产依赖audit均为0漏洞。
|
||||
- 两个active上游通道均为`connected/currentConnections=1`,权威TPS配置已恢复;Redis Stream `gateway.submit.commands` consumer group为`pending=0、lag=0`。部署后API/Gateway error级日志均为0。未发送或重投真实短信,未执行充值、审核、删除或生产业务数据修改。
|
||||
- 本地Browser五视口LG2-P0-01证据已通过;生产浏览器烟测被企业网络策略禁止访问该公网HTTP地址,未使用其他浏览器或自动化方式绕过。生产外部HTTP/TCP、真实服务、数据库、Redis和运行产物均已核验,但本次不把生产浏览器交互标记为通过。
|
||||
|
||||
## 2026-07-21 北向连接名额、历史回执与报表回填复查(未提交、未部署)
|
||||
|
||||
- 生产只读复查确认北向普通/长短信无 `SUBMIT_RESP` 的直接阻塞发生在企业应用下游接入侧:账号 `695829` 的旧连接登记为 `connected`,但 TCP 和 Redis 均无对应在线连接;`lastHeartbeatAt=NULL` 使既有 `lastHeartbeatAt < cutoff` 条件永远不成立,陈旧记录持续占用 `cmppMaxConnections`,新连接在业务层登记时被关闭,Submit 因而未被 Gateway 读取。经用户授权只清理该条已确认无真实连接的陈旧登记,未修改账号、应用或短信数据,连接名额恢复为 0;仍须部署本轮代码后重新做生产普通、长短信和多号码端到端验证。
|
||||
- `markTimedOutDownstreamConnections` 已增加安全的 NULL 心跳清理:仅当 `lastHeartbeatAt IS NULL` 且 `connectedAt` 也早于超时窗口时才删除,避免误清刚建立但首个心跳尚未到达的连接。回归测试先在旧实现上失败,修复后 `sms-config.service.spec.ts` 46 项全部通过。
|
||||
- 生产历史数据只读核对找到 3 条“已有唯一 DELIVRD、主记录仍 submitted”的旧记录,均为同一上游账号复用两个通道时历史回执 `channelId` 归属反转;2026-07-21 新回执已按唯一提交记录正确归属并聚合,说明当前实时匹配代码已生效,遗留缺口是旧数据回填而不是继续发生的实时抢占。
|
||||
- 新增 migration `20260721150000_backfill_misattributed_delivery_receipts`:仅在“同一内部消息、同一 Gateway Msg_Id、同一目的号码恰好只有一条提交记录”时修正回执通道并把 DELIVRD 聚合到短信主记录;零匹配或多匹配保持原样,避免猜测修复。迁移可重复执行;本地真实 PostgreSQL 构造同账号双通道错归属样本后应用及重放均通过,主记录变为 `delivered`,回执状态、原始码/文本、到达时间、通道和通道消息号一致。
|
||||
- 在上述真实样本上执行既有 T-4 至 T-1 报表重算,发送数/成功数/失败数为 `1/1/0`,利润成功数为 1,质量成功率为 100%,平均到达时长为 5000ms;证明 7 月 18、19 日历史报表缺口应按“先迁移回填主记录,再重算对应日期”处理。生产尚未应用 migration 或重算,历史页面当前仍会保持旧结果。
|
||||
- HTTP 正向发送旧结论经 2026-07-21 较新生产证据纠正:仅传 `mobile/content` 的合法请求已返回 202、自动关联正确签名和模板、返回 MessageId,并通过真实路由和计费;不能再归类为当前未修复。重复用户名生产接口也已返回 409。黑名单 P2002 映射代码原已存在,本轮补充全局/企业及逻辑删除占用的 `BLACKLIST_DUPLICATE` 409 回归测试。
|
||||
- 完整验证:API 21 suites/240 tests、Gateway `go test ./...`、API build、前端 TypeScript/Vite build、Prisma generate/validate/status(58 条 migration,schema 最新)均通过;前端仅有既有约 1.9MB chunk warning,Jest 仍需 `--forceExit` 结束既有异步句柄。`git diff --check` 在文档收尾后另行复核。
|
||||
- `npm run verify:phase8` 的契约和 Gateway 阶段通过,但共享 Redis 的 BullMQ 15,000 条/500 并发结果为 enqueue 3577.89 TPS、端到端 431.29 TPS,未达到 500 TPS,因此完整命令未通过;临时隔离 Redis 同参数端到端为 567.37 TPS 并通过。该项按共享环境性能阻塞记录,不把共享 Redis 结果标为通过。
|
||||
- 本地正式链路启动真实 NestJS API、PostgreSQL、Redis 和 Go Gateway:API `/api/health`、Gateway `/health` 均为 `ok`,Redis `PONG`,Gateway 监听 `127.0.0.1:7890` 且恢复候选数为 0;验收后精确停止本轮 3000/8090/7890 端口进程。未向生产号码发送短信,未部署、未提交、未 push。
|
||||
|
||||
## 2026-07-21 UI/UX A1双门户会话隔离与深链恢复(未提交、未部署)
|
||||
|
||||
- 修复`LG2-P1-01/02/03/05`:前端会话存储、DOM事件和BroadcastChannel按`admin/client`命名空间隔离;NestJS使用`cmpp_admin_session/cmpp_client_session`(安全Cookie环境使用对应`__Host-`名称),中间件按目标门户路径只读取对应Cookie;所有touch/lock/unlock/reauthenticate/logout/password接口改为门户专用路径。
|
||||
- 新增真实会话初始化边界和安全returnUrl:受保护路由先读取后端当前会话,未登录时保存同源、同门户白名单目标;登录后回原深链。当前会话接口只返回安全用户DTO和时序状态,不输出令牌。锁定刷新时暂停首次业务路由和运营看板请求,解锁后原URL重新挂载真实数据;当前标签已加载的路由不会被另一门户广播卸载。
|
||||
- 自动化:认证会话2 suites/10 tests、API TypeScript build、前端TypeScript/Vite build及`git diff --check`通过;项目没有前端lint/组件测试脚本,未虚报。前端仍有既有约1.90MB大chunk警告。本批无Prisma schema变更。
|
||||
- 真实链路:本地PostgreSQL建立隔离验收用户,Redis真实保存不透明会话;同一Cookie容器同时得到`cmpp_admin_session,cmpp_client_session`。锁定admin时client仍active,admin解锁轮换Cookie,client退出后admin当前会话仍返回`a1_admin`。
|
||||
- 浏览器:客户端13/13受保护路由逐条打开和刷新均保持目标URL及客户端身份,console error/warn为0;同浏览器双门户并存、客户端退出后运营端刷新继续有效。运营用户页在1440×900、1366×768、768×1024、390×844、375×667实际视口均显示真实用户且console为0;390×844锁定、刷新、解锁后仍在`/admin/users`且真实用户重新出现,锁定阶段console为0。证据在测试项目`平台LG_UIUX二轮走查证据/A1会话隔离-20260721/`。
|
||||
- 未修改生产业务数据,未发送短信、充值、审核、删除或报备;代码未提交、未push、未部署,生产仍运行旧会话实现。生产发布后旧共享Cookie需要重新登录,故台账暂记“待验证”。本地临时API由本会话启动,收尾时精确停止;PostgreSQL/Redis/既有前端进程不属于本会话,不停止。
|
||||
|
||||
## 2026-07-21 UI/UX A2客户端安全上传与日志导出(未提交、未部署)
|
||||
|
||||
- `LG2-P1-04`改为客户端专用上传/下载接口:租户从当前会话用户反查,不再信任`x-tenant-id`;企业认证、签名报备、引流报备用途和目录白名单由后端强制,跨企业下载返回404。前端客户端预览/下载也不再调用admin端点。
|
||||
- `LG2-P1-06`新增两端共用日志导出状态组件:提交中防重复、完成数量/截断提示/操作单号、CSV下载、失败原地重试,并按门户在`sessionStorage`仅保存瞬时恢复筛选。后端从PostgreSQL真实筛选导出;客户端租户由会话反查且CSV只保留时间、级别、模块、操作人、动作、资源ID,另有10000条上限和公式注入防护。
|
||||
- 自动化通过:文件/运营服务2 suites、24 tests,API TypeScript build,前端TypeScript/Vite build,Prisma validate/migrate status(本地58条、schema最新)和`git diff --check`。项目仍无前端lint/组件测试脚本;前端仍有既有约1.9MB chunk告警。
|
||||
- 真实链路:本地NestJS、PostgreSQL、Redis和MinIO在线;客户端真实验证码登录后,携带伪造租户头上传仍落当前企业`a2-local-tenant`,MinIO对象经客户端接口读回55字节。客户端日志导出真实返回安全6列表头、5条记录;Browser在1440×900点击导出显示3条和操作单号`2aa2521d-111d-48a6-87c2-27cdbcf680be`。本轮未完成console专项读取,不虚报console通过。
|
||||
- Browser上传因隐藏file input点击超时未完成页面级验收;1366×768、768×1024、390×844、375×667截图也尚未补齐,因此两项台账均保持“待验证”,不标记已通过。未改生产数据,未提交、未push、未部署;并行会话的短信配置、字典、回执migration及其文档未改动。
|
||||
|
||||
## 2026-07-21 UI/UX A3审核通过风险治理(未提交、未部署)
|
||||
|
||||
- 新增独立审核治理服务和`RiskAction`组件,签名/模板“通过”从直接终态调用改为“后端资格预检→对象/影响确认→提交中锁定→结构化结果”。签名预检覆盖应用绑定、公司/信用代码、法人、责任人/手机号和资质文件;模板覆盖内容、签名绑定及签名审核状态。
|
||||
- 后端从当前会话写审核人,使用`pending + updatedAt`条件更新和Serializable事务阻止并发覆盖;AuditRecord ID作为操作单号,幂等键随审计持久化,同键重试返回原结果。旧批准路由也统一进入治理服务。未修改并行会话占用的`sms-config.service.ts/spec.ts`。
|
||||
- 自动化通过:审核治理1 suite/5 tests、API TypeScript build、前端TypeScript/Vite build、Prisma validate及`git diff --check`。前端仍有既有约1.91MB chunk告警;Jest断言通过后仍需`--forceExit`结束既有异步句柄。
|
||||
- 真实本地API/PostgreSQL:完整签名预检为`approve,reject`且0阻断,首次批准写入操作单号`cmruefurl0002msyuftczvswt`并变为approved;相同幂等键重放返回同一单号和`replayed=true`。验收企业、应用、签名、审核记录、用户和日志已精确清理。
|
||||
- Browser实际打开运营登录页并读取验证码,但登录后受A1未提交会话链的“登录会话已失效”恢复提示阻断,未打开A3确认层;console专项返回空数组。未改生产数据。驳回统一协议、有限撤销/双人复核和五视口页面证据未完成,因此台账记“部分通过”,代码未提交、未push、未部署。
|
||||
|
||||
## 2026-07-21 定时短信自动派发与多实例幂等(未提交、未部署)
|
||||
|
||||
- 根因:到期任务此前只有 `POST /api/admin/send/scheduled/dispatch-due` 手工入口,API 启动后没有自动扫描;派发前也没有数据库条件更新认领,多实例同时扫描会重复冻结和入队。
|
||||
- API 启动后默认 1 秒首次扫描、每 5 秒继续扫描,可通过 `SMS_SCHEDULED_DISPATCH_SCAN_ENABLED` 和 `SMS_SCHEDULED_DISPATCH_SCAN_INTERVAL_MS` 配置;进程内重入保护避免同一实例扫描重叠。
|
||||
- 派发使用 `id + status (+ stale updatedAt)` 的 `updateMany` 条件更新原子认领。正常任务进入 `scheduled_dispatching`;超过默认 2 分钟的陈旧认领在 `scheduled_dispatching/scheduled_recovering` 间交替认领,保证多实例仅一个恢复者胜出。
|
||||
- 恢复时按 `tenantId + transactionType=frozen + relatedType=sms_batch_task + relatedId` 查询既有冻结流水,存在则不再冻结;BullMQ 继续使用消息记录 ID 作为 jobId。0 元任务在资源和余额检查通过后若 Redis 入队失败,也保留调度中状态等待恢复,不会被错误终结。
|
||||
- 回归测试先覆盖旧代码失败,再完成实现;`send-chain.service.spec.ts` 共 67/67 通过,新增自动启动扫描、并发扫描唯一认领、陈旧认领恢复不重复冻结和 0 元任务入队失败可恢复用例。API TypeScript build 通过。
|
||||
- 该 Jest 套件断言约 24 秒完成,但仍需 `--forceExit` 结束仓库既有 BullMQ/Redis 异步句柄;未把句柄问题标记为通过。本批没有 Prisma schema/migration 变化,尚未执行真实 PostgreSQL/Redis 双实例故障注入,留待本地链路总验收。
|
||||
- 代码和文档均未提交、未 push、未部署;生产仍需发布后验证无需手工接口即可自动派发到期任务。
|
||||
|
||||
## 2026-07-21 下游人工重投与Gateway提交异常恢复(未提交、未部署)
|
||||
|
||||
- 根因一:`requeueDownstreamDelivery` 原先先读取再普通 `update`,没有状态版本条件;并发请求可重复递增人工次数并多次调用 Gateway。若直接把认领结果写为 pending,Gateway 恢复扫描还可能与同步控制面调用形成双发窗口。
|
||||
- 下游人工重投改为 `id + status + updatedAt` 的 `updateMany` 原子认领,认领态为 `manual_requeueing`;Gateway pending 查询不会选中该状态。成功写出后进入 `awaiting_ack`,失败走既有明确状态机;超过默认 2 分钟的陈旧认领自动恢复为 pending,阈值可通过 `CMPP_DOWNSTREAM_MANUAL_REQUEUE_STALE_MS` 配置。
|
||||
- 根因二:Gateway提交异常虽然已有 pending→requeueing 抢占,但 Redis XADD 成功、数据库更新 requeued 失败时会永久停留 requeueing;直接重放又可能产生第二条 SubmitCommand。重复异常上报还会无条件把已解决记录重置为 pending。
|
||||
- 提交异常重入队现在使用 `gateway:submit:requeue:{deadLetterId}:{nextAttempt}` 稳定键和 Redis Lua,原子执行幂等检查、XADD 和 30 天 Stream ID 保存;陈旧 requeueing 由后台扫描抢占为 requeue_recovering,并复用同一键完成数据库落账。SubmitResult 可从 pending/requeueing/requeue_recovering/requeued 任一在途状态直接闭环 resolved,恢复不会覆盖 resolved;重复上报只更新失败详情,不回退处理状态。
|
||||
- 回归测试先在旧实现失败,修复后 `send-chain.service.spec.ts` 71/71 通过;新增重复异常上报不回退、陈旧提交重入队恢复、下游并发唯一认领和陈旧人工认领恢复用例。API TypeScript build 通过;Jest 仍需 `--forceExit` 结束仓库既有 BullMQ/Redis异步句柄。
|
||||
- 真实 Redis 验证:同一幂等键并发发布两次返回相同 Stream ID,Stream entry 数为 1,幂等键 TTL 为 2592000 秒;测试 Stream 和 key 已删除。
|
||||
- 真实 PostgreSQL 并发验证:两个请求在读取同一版本后同时重投,结果为 1 成功、1 冲突,Gateway 控制面仅调用 1 次,`manualRetryCount=1`,成功记录进入 awaiting_ack;陈旧 manual_requeueing 记录自动恢复为 pending。另以真实 PostgreSQL+Redis 验证陈旧提交异常恢复后 status=requeued、人工次数=1、重复幂等发布仍只有一条 Stream entry。所有本地临时企业、应用、投递、异常、日志、Stream 和 key 均已清理。
|
||||
- 本批无 Prisma schema 或 migration 变化,没有修改 Go Gateway。未发送短信,未修改生产数据;代码未提交、未 push、未部署。
|
||||
|
||||
## 2026-07-21 UI/UX A4报备生成资格、幂等与结果治理(未提交、未部署)
|
||||
|
||||
- `LG2-P1-15`后端新增`POST /api/admin/report-materials/batches/preflight`,逐资料/通道检查审核与待报备状态、应用启用、版本、路由、通道状态、字段配置和必填值;非法资料ID返回可读400,0可生成目标在创建批次前返回`REPORT_BATCH_NOT_ELIGIBLE`。
|
||||
- 生成接口要求幂等键,通过PostgreSQL advisory transaction lock认领操作;资料类型/ID/版本/应用/通道/运营商形成持久化业务键。相同请求重放返回原操作单和结果,不同范围复用键返回409;成功响应包含成功、跳过、失败分项及每项阻断原因。
|
||||
- 运营端列表由真实预检控制选择资格,明确显示“待补充”及首个阻断原因;生成前再次预检,确认层展示企业、应用、版本、预计通道、运营商和分项计数,提交中锁定,完成后显示批次与操作单号。
|
||||
- 自动化:`report-materials.service.spec.ts` 7/7、API TypeScript build、前端TypeScript/Vite build、Prisma validate及migrate status(本地58条、schema最新)均通过,`git diff --check`通过。API build首次被并行会话尚未完成的`send-chain.service.ts`变量错误阻断,未修改该文件;对方完成后收尾重试已通过。项目没有独立前端lint/组件测试脚本;前端仍有既有约1.91MB单chunk告警。
|
||||
- 真实API/PostgreSQL:临时已审核但未绑定应用的资料经真实管理员登录、待报备和预检接口返回`eligible=false`、0目标、“未绑定短信应用”;临时企业、用户、签名和日志清理后均为0。没有调用生成接口。
|
||||
- Browser只打开到确认层并取消:完整临时应用、路由、通道和字段显示1个可生成组合;1440×900、1366×768、768×1024、390×844、375×667均无横向溢出且弹窗在视口内,console error/warn为0。截图位于测试项目`平台LG_UIUX二轮走查证据/A4_LG2-P1-15_报备预检_*.png`;所有临时数据清理为0。
|
||||
- 未修改生产业务数据,未生成报备、发送短信、审核、充值或删除生产对象;代码未提交、未push、未部署,生产仍运行旧实现。
|
||||
|
||||
## 2026-07-21 UI/UX A5通道/签名/模板删除治理(未提交、未部署)
|
||||
|
||||
- `LG2-P1-20`新增统一`DeletionGovernanceService`、admin/client预检与删除接口及共享`DeleteRiskAction`。确认层展示对象名称/ID、活动依赖数量和对象摘要、阻断原因、影响范围、可恢复说明、原因、提交态及结构化操作结果;客户端查询由会话企业强制裁剪。
|
||||
- 后端对通道组/路由/活动连接/报备、签名关联模板/引流/报备、模板发送/批量任务做真实依赖检查。删除使用`updatedAt`乐观锁、Serializable事务、幂等键、逻辑删除和OperationLog操作单,旧通道删除及旧签名/模板删除状态入口统一委托治理服务。
|
||||
- 正向真实链路首次暴露`PrismaService.operationLog`代理属性不可配置导致事务客户端访问代理时报500;将属性改为可配置并新增`prisma.service.spec.ts`,修复后同一页面请求成功。真实PostgreSQL模板状态变为`deleted`并写入`governance.delete`日志、原因、依赖、影响和幂等键。
|
||||
- 自动化通过:`deletion-governance.service.spec.ts`与`prisma.service.spec.ts`共2 suites / 7 tests、API TypeScript build、前端TypeScript/Vite build、Prisma migrate status(本地58条、schema最新)和`git diff --check`。项目没有独立前端lint/组件测试脚本;前端仍有既有约1.91MB单chunk告警。
|
||||
- Browser真实页面:通道被活动组引用时,1440×900、1366×768、768×1024、390×844、375×667均展示1项引用和后端阻断原因,确认按钮断言disabled;无依赖模板填写原因后页面列表变空,数据库和审计同步落账。console error/warn为0。证据位于测试项目`平台LG_UIUX二轮走查证据/整改_A5_LG2-P1-20_20260721/`。
|
||||
- 本轮只写入并精确清理本地验收数据,未操作生产对象。代码未提交、未push、未部署;通用Dialog焦点/背景/dirty保护归A7,前端拆包与缓存归C阶段。
|
||||
|
||||
## 2026-07-21 UI/UX A6人工充值草稿、确认与幂等治理(未提交、未部署)
|
||||
|
||||
- `LG2-P1-21`将充值记录页和企业管理页两个入口收敛到共享`ManualRechargeDialog`。取消、右上角关闭和完成都会销毁金额、备注、预检、结果及幂等键;浏览器分别填写`123.45/88.88`和备注后取消/关闭,重开字段均为空。
|
||||
- 新增`POST /api/admin/billing/manual-recharges/preflight`,从真实企业账户返回版本、现金余额、授信、方向和预计余额。确认层显示企业名称/编码/ID、方向、当前余额、变动、预计余额及备注,提交期间防重复,成功显示订单号、余额、操作单和幂等重放状态。
|
||||
- 最终接口由当前会话注入操作者,要求8—128位幂等键和账户版本;advisory transaction lock串行同键请求,`updatedAt`条件更新阻止覆盖新余额。订单、余额增量、账户流水和OperationLog在Serializable事务中原子写入。
|
||||
- 真实API/PostgreSQL:本地临时账户10.0000元,经页面充值1.2345元后为11.2345元;订单、流水、审计各1条。相同幂等键再次调用真实API返回同订单、同操作单、`replayed=true`,三个计数仍各1。充值记录页和企业管理页均回读11.2345元。
|
||||
- Browser五视口1440×900、1366×768、768×1024、390×844、375×667通过;375短屏底部操作可达,console error/warn为0。截图和API日志位于测试项目`平台LG_UIUX二轮走查证据/整改_A6_LG2-P1-21_20260721/`。
|
||||
- 自动化通过:`billing.service.spec.ts` 1 suite / 11 tests、API TypeScript build、前端TypeScript/Vite build、Prisma migrate status(58条、schema最新)及`git diff --check`。项目没有独立前端lint/组件测试脚本;约1.92MB单chunk告警归C阶段性能项。
|
||||
- 仅修改并精确清理本地验收数据,未操作生产充值。代码未提交、未push、未部署;生产仍运行旧实现,通用Dialog焦点/背景隔离/dirty guard继续由A7处理。
|
||||
|
||||
## 2026-07-22 UI/UX A7公共Dialog整改(未提交、未部署)
|
||||
|
||||
- 修复`LG2-P1-23`:公共Modal新增初始焦点、顶层焦点栈、Tab/Shift+Tab约束、背景`inert`/`aria-hidden`、滚动锁、`aria-labelledby`和关闭后焦点恢复;遮罩由可聚焦button改为非交互div。
|
||||
- 新增dirty关闭协议:右上角、取消、Escape和遮罩统一进入具名`alertdialog`;父弹窗暂时inert,继续编辑保留草稿并恢复原字段焦点,明确放弃后才关闭并销毁。人工充值、删除风险动作和两端模板表单接入该协议。
|
||||
- 真实浏览器:本地NestJS/PostgreSQL/Redis会话分别登录运营与客户端;运营企业模板留存1440×900、1366×768、768×1024、390×844、375×667五张截图,客户端模板在1440×900和390×844完成DOM/键盘实测。五视口无横向溢出,主弹窗与确认层焦点循环、草稿保留、背景恢复、触发按钮焦点恢复均通过;两端console error/warn为空。
|
||||
- 验证:前端TypeScript/Vite build、API TypeScript build、Prisma validate/migrate status和`git diff --check`通过。项目无独立前端lint/组件/axe脚本,未虚报;构建仍有既有约1.92MB单chunk告警。
|
||||
- 未创建模板、未执行审核/删除/充值/发送或其他生产业务写入;本地临时用户、企业、日志和Redis会话已精确清理。代码未提交、未push、未部署,生产仍为旧Dialog实现。
|
||||
|
||||
## 2026-07-22 UI/UX A2/A3收口(未提交、未部署)
|
||||
|
||||
- `LG2-P1-04`通过真实Browser文件选择完成客户端企业认证材料上传;PostgreSQL FileObject归属当前会话企业,MinIO对象回读93字节。复验发现并修复手机端文件名、步骤条和省市选择的卡片内裁切,五视口重新截图后文件名完整且页面无横向溢出。
|
||||
- `LG2-P1-06`在客户端系统日志页真实点击导出,五视口均显示5条和操作单号;独立客户端会话API再次导出7条,表头严格为`时间,级别,模块,操作人,动作,资源ID`,测试注入的详情/IP/供应商内部字段均未泄露。
|
||||
- `LG2-P1-14`在运营签名和模板审核页分别打开通过确认层,仅取消时数据库状态仍为pending且审计为0。真实API随后首次批准签名并用相同幂等键重放,两次返回同一操作单`cmrvlgtrq000gakyumhm1iuiv`,重放标志为true且只写一次审核。
|
||||
- 浏览器证据覆盖上传页和日志导出页五视口、签名确认层五视口截图,以及模板确认层1440×900截图和390×844 DOM尺寸测量;两端console error/warn为空。证据位于测试项目`平台LG_UIUX二轮走查证据/整改_A2_A3收口_20260722/`。
|
||||
- 自动化通过:files、operations、review-governance 3 suites / 31 tests;前端build、API build、Prisma validate/status(58条、schema最新)和`git diff --check`。前端仍有约1.92MB单chunk告警,项目无独立前端lint/组件/axe脚本。
|
||||
- 客户端日志列表仍展示内部详情/IP,继续归`LG2-P1-07`,未因导出安全而标记完成。临时数据、MinIO对象和Redis会话已清理;未触碰生产,未提交、未push、未部署。
|
||||
|
||||
Reference in New Issue
Block a user