更新一下

This commit is contained in:
2026-07-21 15:26:07 +09:00
parent 2823ff13bf
commit 4902a02c87
32 changed files with 4138 additions and 294 deletions

View File

@@ -14,10 +14,10 @@
6. 回调是主通道,每 2 分钟查询审批详情作为待审批单兜底。
7. 回调与轮询必须进入同一个状态同步用例,业务终态处理只能执行一次。
8. 企微模板 ID 和控件 ID 都可能因管理员编辑模板而变化,必须使用稳定业务场景码、不可变模板版本和控件映射快照。
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application旧业务单级通过、驳回、退回和确认入账接口下线不保留两套审批入口。
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application旧业务单级通过、驳回、退回、重新提交和确认入账接口下线,不保留两套审批入口。
10. 系统无法从旧模板 ID 自动发现编辑后生成的新模板 ID模板变更必须先暂停业务场景再发布新映射并原子切换避免继续向旧模板提交。
11. 退款仍为财务人工退款,本系统不调用微信、支付宝等支付渠道退款接口;只有代理钱包支付订单自动回溯原扣款代理主钱包,不向个人客户或资产钱包自动回款。
12. 系统账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`管理端只查看状态和强制解绑
12. 平台账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`代理账号不绑定企微,统一使用部署配置中的固定企微账号代提交。两类路径都独立保存真实业务提交人
## 二、系统边界
@@ -45,7 +45,7 @@
```mermaid
sequenceDiagram
actor User as 平台员工
actor User as 业务提交人
participant API as Refund/Recharge Application
participant DB as PostgreSQL
participant Outbox as Outbox
@@ -60,7 +60,7 @@ sequenceDiagram
API->>Outbox: 同事务写 SubmissionRequested
API-->>User: 返回业务单和提交中状态
Outbox->>Worker: 投递提交任务
Worker->>WeCom: 上传附件、applyevent
Worker->>WeCom: 按账号类型选择企微身份并applyevent
WeCom-->>Worker: sp_no
Worker->>DB: 保存 sp_no状态改为 pending
@@ -87,6 +87,7 @@ wecom:
callback_encoding_aes_key: ""
callback_path: "/api/callback/wecom/approval"
account_binding_redirect_url: "https://后台域名/api/callback/wecom/account-binding"
agent_approval_creator_userid: ""
approval_poll_interval: 2m
template_verify_interval: 10m
request_timeout: 10s
@@ -101,14 +102,15 @@ JUNHONG_WECOM_AGENT_SECRET
JUNHONG_WECOM_CALLBACK_TOKEN
JUNHONG_WECOM_CALLBACK_ENCODING_AES_KEY
JUNHONG_WECOM_ACCOUNT_BINDING_REDIRECT_URL
JUNHONG_WECOM_AGENT_APPROVAL_CREATOR_USERID
```
后台只能查询“是否已配置、最近连通时间、最近错误”,不能返回 Secret、TokenEncodingAESKey。
后台只能查询“是否已配置、最近连通时间、最近错误”以及代理固定代提交成员是否就绪和成员显示名,不能返回 Secret、TokenEncodingAESKey 或原始固定 `userid`
企微自建应用还必须配置:
- Web 登录回调可信域名与 `account_binding_redirect_url` 域名一致。
- 应用可见范围覆盖需要发起退款或线下充值审批的平台员工,否则扫码时企微会提示无权限
- 应用可见范围覆盖需要本人发起审批的平台/超级管理员以及代理固定代提交成员;代理业务账号本身不要求成为企微成员
- `CorpID``AgentID`、用于换取身份的 Access Token 必须属于同一个自建应用配置。
用户提供的 demo 中已经出现完整密钥正式接入前必须在企业微信后台轮换并禁止将新值写入代码、Markdown、日志或审计快照。
@@ -277,15 +279,15 @@ shop_name, recharge_no, amount, remark, attachment, submitter
提交审批前,如果 `last_verified_at` 超过验证间隔Worker 先同步验证一次。验证失败不调用 `applyevent`
## 六、内部账号与企微成员绑定
## 六、平台账号绑定、代理固定代提交与真实业务提交人
创建人必须有明确的企微 `userid`,不使用固定手机号冒充所有申请人,也不要求运营人员进入企微后台查找成员 ID
企微发起身份按账号类型解析:平台账号和超级管理员必须有本人企微 `userid`;代理账号发起退款时使用部署配置中的固定企微成员 `agent_approval_creator_userid`。两类路径都把当前登录账号作为真实业务提交人写入业务快照和模板 `submitter` 字段
### 6.1 绑定流程
```mermaid
sequenceDiagram
actor User as 已登录平台员
actor User as 已登录平台/超级管理
participant FE as 管理后台
participant API as WeComIdentity Application
participant Redis as Redis绑定会话
@@ -351,7 +353,7 @@ value:
created_at
```
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话。
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话;代理账号不提供绑定会话
- `state` 使用密码学安全随机数,回调时通过 Lua 原子读取并删除;后续成功或失败结果写入独立的 `session_id` 会话。
- 回调不接受前端传入的 `account_id`,绑定目标只能来自服务端会话。
- 同一账号再次创建会话时,旧会话立即失效。
@@ -377,7 +379,7 @@ CREATE TABLE tb_account_wecom_mapping (
);
```
- 平台员创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态。
- 平台/超级管理员创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态;代理创建退款不检查个人绑定,改用固定代提交身份
- `account_id``wecom_userid` 都是一对一唯一;企微成员已绑定其他账号时拒绝覆盖,必须先由超级管理员解除旧绑定。
- 重新绑定同一账号必须再次扫码,成功后在事务内替换旧身份并记录前后值审计。
- 自助解绑不影响历史审批;历史实例继续使用提交时保存的 `creator_wecom_userid` 快照,新审批在重新绑定前禁止创建。
@@ -387,6 +389,20 @@ CREATE TABLE tb_account_wecom_mapping (
普通运营界面不提供手工录入 `userid`。超级管理员仅能查看、强制解绑并要求员工重新扫码,不提供直接改写成员 ID 的入口。
代理固定代提交身份只通过部署配置维护。其 `userid` 必须属于当前 CorpID、处于启用状态且在自建应用可见范围内后台只返回是否就绪及成员显示名不返回或修改原始 `userid`。固定身份未配置或失效时,代理新申请必须在业务单、审批实例和 Outbox 落库前拒绝。固定身份变更只影响新审批,历史实例保留原发起身份快照。
### 6.4 权限边界
- 连接状态、场景状态、模板版本、模板读取/发布、场景暂停/恢复仅超级管理员可访问。
- 平台账号只能查询、重新绑定和解绑自己的企微身份;绑定列表和强制解绑仅超级管理员可访问。
- 审批运行列表、详情和“立即同步”仅超级管理员或具备独立“企微审批运营”权限的平台账号访问。
- “企微审批运营”不包含场景/模板维护、账号绑定管理和提交结果未知恢复。
- 提交结果未知恢复仅超级管理员或具备独立“企微审批异常恢复”权限的平台账号访问。
- 代理不访问企微配置、绑定和审批运行接口,只能按现有店铺层级数据范围查看退款详情中的只读审批摘要。
- 所有接口均执行后端鉴权,前端隐藏按钮或路由不构成权限控制。
审批内容继续按主体最小化投影:代理只读取真实业务提交人、审批状态、状态时间、业务处理结果,以及其业务范围内的退款资料和业务凭证;企微审批人、内部意见和审批人上传附件只允许具备对应业务查看权限的平台账号或超级管理员读取。审批详情、附件解析和导出必须共享同一权限判定,历史导出中的稳定附件引用也要在每次访问时重新校验当前权限。
## 七、审批实例
```sql
@@ -395,13 +411,13 @@ CREATE TABLE tb_wecom_approval_instance (
biz_type VARCHAR(64) NOT NULL,
biz_id BIGINT NOT NULL,
biz_no VARCHAR(64) NOT NULL DEFAULT '',
round_no INT NOT NULL DEFAULT 1,
scene_code VARCHAR(64) NOT NULL,
template_version_id BIGINT NOT NULL,
template_id_snapshot VARCHAR(128) NOT NULL,
control_mapping_snapshot JSONB NOT NULL,
creator_account_id BIGINT NOT NULL,
creator_wecom_userid VARCHAR(128) NOT NULL,
creator_identity_source VARCHAR(32) NOT NULL,
sp_no VARCHAR(128),
status INT NOT NULL DEFAULT 0,
submitted_snapshot JSONB NOT NULL,
@@ -418,7 +434,7 @@ CREATE TABLE tb_wecom_approval_instance (
version BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (biz_type, biz_id, round_no)
UNIQUE (biz_type, biz_id)
);
CREATE UNIQUE INDEX uq_wecom_approval_sp_no
@@ -440,7 +456,7 @@ CREATE UNIQUE INDEX uq_wecom_approval_sp_no
| 7 | 提交失败 | 明确未创建审批 |
| 8 | 提交结果未知 | 请求超时,无法确认是否创建 |
`submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key不保存临时 `media_id`、签名 URL 或敏感密钥。
每条实例表示一张业务单唯一的审批申请,不使用 `round_no`。业务表通过 `approval_instance_id` 明确引用该申请,`(biz_type, biz_id)` 唯一约束保证退款或线下充值业务单不能产生第二条审批申请。`creator_account_id` 是真实业务提交人;`creator_wecom_userid` 是本次实际调用企微的成员快照,`creator_identity_source` 固定为 `self_binding``agent_proxy``submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key不保存临时 `media_id`、签名 URL 或敏感密钥。
## 八、DDD 设计
@@ -459,7 +475,7 @@ internal/
│ ├── sync_approval_status.go
│ ├── handle_callback.go
│ ├── poll_pending.go
│ └── resubmit_approval.go
│ └── recover_unknown_submission.go
├── domain/wecomidentity/
│ ├── binding.go 账号与企微成员一对一绑定规则
│ └── repository.go
@@ -512,7 +528,7 @@ Worker 处理:
5. 逐个调用 `media/upload` 获取临时 `media_id`
6. 按控件映射构建 `apply_data`
7. 使用模板审批人配置,固定 `use_template_approver=1`
8. 调用 `applyevent`
8. 按实例身份来源使用平台账号绑定的本人 `userid` 或代理固定代提交 `userid` 调用 `applyevent`;不得在 Worker 执行时重新按账号当前状态选择身份
9. 保存 `sp_no`,状态变为审批中并释放租约。
附件的本地对象存储 Key 是权威记录;企微 `media_id` 只是提交期间使用的临时值。
@@ -524,7 +540,7 @@ Worker 处理:
- 收到明确 `errcode != 0`:状态改为提交失败,可修正配置后重新提交。
- 建连失败且确认请求未发送:允许自动重试。
- 请求已发送但响应超时/连接中断:状态改为提交结果未知,不自动再次调用 `applyevent`
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新提交”或“绑定已有 sp_no”。
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新发送”或“绑定已有 sp_no”。重新发送只是同一审批申请的新技术尝试,不创建新的业务审批申请;旧尝试继续保留在 Integration Log。
## 十、回调和轮询
@@ -594,18 +610,12 @@ AND (last_polled_at IS NULL OR last_polled_at <= NOW() - INTERVAL '2 minutes')
|---|---|
| 通过 | 记录人工退款完成;代理钱包支付订单回溯原扣款代理主钱包;更新订单状态,随后异步佣金回扣和资产处理 |
| 驳回 | 退款状态改为已拒绝,保存审批详情摘要 |
| 撤销/删除 | 退款状态改为已撤销;允许申请人修改后创建下一轮审批 |
| 撤销/删除 | 非正常业务结果,记录异常、停止自动业务处理并进入人工处置;不开放正常重新提交入口 |
| 通过后撤销 | 已退款则不冲正,记录严重异常并通知财务;尚未执行则阻止退款 |
`POST /api/admin/refunds/{id}/approve``reject``return` 下线。
`POST /api/admin/refunds/{id}/approve``reject``return``resubmit` 全部下线。
重新申请使用:
```http
POST /api/admin/refunds/{id}/resubmit
```
它只允许已驳回、已撤销或已删除且业务尚未退款的申请,修改业务资料后创建 `round_no + 1` 的企微审批。
一张退款单只产生一条审批申请,正常业务结果只有同意或拒绝,任一结果产生后审批和退款单即同时完结。企微拒绝时保存拒绝原因和审批详情,原退款单进入不可编辑、不可重提的已拒绝终态。业务人员纠正问题后若仍需退款,必须重新调用 `POST /api/admin/refunds`,系统生成新的退款 ID、退款单号、业务快照、提交人快照和企微审批申请新单不继承旧审批节点、意见、附件、状态或企微发起身份。旧单只作为历史与审计事实保留。创建校验不把已拒绝退款视为活跃退款但存在其他审批中或处理未完成的退款时仍拒绝新建。撤销、删除和通过后撤销属于外部异常防御不作为自动放行新退款的依据。
审批通过后的业务 Worker 处理失败时,审批实例保持“已通过”,`business_process_result=failed`,由任务重试;前端明确区分“审批已通过”和“退款终态处理失败”。只有退款终态事务成功后才写 `business_processed_at`
@@ -633,10 +643,10 @@ POST /api/admin/refunds/{id}/resubmit
### 12.1 基础配置状态
```http
GET /api/admin/wecom/config/status
GET /api/admin/wecom/status
```
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误,不返回任何密钥
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误、代理固定代提交成员是否就绪及成员显示名,不返回任何密钥或原始固定 `userid`
### 12.2 审批场景状态
@@ -735,9 +745,10 @@ GET /api/admin/wecom/approvals
GET /api/admin/wecom/approvals/{id}
POST /api/admin/wecom/approvals/{id}/sync
POST /api/admin/wecom/approvals/{id}/bind-sp-no
POST /api/admin/wecom/approvals/{id}/confirm-not-created-and-resend
```
`bind-sp-no` 仅用于提交结果未知的人工恢复,必须写高等级审计日志。
`bind-sp-no` 请求包含 `sp_no` 和必填恢复原因;后端调用 `getapprovaldetail` 完成企业、模板、发起身份、业务场景、真实业务提交人和业务快照核对后才能绑定。`confirm-not-created-and-resend` 请求包含必填恢复原因,只在操作人已在企微确认未创建审批时重新发送同一审批申请。两者仅用于提交结果未知的人工恢复,必须二次确认并写高等级审计日志。
### 12.8 业务详情响应
@@ -752,7 +763,6 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
"status": 2,
"status_name": "已通过",
"template_name": "退款审批",
"round_no": 1,
"creator_name": "张三",
"approvers": [],
"submitted_at": "2026-07-15T10:00:00+08:00",
@@ -773,16 +783,18 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
### 13.2 业务详情
退款和充值详情增加只读“企业微信审批”区块:
退款和充值详情增加只读“企业微信审批”区块。平台/超级管理员在具备业务查看权限时展示
- 审批单号。
- 当前状态和状态更新时间。
- 模板名称和模板版本。
- 申请人。
- 真实业务提交人;代理代提交时可只读标识“企微由固定账号代提交”,但不能把固定账号展示为业务申请人。
- 审批人、审批结果、意见和时间线。
- 业务处理结果。
- “立即同步”图标按钮,仅触发 `getapprovaldetail`,不提供审批按钮。
代理退款详情只展示真实业务提交人、审批状态、状态时间和业务处理结果,以及其原有业务权限允许查看的退款资料和业务凭证;接口不向代理返回审批人、内部审批意见和审批人上传附件。
通过后撤销且业务已执行时使用红色异常状态条,明确显示“资金动作未自动冲正,需人工处理”。
### 13.3 企微配置页
@@ -793,7 +805,7 @@ Tab
1. **连接状态**:只显示配置状态和最近连通结果。
2. **审批模板**:按业务场景展示当前版本、模板 ID、验证状态和历史版本。
3. **账号绑定**:查看平台员绑定状态、企微名称和最近验证时间;不允许编辑 userid。
3. **账号绑定**:查看平台/超级管理员绑定状态、企微名称和最近验证时间;不允许编辑 userid;代理固定代提交账号只在连接状态中展示就绪状态和成员显示名
4. **异常审批**:提交未知、模板失效、终态业务处理失败、通过后撤销。
模板发布交互:
@@ -814,10 +826,10 @@ Tab
账号绑定交互:
- 当前用户个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间。
- 平台/超级管理员个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间;代理个人中心不展示企微绑定能力
- 点击“绑定企业微信”后打开官方企微二维码窗口,原页面显示 5 分钟倒计时。
- 绑定成功后窗口自动关闭,原页面刷新绑定状态;失败时显示企微返回的可操作原因。
- 创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页。
- 平台/超级管理员创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页;代理创建退款不检查个人绑定
- 管理员的账号绑定列表只提供筛选、查看和强制解绑;不提供 userid 输入框。
### 13.4 审批运行页
@@ -829,10 +841,10 @@ Tab
页面用于运行监控,不提供审批动作:
- 按业务类型、企微状态、提交状态和业务处理结果筛选。
- 查询 `sp_no`、业务单号、申请人和模板版本。
- 查询 `sp_no`、业务单号、真实业务提交人、企微发起身份来源和模板版本。
- 查看企微审批详情快照和本地业务处理结果。
- 对审批中记录执行“立即同步”。
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新提交”
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新发送”;后者仍属于同一审批申请的技术恢复
- 对业务处理失败记录查看错误和任务重试状态。
## 十四、Token、日志和限流
@@ -852,11 +864,11 @@ Tab
1. 在企微自建应用配置 Web 登录可信域名和平台员工可见范围。
2. 创建企微模板版本、账号绑定和审批实例表。
3. 发布企微身份 Adapter、账号绑定回调、审批回调、轮询 Worker 和业务终态用例。
4. 要求会发起审批的平台员工完成扫码绑定
4. 要求会发起审批的平台/超级管理员完成扫码绑定,并验证代理固定代提交成员可用
5. 发布并验证退款、线下充值两个模板映射版本。
6. 下线本地审批动作路由和前端按钮。
7. 存量已通过/已拒绝记录保留原业务审批快照,展示 `approval.source=legacy`
8. 存量待审批退款和线下充值仅在创建人已绑定企微时提交;未绑定记录进入迁移待处理列表。
8. 存量待审批记录按创建人类型处理:代理创建的退款使用固定代提交成员;平台/超级管理员创建的退款和线下充值仅在创建人已绑定企微时提交;未绑定或创建人缺失的记录进入迁移待处理列表。
9. 提交失败的存量记录进入异常审批页面,不允许继续走旧接口处理。
## 十六、人工验证
@@ -872,7 +884,8 @@ Tab
9. 回调和轮询同时到达时,只有一个处理器执行业务动作。
10. 附件始终保留本地对象存储 Key企微 media_id 失效不影响历史资料下载。
11. Secret、Token、EncodingAESKey 和附件内容不出现在 API、日志和审计详情中。
12. 已登录平台员扫码后自动获得企微 userid不需要人工查询或录入成员 ID。
12. 已登录平台/超级管理员扫码后自动获得本人企微 userid不需要人工查询或录入成员 ID;代理不展示绑定入口并使用固定账号代提交
13. 绑定 `state` 过期、重复回调、非企业成员扫码时均不会产生绑定。
14. 同一企微 userid 尝试绑定第二个系统账号时被拒绝,原绑定不被覆盖。
15. 自助解绑和管理员强制解绑不影响历史审批快照,但会阻止该账号发起新审批。
16. 同一份退款分别由平台账号和代理账号创建时,前者使用本人绑定身份,后者使用固定代提交身份;两份审批单都正确展示真实业务提交人,通知和审计不会把固定账号当成代理本人。