feat: harden platform workflows and UI governance
This commit is contained in:
@@ -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或嵌套内部字段。
|
||||
- 签名和模板审核通过/驳回必须共用资格预检、状态版本、幂等键、事务审计和结构化结果协议。页面必须在最终决定前显示对象唯一标识、资格和影响范围;取消确认不得改变状态或写审计。
|
||||
|
||||
Reference in New Issue
Block a user