深色模式
架构决策记录 (ADR)
本页说明
讲什么:出入金系统中关键设计决策的背景、可选方案、最终选择和影响——帮助理解"为什么是这样" 适合谁:产品经理评估需求变更影响时、新人理解系统设计逻辑时 前置阅读:系统架构与数据流格式:每条决策按「背景 → 可选方案 → 决定 → 后果」结构记录 预计阅读:4 分钟 负责人:出入金产品团队
核心要点:每条架构决策记录了"背景→可选方案→最终选择→后果"——PM 评估需求变更时,先看相关 ADR 理解"为什么是这样",避免重复踩坑。
ADR-001:入金匹配采用五维度匹配引擎
状态:已采纳 · 生效中
背景: 用户通过银行转账入金到证券账户时,系统需要将银行流水(Flow)与用户的入金申请(Apply)进行配对。早期方案是纯人工匹配,但随着用户量增长,人工处理无法扩展。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 纯人工匹配 | 准确率最高 | 不可扩展,处理时效差 |
| B. 单维度匹配(仅金额) | 简单 | 误匹配率高(同金额不同用户) |
| C. 五维度匹配(金额+币种+银行+日期+卡号) | 准确率高,可自动化 | 实现复杂,需维护各银行容差 |
| D. 银行端回传唯一 ID | 最精确 | 大部分银行不支持(仅 BST 可做到) |
决定:采用方案 C,五维度匹配引擎,辅以两级容差机制(自动入账容差 + 辅助匹配容差)。
后果:
- 正面:~95% 的入金可自动完成匹配和入账,运营只需处理 ~5% 的异常
- 负面:需要为每家银行单独维护匹配规则和容差参数(12 家银行 12 套规则)
- 变更影响:新增银行时必须实现对应的 Match 类(如
BocMatch.php、HsbcMatch.php),并配置容差参数
代码位置:deposit/src/app/Business/Match/ 目录下各银行 Match 类
ADR-002:出金采用三步审批工作流(Audit → Confirm → Remittance)
状态:已采纳 · 生效中
背景: 出金涉及真实资金流出,需要严格的审批控制。但过于繁琐的审批会影响用户体验。需要在安全和效率之间取得平衡。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 单步审批(直接汇款) | 最快 | 安全风险高 |
| B. 两步审批(审核+汇款) | 简单 | 通道选择无法独立审核 |
| C. 三步审批 + 6 套模板 | 灵活,普通出金跳过 Audit | 实现复杂 |
| D. 全流程人工 | 最安全 | 时效差,无法自动化 |
决定:采用方案 C,三步审批(Audit → Confirm → Remittance),通过 6 套模板控制哪些出金需要哪些步骤。普通出金(default 模板)跳过 Audit 直接进入 Confirm。
后果:
- 正面:普通出金只需 2 步,高风险出金有额外审核层
- 正面:BST 通道满足 6 个条件时可全自动完成,无需人工
- 负面:6 套模板的维护成本;新增审批步骤需创建 Step 类
- 变更影响:新增审批步骤 → 创建 Step 类实现
IFStep接口 → 更新$stepTemplates
代码位置:
- 模板定义:
withdraw/src/app/Business/Task.php→$stepTemplates - 步骤实现:
withdraw/src/app/Business/Tasks/Step/{Audit,Confirm,Remittance}.php
ADR-003:BST 银证采用"内银系统一接入 + 银行适配层"架构
状态:已采纳 · 生效中
背景: 招行、民生、天星三家银行都支持银证转账(BST),但接入协议完全不同:招行/民生用 SM2 加密 Socket,天星用 HTTPS REST API + Mandate 模型。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 每家银行独立系统 | 互不影响 | 重复开发,统一调度困难 |
| B. 统一业务层 + 银行适配层 | 业务逻辑共享,银行差异封装 | 适配层设计复杂 |
| C. 统一协议转换网关 | 最简洁 | 协议差异太大,强行统一会丢失特性 |
决定:采用方案 B。入金/出金的业务流程(申请、审批、入账)在上层统一,银行通信差异封装在独立的服务中(cmb_stock_trans、ms_stock_bank_transaction、天星 API 调用层)。
后果:
- 正面:新增 BST 银行时只需实现适配层,不改动业务逻辑
- 负面:天星的异步轮询模型与招行/民生的实时推送模型差异大,上层需要兼容两种模式
- 变更影响:天星出金需要额外的轮询任务(
AsbBstTransfer+SyncAsbBstWithdraw),而招行/民生不需要
ADR-004:eDDA/eDDI 并发控制采用 Token Bucket(汇丰)+ 悲观锁(恒生)
状态:已采纳 · 生效中
背景: 汇丰和恒生的 eDDI 扣款接口都有并发限制。如果并发请求过多,银行会拒绝或延迟处理。需要在系统侧做速率控制。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 全局互斥锁 | 最简单 | 吞吐量极低 |
| B. 汇丰:Token Bucket 乐观锁 | 高吞吐,平滑限流 | 实现较复杂 |
| C. 恒生:InnoDB 行锁串行化 | 严格有序,满足交易时段约束 | 吞吐量受限 |
| D. 消息队列限流 | 解耦 | 引入额外基础设施 |
决定:
- 汇丰:Token Bucket 乐观锁(
UPDATE ... WHERE tokens >= 1),每秒 4 个 Token,数据库持久化 - 恒生:InnoDB 行锁悲观锁(
SELECT ... FOR UPDATE),因为恒生有交易时段要求,需要严格串行
两家银行采用不同方案的核心原因:恒生需要感知交易时段(非交易时段请求进入 new_blank 状态),这要求严格的请求排序;汇丰无此约束,用 Token Bucket 可获得更高吞吐。
后果:
- 正面:两套方案各自适配银行特点,稳定运行
- 负面:两套并发控制机制增加维护成本
- 变更影响:调整汇丰限流 → 修改 Token Bucket 产生速率;调整恒生 → 修改行锁粒度
代码位置:
- 汇丰:
sba_hsbc_eddi/Token Bucket 实现 - 恒生:
sba_hase_eddi/行锁实现
ADR-005:出金通道选择采用规则路由 + 人工兜底
状态:已采纳 · 生效中
背景: 系统支持 12 种出金通道(Method),需要根据用户银行卡、币种、金额等条件自动选择最优通道。但部分场景无法自动判断。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 全自动路由 | 无需人工 | 无法覆盖所有边界情况 |
| B. 自动路由 + method=null 人工兜底 | 覆盖率高,异常有人工保障 | 运营需要通道知识 |
| C. 全人工选择 | 最灵活 | 不可扩展 |
决定:采用方案 B。calcMethod() 根据银行卡类型自动路由,无法判断时设 method = null,由运营在 Confirm 步骤手动选择。
后果:
- 正面:~80% 的出金通道可自动确定
- 负面:
method = null是 Confirm 步骤卡住最常见的原因 - 变更影响:新增银行卡类型时必须更新
calcMethod()路由规则,否则会产生 method=null
代码位置:withdraw/src/app/Business/CreatorBase.php → calcMethod()
ADR-006:银行流水采集采用"一银行一服务"架构
状态:已采纳 · 生效中
背景: 不同银行的流水接入协议差异极大:中银用 B2E XML、汇丰用 MT910 SWIFT、工银用银企直联、EWB 用 CSV 文件导入。
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 统一流水采集服务 | 集中管理 | 协议差异太大,强行统一不现实 |
| B. 一银行一独立服务 | 各服务独立部署、独立维护、互不影响 | 服务数量多 |
| C. 按协议类型分组 | 折中 | 分组标准不稳定 |
决定:采用方案 B。每家银行有独立的流水采集服务:
- 中银:
bochk_flow_go(Go) - 汇丰:
hsbc_bank_flow_service(Python) - 工银:
icbc_be_relay(Python) - 广发:
cgb_fps_service(Go) - 渣打:
scb_service(Go)
所有服务将流水统一写入 acct_trd_record / {bank}_bank_flow 表,由匹配引擎统一消费。
后果:
- 正面:某家银行服务故障不影响其他银行;各服务可用最适合的技术栈
- 负面:服务数量多(5+),监控和运维成本高
- 变更影响:新增银行 → 新建独立服务 + 实现 Match 类 + 注册到匹配引擎
ADR-007:入金冲正执行时同步解绑银行卡
状态:已采纳 · 生效中(有争议)
背景: 入金冲正(资金退回)后,该笔入金绑定的银行卡是否应该自动解绑?
可选方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 冲正时自动解绑 | 防止同一张卡再次出现问题 | 用户可能需要用同一张卡重新入金 |
| B. 冲正后保留绑定 | 用户体验好 | 风险卡可能继续使用 |
| C. 按冲正原因决定 | 精细化控制 | 实现复杂 |
决定:采用方案 A——冲正时一律自动解绑。
后果:
- 正面:降低同一张有问题的银行卡反复入金的风险
- 负面:chargeback 类冲正后,用户需要重新绑卡才能用同一张卡入金
- 变更影响:如需改为"不解绑",移除
deposit/src/app/Business/Reverse.php中的解绑逻辑
如何使用 ADR
产品经理评估需求变更时:
- 先查看相关 ADR,理解现有设计的背景和约束
- 评估新需求是否与现有 ADR 冲突
- 如果需要推翻现有决策,新建 ADR 记录新决策,并标注旧 ADR 为"已废弃"
新增 ADR 模板:
ADR 模板
markdown
## ADR-XXX:决策标题
**状态**:提议中 / 已采纳 / 已废弃 / 已替代(被 ADR-YYY 替代)
**背景**:
为什么需要做这个决策?当时面临什么问题?
**可选方案**:
| 方案 | 优点 | 缺点 |
|------|------|------|
**决定**:选择了哪个方案,以及核心理由。
**后果**:
- 正面影响
- 负面影响
- 变更影响(后续需求变更时需要注意什么)
**代码位置**:涉及的核心代码文件读完之后
| 我想... | 去看 |
|---|---|
| 理解系统整体架构 | 系统架构与数据流 |
| 深入 SBA 编排层 | SBA 资金编排 |
| 了解入金业务全景 | 入金方式总览 |
| 了解出金审批流程 | 出金生命周期 |
这个页面有帮助吗?