17 KiB
Context
本 Change 以 docs/7月迭代/七月迭代需求范围确认表.md 为业务范围,以 docs/7月迭代/物联网卡管系统-需求.csv 为原始来源,以 docs/7月迭代/企业微信审批官方接口调研.md 为企微能力依据。旧 Change 已完成部分公共能力,但其剩余任务存在漏项、错误口径和过度 DDD 迁移,不能继续作为实施顺序。
当前仓库已经具备可复用的通用审批实例、标准决策同步、公共 Outbox/Relay、异步任务、对象存储、站内通知、system_config、钱包流水和 Integration Log。本设计只补业务接线和确实缺失的企微 Adapter,不重建这些基础设施。
涉及方包括后端、前端、测试、部署人员,以及企业微信和 Gateway 配置负责人。当前仓库没有前端源码,因此本 Change 交付后端接口、OpenAPI 契约和联调门禁。
Goals / Non-Goals
Goals:
- 按已确认口径覆盖所有“本期做”需求,并核验所有“已完成,只联调/验收”需求。
- 让简单字段、筛选、配置和 Bug 保持简单,能够独立开发、独立验证、独立上线。
- 让退款和员工线下代充值真正通过企微模板审批闭环,同时保留金额、幂等和终态处理的现有安全边界。
- 冻结前后端需要的字段、接口、状态和错误语义,避免实现阶段再次扩张范围。
Non-Goals:
- 不继续执行旧 Change 中未被本 Change重新列出的任务。
- 不做代理在线扫码充值、原路退款、聚水潭、跨品类换货、分销码/佣金提现、自动限速、通用营销/ERP、本地审批流程设计器、未来钉钉适配或全局 Audit Event 专项。
- 不迁移未触碰的旧模块,不因为新增字段或筛选建立聚合、Repository 接口、事件消费者或历史投影。
- 不强制吊销已登录 C 端 Token;店铺限制只阻止新的登录流程。
- 本次执行不编写或补齐测试代码,不运行测试、构建、LSP、迁移、OpenAPI 生成或真实外部环境验收;实现收口到生产代码可联调状态。
Decisions
1. 新 Change 是后续实施的唯一契约
本 Change 校验通过后,complete-july-iteration-test-release 只作为历史完成证据归档,不再从其中领取任务。已完成代码不会回滚或重复实现;本 Change 的 tasks 会先做一次证据核验,再只实现真实缺口。
理由:旧 Change 的 task 勾选、proposal/spec 和仓库状态已经漂移,继续补丁式修订容易再次执行错误任务。
否决方案:在旧 Change 上继续增删 191 个任务。该方案无法可靠区分已完成公共能力、错误口径和本期真实需求。
2. 按纵向切片选择最小架构通道
| 需求切片 | 主通道 | 完整业务边界 | 明确不迁移范围 |
|---|---|---|---|
| #189、#181、#57 | 旧 Handler → Service → Store → Model |
对现有换货、退款、订单路径做局部修复和拦截;新建设备退款固化订单资产标识快照 | 不迁移换货、退款或订单模块,不兼容历史空快照 |
| #182、#44、#53 | 既有 Query 或旧 Store 查询 | 列表/详情字段、实名筛选、批量账号名称解析 | 不建聚合、历史投影或事件消费者 |
| #41、#62、#48 | 简单写 + 既有读取 | 店铺开关、实名策略、系统配置的更新和查询 | 不建领域模型,不重构认证/支付模块 |
| #188、#97、#33 | Application/既有通知 Adapter | 产生明确业务事件后向既有站内通知接线 | 不建营销平台或新通知中心 |
| #36、#42、#49 | 既有异步导入/导出 Infrastructure + 业务 Service | 文件校验、异步处理、结果文件和业务写入 | 不再建第二套任务、对象存储或进度平台 |
| #47 | 旧 IoT 卡 Service + Gateway Adapter | 固定档位校验、卡 ICCID 权限解析、一次 Gateway 调用 | 不提供设备限速,不通过设备绑定关系间接限速,不自动计算运营商策略,不迁移资产模块 |
| #34、#35 | 复杂写 Application/Domain | 申请快照、审批终态、充值入账或退款终结 | 不把企微 DTO/状态码写入资金领域 |
| #37 | Infrastructure Adapter + Application Port | 通讯录、模板、提交、回调、轮询和状态翻译 | 不建设本地审批流引擎或继续抽象未来渠道 |
| #40 | 旧 Service/Query | 下架套餐续费资格与可购列表过滤 | 不迁移套餐或订单全模块 |
依赖通过构造函数结构体字段注入。旧 Service 保持现有注入方式;企微 Adapter 实现现有 internal/application/approval Port,由 bootstrap 同时注入 API、回调和 Worker。新增 Handler 必须同步两个文档生成器。
3. API 只表达当前业务,不暴露通用平台
所有接口继续使用 Fiber、Validator、pkg/errors 和 {code,msg,data,timestamp} 统一响应;既有项目字段名为 msg,不引入另一套 message 字段。列表默认 20、最大 100,平台/代理数据权限沿用现有中间件与 GORM Callback。
关键契约如下:
| 场景 | 接口决定 |
|---|---|
| 店铺 C 端登录限制 | 店铺模型和管理接口增加 client_login_disabled,默认 false;复用店铺更新接口或增加单一语义 PATCH。C 端 verify-asset 在签发 asset token 前按资产 shop_id 拦截 |
| 企微账号绑定 | 账号列表/详情返回 wecom_corp_id、wecom_userid、wecom_name;管理员更新账号时选择通讯录成员,不提供个人扫码接口 |
| 企微通讯录 | 提供分页成员选择接口,支持姓名或 userid 搜索;只展示应用可见成员,不依赖手机号/邮箱 |
| 企微场景配置 | 管理员配置 business_type → template_id 及业务字段到控件 ID/类型/选项 key 的映射;模板必须先在企微后台创建 |
| 实名顺序 | 保持已约定的单资产 PATCH、卡批量 POST、设备批量 POST;枚举仅为 none/before_order/after_order,批量上限 500、全成全败 |
| 列表人员字段 | 退款、充值、换货返回提交人 ID/名称;审批人仅从已同步企微详情中的 userid 批量映射,映射不到返回空,不实时调用企微 |
| 设备退款资产标识 | 创建退款时从订单固化 asset_identifier;列表和详情只返回退款快照,不按 device_id 查询当前设备,不兼容历史空快照;单卡继续使用 ICCID 快照 |
| 批量订购 | CSV 只有资产标识列;请求额外选择一个套餐和一个支付方式,不选择代理;整批使用相同参数 |
| 导出 | 复用现有导出任务入口,通过六个 datasource code 区分;每类只输出系统现有且有稳定来源的字段,原始字段清单中当前无法提供的字段不伪造、不新增模型,也不阻断其他字段导出 |
| 支付方式 | system_config 分别保存卡和设备允许的支付方式;两类资产默认均启用 wallet/wechat/alipay,三项可独立取消但至少保留一项;C 端返回当前购包场景有效集合,创建订单时后端再次校验 |
| 设备批量分配 | 复用现有导入任务 API 和状态模型,CSV 单列设备标识;业务参数选择“代理”或“套餐系列”及目标 ID |
4. 店铺登录限制在资产令牌前拦截
client_auth.Service.VerifyAsset 解析出卡或设备后取得资产 shop_id,再读取店铺开关。无店铺/平台库存保持当前行为;开关关闭时返回统一禁止错误,不签发短期资产令牌,因此不会进入微信登录、客户创建或资产绑定。
不在每个 C 端业务接口重复查询店铺开关,也不吊销已有 Token。这严格对应“不能登录”而不是“立即踢下线”,并避免在本期引入 Token 版本和全局会话撤销。
5. 企微只做 Adapter,审批流程仍由企微模板拥有
系统按应用明文保存 Secret、回调 Token、EncodingAESKey、corp_id/agent_id 和一个从当前可见成员中选择的默认审批发起人;不要求额外启动加密密钥。access_token 按应用缓存并预留提前刷新。管理员从应用可见通讯录选择成员,账号绑定键为 (corp_id, userid),姓名和部门仅为展示快照。内部员工已绑定且仍可见时优先以本人发起,代理等非企微账号始终以应用默认成员发起;本地申请仍保留真实业务提交人,不用默认成员冒充业务操作者。
场景配置保存已知 template_id。保存或发布时调用 oa/gettemplatedetail 校验控件 ID、类型、必填项和选择项 key。系统不创建模板、不保存审批节点、不计算部门领导或财务人员;oa/applyevent 使用 use_template_approver=1,审批人由企微后台模板决定。
提交过程:
- 业务用例校验内部提交人的企微绑定或应用默认发起人仍可见、场景已启用且模板结构有效。
- 退款或线下充值在同一 GORM 事务保存业务申请、通用审批实例和提交 Outbox。
- Adapter 消费提交事件,上传必要附件并调用
oa/applyevent,保存返回的sp_no到通用实例external_ref。 - 外部调用记录 Integration Log;结果未知时不盲目创建第二张审批单,转人工/查询恢复。
- 回调端点在 5 秒内完成验签解密、必要幂等记录和快速响应,再异步调用
getapprovaldetail。 - Adapter 将企微状态翻译为现有标准决策;现有决策 Outbox 分别驱动退款终结或线下代充入账。
- 定时任务扫描未终态实例,并用批量拉取审批单号与详情查询补偿回调遗漏。
企业微信审批详情的审批节点 userid 可以用于页面展示,但只做读取投影;不能映射系统账号时返回空名称,不影响业务终态。
6. 通知只复用现有站内能力
- 创建物流换货单成功后,向关联个人客户写一次站内通知;C 端按既有未读弹窗机制展示。
- 店铺主钱包余额从不低于 100 元变为低于 100 元时,向
business_owner_account_id对应后台账号发送一次通知;余额恢复到阈值以上后允许下一次下降再次提醒。 - 每日扫描预计套餐到期日在 15、7、3 天节点的资产,生成后台/代理临期列表所需标识,并向关联个人客户发送节点通知。相同资产、节点、到期日只通知一次。
通知属于可靠副作用时使用现有 Outbox 和幂等键;不新增短信、企微业务员提醒或自定义营销内容。
7. 批量与导出复用已完成基础设施
批量订购和设备分配只新增业务解析器/执行器,继续使用现有对象存储、任务五态、Asynq 重试、失败明细和下载能力。文件级校验失败不写业务数据;涉及扣款/订购时按现有订单幂等键和钱包流水保证不重复扣款。
六类导出分别实现 datasource,查询直接投影 DTO,不串联多个业务迁移。不存在或无稳定来源的字段不得临时创造含义,本 Change 不为此新增字段或迁移;该字段从导出表头中省略并在对接说明中记录,不阻断其他稳定字段的导出交付。
8. 数据、事务、常量与缓存
- 新表/字段通过 PostgreSQL 增量迁移添加,不建外键,不写 GORM 关联标签。
- 简单列表字段使用批量
WHERE id IN (...)或 JOIN 投影,禁止逐行查询账号。 - 批量实名策略在单事务内校验全部资产后统一更新;任何冲突则全部回滚。
- 退款和充值金额继续使用现有 Domain Ledger/钱包流水与乐观锁;审批 Adapter 不直接修改余额或退款金额。
- 所有新枚举、任务类型、场景编码、导出 datasource code 和 Redis key 在
pkg/constants定义并写中文注释。 - 企微 token 使用 Redis 缓存和短锁;Redis 不可用时允许受控回源,但不得把 token 返回前端。
9. Audit Event、Domain Ledger、Integration Log 与 Outbox 决策
| 切片 | Audit Event | Domain Ledger | Integration Log | Outbox |
|---|---|---|---|---|
| 字段、筛选、Bug、只读导出 | N/A:无敏感状态变更;实现时登记覆盖基线理由 | N/A 或沿用既有订单/套餐事实 | N/A | N/A |
| 店铺登录开关、企微账号绑定、模板场景、支付方式配置 | 使用现有统一审计接缝记录操作者和前后值;不建设新的审计中心 | N/A | 模板校验调用记录 | 缓存失效仅沿用现有配置机制 |
| 退款、线下代充 | 审批申请人和配置操作进入既有审计/申请快照;企微自动终态不伪造人工操作者 | 沿用退款、订单、钱包流水 | 所有企微请求、响应摘要、回调和结果未知 | 申请提交、标准终态和资金后续动作使用现有 Outbox |
| 通知 | N/A:通知记录本身是投递事实 | N/A | N/A | 必须,按业务键和接收人防重 |
| Gateway 限速 | 记录后台操作者、资产和档位 | N/A | 必须记录请求、响应及结果未知 | 无后续可靠副作用时 N/A |
| 批量订购/分配 | 沿用任务提交审计;资金动作沿用既有记录 | 订购扣款沿用订单/钱包流水 | 涉及外部支付或 Gateway 时沿用 | 仅业务已有可靠副作用继续使用 |
实现前必须增量更新 .scratch/tech-global-audit/审计覆盖基线.md,不得因本 Change 排除全局 Audit Event 专项而跳过上述已有审计接缝。
10. 续费、待支付订单与强充支付方式边界
- 历史已支付订单和资产当前套餐只提供稳定的资产、套餐引用。用户点击续费时继续调用现有 C 端创建订单接口并生成一张新订单,不新增续费接口,也不在旧订单上修改支付方式。
- 续费新订单创建前按当前
system_config、当前套餐价格/流量和当前强充状态重新选择支付方式;订单创建成功后,和普通订单一样固化所选方式。既有待支付订单再次支付时只能执行其已保存方式,不允许切换。 - 卡和设备分别维护非空支付方式集合,合法选项均为
wallet、wechat、alipay,三项可独立启停;首次初始化两类资产均全选。配置非法或读取失败时失败关闭,不推断或放开支付渠道。 - C 端购包接口返回“配置集合与当前业务场景的交集”。当前资产仍命中既有强充规则时必须剔除
wallet;普通钱包充值场景固定剔除wallet。后端在创建订单、创建充值单和支付准备时执行相同校验,不能信任前端展示结果。 - 强充继续沿用现有流程:第三方支付充值成功后先入资产钱包,再通过既有异步自动购包任务扣钱包并购买所选套餐。支付方式配置不得把强充改成提示后由用户手工充值。
- 强充资格必须复用既有一次性佣金状态语义:
first_recharge与accumulated_recharge是同一强充/自动购包流程的不同触发配置;该系列一次性佣金已经触发后不再强充。不得在 C 端订单服务复制一套遗漏已触发状态的判断。
Risks / Trade-offs
- [企微应用看不到提交人] → 保存绑定前只允许选择应用可见成员,提交前再次校验;权限变化时明确失败,不创建业务申请和审批实例。
- [管理员修改企微模板导致控件映射失效] → 保存/发布场景时拉取模板详情并保存指纹,提交前校验关键控件;失效只阻断新提交,存量审批继续同步。
- [回调丢失或重复] → 回调快速响应、按
sp_no幂等同步,保留未终态轮询和时间窗补偿。 - [企微提交超时但实际成功] → 标记结果未知并先查详情/批量单号,不直接重试创建,避免重复审批。
- [简单需求再次被扩张] → tasks 明确每个切片的不迁移范围;实现阶段不得改变架构通道,确需改变必须先更新提案。
- [大批量影响接口响应] → 实名批量限制 500 且同步事务;CSV 和导出走异步任务,列表/人员映射使用批量查询。
- [店铺开关不能立即踢掉在线用户] → 这是本期有意取舍;接口和文档明确只阻止新登录。
Migration Plan
- 先完成仓库证据核验,确认公共 Outbox、审批核心、通知、导入/导出和
system_config可复用,禁止重复建表。 - 按纵向切片先交付无外部依赖的 Bug、字段、筛选、开关、实名、支付配置和本地通知。
- 增量迁移企微应用/场景/账号绑定及确实缺失的业务字段,迁移脚本保持向前兼容,不删除旧字段。
- 部署企微 Adapter、回调和轮询 Worker,配置测试应用、模板和账号绑定,完成真实退款/线下代充闭环后再关闭旧人工审批入口。
- 部署批量订购、导出、限速和设备批量分配,完成前后端联调与任务失败恢复验证。
- 完成生产代码、必要迁移文件、接口契约、配置说明和实施证据,以
gofmt、只读检查与git diff --check做静态收口并交付联调;自动化测试、构建、迁移执行和真实外部环境验收由后续流程承担。
回滚时,简单字段和兼容接口可回退应用版本;新增列/表暂不删除。产生企微审批、退款、充值、钱包流水、订单、通知、Outbox 或 Integration Log 后不得清表或恢复旧 Writer,只能暂停新入口并前向修复。旧人工审批入口仅在新链路验证完成后停用,因此切换前仍可回退。
Open Questions
无阻塞性业务问题。实施和部署阶段仅需提供测试环境企微应用 Secret、可见范围、模板 ID、可信 IP、回调 Token/EncodingAESKey,以及 Gateway 测试配置;这些是环境输入,不改变需求范围。