Files
junhong_cmp_fiber/openspec/specs/external-integration/spec.md
break 370fd3e67f
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m49s
update
2026-09-03 09:28:28 +08:00

214 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# external-integration 当前行为
## Purpose
描述企业微信、支付渠道、Gateway 与运营商回调的当前失败、重试和幂等边界。
## Requirements
### Requirement: 企微审批状态
系统 SHALL 将审批状态按 0=提交中、1=审批中、2=已通过、3=已拒绝、4=已撤销、5=通过后撤销、6=已删除、7=提交失败、8=提交结果未知返回。
#### Scenario: 企微审批状态
- **GIVEN** 审批实例存在
- **WHEN** 查询审批
- **THEN** 返回数值状态和对应中文名称
### Requirement: 企微回调与补偿
系统 SHALL 对企业微信回调执行验签解密,并使回调、轮询和人工同步进入同一权威状态同步语义。
#### Scenario: 企微回调与补偿
- **GIVEN** 同一审批变化由多个同步来源到达
- **WHEN** 处理同步
- **THEN** 审批终态和业务终态至多生效一次
### Requirement: 外部失败边界
系统 SHALL 将第三方超时、渠道错误和无效响应转换为当前稳定的系统错误已接入外部交互日志的渠道同时保留脱敏结果。Gateway 同步轮询在已建立 pending 外部交互日志后无论查询成功、失败或任务上下文取消MUST 尝试将该尝试终结为公开终态;终结失败 MUST 作为任务失败返回或记录为可重试错误,不得静默忽略。
#### Scenario: 外部失败边界
- **GIVEN** 外部系统超时或返回失败
- **WHEN** 调用依赖该系统的操作
- **THEN** 客户端收到当前稳定错误;已接入外部交互日志的调用记录脱敏渠道结果
#### Scenario: Gateway 轮询查询失败
- **GIVEN** Gateway 同步轮询已建立 pending 外部交互日志且查询返回错误
- **WHEN** 轮询任务处理该错误
- **THEN** 系统将该日志终结为失败或结果未知的公开终态,并按既有策略重新入队
#### Scenario: Gateway 轮询终结日志失败
- **GIVEN** Gateway 同步轮询已建立 pending 外部交互日志且终结写入失败
- **WHEN** 轮询任务结束该次尝试
- **THEN** 系统记录并返回该终结失败,不得把该错误静默丢弃
### Requirement: 外部调用重试边界
系统 SHALL 仅对 Gateway 客户端超时、连接失败和 DNS 失败自动重试默认最多重试两次且每次重新签名HTTP 非 200、响应解析失败、Gateway 业务失败和调用方取消不重试。企业微信审批只有确认尚未调用提交接口的失败可释放后重试,提交结果未知时不得盲目重建审批。
#### Scenario: 外部写请求结果未知
- **GIVEN** 企业微信审批提交请求可能已到达渠道但本地未取得确定结果
- **WHEN** Worker 处理该失败
- **THEN** 系统保留提交结果未知状态且不自动创建第二张审批单
### Requirement: 富友调用超时兼容行为
系统 SHALL 保持当前富友预下单客户端未设置独立 HTTP 超时的兼容行为;调用可能持续等待底层连接结束,此行为作为当前缺陷记录而不在基线任务中修复。
#### Scenario: 富友端点不返回响应
- **GIVEN** 富友连接建立后持续不返回响应
- **WHEN** 系统发起预下单
- **THEN** 当前适配器没有自身超时门禁,调用结果保持未确定直到底层请求返回错误或响应
### Requirement: 运营商回调幂等
系统 SHALL 以渠道业务标识与标准化载荷生成的幂等键记录运营商实名及网络状态回调;重复回调只应用一次,字段冲突作为独立冲突事实保留且不得覆盖已确认状态。
#### Scenario: 重复运营商回调
- **GIVEN** 同一合法运营商回调已经成功应用
- **WHEN** 渠道再次发送相同业务事实
- **THEN** 系统返回渠道可接受响应且不重复推进卡状态
### Requirement: 富友主扫统一下单与订单查询
系统 SHALL 通过富友主扫统一下单创建微信二维码支付并返回 `qr_code` 二维码链接;系统 SHALL 通过富友订单查询按 `trans_stat` 将状态映射为已支付、已关闭、待支付或未知,未知状态 MUST 保持待恢复。下单与查询失败 MUST 映射为项目稳定错误,已接入外部交互日志的调用保留脱敏结果。
#### Scenario: 主扫下单成功返回二维码链接
- **GIVEN** 富友支付配置完整
- **WHEN** 系统发起主扫统一下单且渠道返回成功
- **THEN** 系统返回 `qr_code` 作为支付链接,业务以该链接生成二维码
#### Scenario: 查询映射支付成功
- **WHEN** 富友订单查询返回 `trans_stat=SUCCESS`
- **THEN** 系统将状态映射为已支付并取得渠道交易号、金额与支付时间
#### Scenario: 查询映射已关闭
- **WHEN** 富友订单查询返回 `trans_stat``PAYERROR``CLOSED``REVOKED`
- **THEN** 系统将状态映射为已关闭
#### Scenario: 查询映射待支付
- **WHEN** 富友订单查询返回 `trans_stat``USERPAYING``NOTPAY`
- **THEN** 系统将状态映射为待支付
#### Scenario: 查询状态未知保持待恢复
- **WHEN** 富友订单查询返回系统错误、找不到交易或无法识别的 `trans_stat`
- **THEN** 系统将状态映射为未知并保持本地支付单待恢复,不得据此确认收款或关闭订单
#### Scenario: 下单失败返回稳定错误
- **WHEN** 富友主扫统一下单返回失败或请求结果未知
- **THEN** 系统返回项目稳定错误且已接入外部交互日志的调用记录脱敏结果
### Requirement: 高频轮询外部交互日志保留边界
系统 SHALL 不为成功且无业务变化的自动轮询逐次创建 `tb_integration_log`。支付、审批、入站回调及其他依赖外部交互记录实现可靠幂等、结果恢复或渠道裁决的调用 MUST 继续持久化其外部交互记录。轮询外部调用失败、响应无效或结果未知时,系统 MUST 持久化可调查的失败或恢复记录。
#### Scenario: Gateway 轮询成功且状态未变化
- **GIVEN** 一次 Gateway 自动轮询成功,且结果没有引起业务事实变化
- **WHEN** 系统完成该轮询
- **THEN** 系统不创建该次轮询的 Integration Log
#### Scenario: Gateway 轮询调用失败
- **WHEN** 一次 Gateway 自动轮询超时、连接失败或返回无效响应
- **THEN** 系统创建可调查的失败或恢复记录,并按既有策略处理后续轮询
#### Scenario: 入站支付回调
- **GIVEN** 支付渠道发送入站回调
- **WHEN** 系统处理该回调
- **THEN** 系统继续使用可靠持久化的外部交互记录保证既有幂等和恢复语义
### Requirement: 外部交互日志归档与留存受控执行
系统 SHALL 在审计归档与日留存任务总开关关闭时停止 Integration Log 的日归档和日留存处理,并安全跳过已入队的相关任务。总开关开启后,每次相关任务执行 MUST 至多处理一个已结束的上海自然日,且必须继续满足既有归档校验和 pending 记录保留规则。
#### Scenario: Integration Log 归档任务被停用
- **GIVEN** 审计归档与日留存任务总开关关闭
- **WHEN** Integration Log 日归档或日留存任务被调度或消费
- **THEN** 系统不扫描、归档、删除或更新在线 Integration Log
#### Scenario: Integration Log 积压受控推进
- **GIVEN** 任务总开关开启且存在多个满足既有归档条件的日期
- **WHEN** 系统执行一次 Integration Log 归档或留存任务
- **THEN** 系统仅处理一个自然日,并继续保留未处理日期以供后续执行
### Requirement: 外部交互日志逐日物理留存
系统 SHALL 以 Asia/Shanghai 已结束的自然日为单位物理留存 `tb_integration_log`。删除一条已终态在线外部交互日志前,系统 MUST 已为其创建日生成可读取、可验证的归档对象和清单;该归档 MUST 包含该日当时全部外部交互日志,并验证日期范围、记录数量和校验摘要可作为恢复凭证。`pending` 记录 MUST 保留在线,不得被物理删除,且不得阻断同日已终态记录或更晚日期的归档、验证与物理清理。每次执行 SHALL 至多处理一个满足归档校验条件的已结束自然日;某一来源的归档或校验失败时,系统 MUST 保留该来源当天及更晚日期的在线外部交互日志。
#### Scenario: 最终归档通过后清理在线外部交互日志
- **GIVEN** 某已结束自然日的外部交互归档对象和清单校验成功,且存在已终态在线外部交互日志
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日已终态的在线外部交互日志并保留归档对象
#### Scenario: 存在待完成外部交互日志
- **GIVEN** 某归档日仍存在待完成的外部交互日志
- **WHEN** 日留存任务尝试处理该日期
- **THEN** 系统保留该 pending 日志,但不得停止同日终态记录和更晚日期的清理
#### Scenario: 含 pending 的日期清理终态日志
- **GIVEN** 某已结束自然日同时存在已终态和 `pending` 的外部交互日志,且该日归档对象和清单校验成功
- **WHEN** 日留存任务处理该日期
- **THEN** 系统分批删除该日已终态的在线外部交互日志,保留 `pending` 记录,并保留归档对象
#### Scenario: 历史积压按日期受控推进
- **GIVEN** 存在多个昨天及更早日期尚未完成物理清理,且这些日期各自存在满足归档校验条件的已终态外部交互日志
- **WHEN** 日留存任务执行
- **THEN** 系统仅处理最早的一个符合条件日期,并保留其余日期供后续执行
#### Scenario: pending 不阻断后续日期
- **GIVEN** 较早归档日只剩 `pending` 外部交互日志,且更晚归档日存在满足归档校验条件的已终态外部交互日志
- **WHEN** 日留存任务执行
- **THEN** 系统在后续日留存任务中继续处理更晚归档日的已终态外部交互日志,不因较早日期的 pending 停止日期推进
#### Scenario: pending 后续终结
- **GIVEN** 某归档日的 pending 外部交互日志在该日首次留存后进入公开终态
- **WHEN** 后续日留存任务执行
- **THEN** 系统为包含该记录的当前在线集合重新验证归档,并删除该已终态记录
#### Scenario: 归档或校验失败保留同来源后续数据
- **GIVEN** 某归档日的外部交互归档缺失、归档校验失败或清单与在线数据不一致
- **WHEN** 日留存任务处理该日期
- **THEN** 系统不删除该日及更晚日期的在线外部交互日志,并将失败信息写入独立日留存日志
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
### 企业微信审批
`GET /api/admin/wecom/applications`(查询企业微信应用配置);`POST /api/admin/wecom/applications`(创建或更新企业微信应用配置);`PUT /api/admin/wecom/applications/{id}/default-creator`(保存企业微信应用默认审批发起人);`GET /api/admin/wecom/applications/{id}/members`(分页查询企业微信应用可见成员);`POST /api/admin/wecom/applications/{id}/members/sync`(同步企业微信应用可见成员);`POST /api/admin/wecom/applications/{id}/templates/inspect`(读取企业微信审批模板控件);`POST /api/admin/wecom/applications/{id}/test`(测试企业微信应用连接);`GET /api/admin/wecom/scenes`(分页查询企业微信审批场景配置);`PUT /api/admin/wecom/scenes/{business_type}`(保存并校验企业微信审批场景模板映射);`GET /api/admin/wecom/scenes/{business_type}/fields`(查询企业微信审批场景可映射字段)。账号与企业微信成员的绑定属于账号身份能力。
### 企业微信审批回调
`GET /api/callback/wecom/approval/{application_id}`(验证企业微信审批回调地址);`POST /api/callback/wecom/approval/{application_id}`(接收企业微信审批状态变化回调)。
### 微信支付配置管理
`GET /api/admin/wechat-configs`(获取支付配置列表);`POST /api/admin/wechat-configs`(创建支付配置);`DELETE /api/admin/wechat-configs/{id}`(删除支付配置);`GET /api/admin/wechat-configs/{id}`(获取支付配置详情);`PUT /api/admin/wechat-configs/{id}`(更新支付配置);`POST /api/admin/wechat-configs/{id}/activate`(激活支付配置);`POST /api/admin/wechat-configs/{id}/deactivate`(停用支付配置);`GET /api/admin/wechat-configs/active`(获取当前生效的支付配置)。
### 对象存储
`POST /api/admin/storage/batch-download-urls`(批量获取文件下载预签名 URL`POST /api/admin/storage/upload-url`(获取文件上传预签名 URL
### 运营商回调
`POST /api/callback/carriers/cmcc/realname`(移动实名结果回调);`POST /api/callback/carriers/ctcc/realname`(电信实名结果回调);`POST /api/callback/carriers/cucc/realname`(联通实名结果回调);`POST /api/callback/carriers/cucc/realname/remove`(联通解除实名回调)。
### 运营商管理
`GET /api/admin/carriers`(运营商列表);`POST /api/admin/carriers`(创建运营商);`DELETE /api/admin/carriers/{id}`(删除运营商);`GET /api/admin/carriers/{id}`(获取运营商详情);`PUT /api/admin/carriers/{id}`(更新运营商);`PUT /api/admin/carriers/{id}/status`(更新运营商状态)。