Files
lijiaoqiao/docs/supply_technical_design_enhanced_v1_2026-03-25.md

211 lines
8.6 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.
# 供应侧技术设计增强版XR-001
- 版本v1.1
- 日期2026-03-27
- 状态:生效(实施基线)
- 目标:补齐供应侧关键写路径的幂等、并发、事务、不变量与可靠性闭环
- 关联 SSOT
- `llm_gateway_subapi_evolution_plan_v4_2_2026-03-24.md`
- `acceptance_gate_single_source_v1_2026-03-18.md`
- `supply_button_level_prd_v1_2026-03-25.md`
- `supply_api_contract_openapi_draft_v1_2026-03-25.yaml`
- `database_domain_model_and_governance_v1_2026-03-27.md`
---
## 1. 设计边界与约束
1. 业务链路固定为:`用户A供给 -> 平台 -> 用户B购买平台服务`
2. 供应方上游凭证仅平台托管,任何北向接口不得回显可复用凭证片段。
3. 所有关键写操作必须支持双键幂等:`request_id + idempotency_key`
4. 所有状态迁移必须满足“显式前置状态 + 原子落库 + 审计可追溯”。
5. 所有跨系统副作用必须通过 Outbox/Saga 触发,禁止在数据库事务中直连外部系统。
---
## 2. 关键写路径与幂等协议
## 2.1 适用操作
1. `POST /api/v1/supply/accounts`
2. `POST /api/v1/supply/packages/{id}/publish`
3. `POST /api/v1/supply/packages/batch-price`
4. `POST /api/v1/supply/settlements/withdraw`
5. `POST /api/v1/supply/settlements/{id}/cancel`
## 2.2 入站协议MUST
1. Header 必填:`X-Request-Id`UUID
2. Header 必填:`Idempotency-Key`(长度 16-128
3. 幂等作用域:`tenant_id + operator_id + api_path + idempotency_key`
4. 幂等有效期:`24h`(提现类可扩展到 `72h`
5. 契约落地状态:已在 OpenAPI 写操作路径挂载上述 header并补充 `409/202` 幂等语义示例2026-03-27
## 2.3 语义规范
1. 首次成功:返回业务成功码(`200/201`)并写入幂等记录。
2. 重放同参:返回同一业务结果,`idempotent_replay=true`
3. 重放异参:返回 `409 IDEMPOTENCY_PAYLOAD_MISMATCH`
4. 首次处理中:返回 `202 IDEMPOTENCY_IN_PROGRESS`,携带 `retry_after_ms`
## 2.4 存储建议PostgreSQL
```sql
create table if not exists supply_idempotency_record (
id bigserial primary key,
tenant_id bigint not null,
operator_id bigint not null,
api_path varchar(200) not null,
idempotency_key varchar(128) not null,
request_id varchar(64) not null,
payload_hash char(64) not null,
response_code int,
response_body jsonb,
status varchar(20) not null, -- processing/succeeded/failed
expires_at timestamp not null,
created_at timestamp not null default now(),
updated_at timestamp not null default now(),
unique (tenant_id, operator_id, api_path, idempotency_key)
);
```
---
## 3. 并发控制策略(按领域动作)
## 3.1 账号挂载与状态迁移
1. 账号状态变更(激活/暂停/禁用)采用乐观锁:`version` 字段 CAS 更新。
2. 激活操作 SQL 需带前置状态:`where id=? and status in ('pending','suspended') and version=?`
3. 同一账号同一时刻只允许一个状态迁移事务;冲突返回 `409 SUP_ACC_4091`
## 3.2 套餐发布与批量调价
1. 套餐单条迁移采用乐观锁,保证 `draft -> active -> paused -> expired` 不跳态。
2. 批量调价采用“分片事务 + 明细回执”模式,单条失败不回滚全部成功项。
3. 批量任务必须落审计明细:`total/success/failed/failed_items[]`
## 3.3 提现发起与撤销
1. 发起提现采用悲观锁:`select ... for update` 锁定供应方可提现余额行。
2. 约束:同一供应方同一时刻最多 1 笔 `processing` 提现单。
3. 唯一约束建议:
```sql
create unique index if not exists uq_settlement_supplier_processing
on supply_settlements(user_id)
where status = 'processing';
```
4. 余额扣减与结算单创建必须同事务提交,任一失败整体回滚。
---
## 4. 领域不变量Invariant
| 编号 | 不变量 | 触发动作 | 拒绝码 |
|---|---|---|---|
| INV-ACC-001 | `active` 账号不可删除 | 删除账号 | `SUP_ACC_4092` |
| INV-ACC-002 | 账号 `disabled` 仅管理员可恢复 | 激活账号 | `SUP_ACC_4031` |
| INV-PKG-001 | `sold_out` 只能系统迁移 | 人工改状态 | `SUP_PKG_4092` |
| INV-PKG-002 | `expired` 套餐不可直接恢复 | 发布上架 | `SUP_PKG_4093` |
| INV-PKG-003 | 售价不得低于保护价 | 发布/调价 | `SUP_PKG_4001` |
| INV-SET-001 | `processing/completed` 不可撤销 | 撤销申请 | `SUP_SET_4092` |
| INV-SET-002 | 提现金额不得超过可提现余额 | 发起提现 | `SUP_SET_4001` |
| INV-SET-003 | 结算单金额与余额流水必须平衡 | 结算入账 | `SUP_SET_5002` |
说明:所有不变量失败必须写入审计事件 `invariant_violation`,并携带 `rule_code`
---
## 5. 事务边界与副作用编排
## 5.1 本地事务内(必须原子)
1. 领域状态变更(账号/套餐/结算单)
2. 资金子账变更(冻结/解冻/可提现)
3. 幂等记录更新(`processing -> succeeded/failed`
4. 审计日志落库(最小字段集)
5. Outbox 事件入库
## 5.2 事务外(异步执行)
1. 通知发送(站内信/邮件/短信)
2. 导出任务生成
3. 风险引擎异步评分
4. BI 聚合看板更新
## 5.3 Outbox 事件规范
1. 事件命名:`supply.{domain}.{action}.{result}`
2. 必填字段:`event_id/request_id/tenant_id/object_id/before_state/after_state`
3. 消费保障:至少一次投递 + 消费幂等(以 `event_id` 去重)
---
## 6. 失败注入与回滚策略
| 场景ID | 注入点 | 预期行为 | 验收点 |
|---|---|---|---|
| FI-001 | 提现创建后数据库超时 | 事务回滚,不产生挂单 | 余额不变、无孤儿单 |
| FI-002 | 幂等记录已存在同键异参 | 返回 409 | 不改业务状态 |
| FI-003 | 套餐发布时状态冲突 | 返回 409 | 状态不跳变 |
| FI-004 | 审计落库失败 | 主事务失败并回滚 | 无“成功但无审计” |
| FI-005 | Outbox 入库失败 | 主事务失败并回滚 | 无“状态已变更但无事件” |
| FI-006 | 导出服务不可用 | 主事务成功,异步重试 | 业务不阻塞 |
| FI-007 | 外部 query key 请求 | 网关拒绝 | M-016=100% |
| FI-008 | 响应误回显凭证片段 | 安全门禁阻断 | M-013=0 |
---
## 7. SLO 与页面动作映射
| 页面按钮 | API | SLI | SLO | Error Budget |
|---|---|---|---|---|
| BTN-ACC-001 立即验证 | `/api/v1/supply/accounts/verify` | 可用率 + P95 | 可用率 >= 99.9%P95 <= 800ms | 月度 0.1% |
| BTN-ACC-002 提交挂载 | `/api/v1/supply/accounts` | 成功率 | 成功率 >= 99.5% | 月度 0.5% |
| BTN-PKG-002 发布上架 | `/api/v1/supply/packages/{id}/publish` | 成功率 + 冲突率 | 成功率 >= 99.5%,冲突率 <= 0.3% | 月度 0.5% |
| BTN-PKG-005 批量调价 | `/api/v1/supply/packages/batch-price` | 局部成功可解释率 | 明细可解释率 = 100% | 0 |
| BTN-SET-002 发起提现 | `/api/v1/supply/settlements/withdraw` | 一致性 + 时延 | `billing_error_rate_pct<=0.1%`P95<=1200ms | 与 M-004 联动 |
| BTN-SET-003 撤销申请 | `/api/v1/supply/settlements/{id}/cancel` | 成功率 | 成功率 >= 99.9% | 月度 0.1% |
---
## 8. 审计与安全对齐
1. 所有关键写请求必须记录:`request_id/idempotency_key/operator_id/object_id/result_code`
2. 错误体、导出、日志统一经过脱敏扫描;命中即触发 P0。
3. 与门禁指标映射:
1. M-013凭证泄露事件数=0
2. M-014平台凭证入站覆盖率=100%
3. M-015需求方绕平台直连事件=0
4. M-016外部 query key 拒绝率=100%
---
## 9. 实施与验收清单
1. API 网关:校验并透传 `X-Request-Id``Idempotency-Key`
2. 数据库:新增幂等表、状态版本字段、提现唯一索引。
3. 服务层:统一幂等拦截器与冲突返回码。
4. 测试层:新增并发冲突、幂等重放、失败注入专项。
5. 门禁层:将 FI-001~FI-008 纳入 `SUP-*``SEC-*` Gate。
6. 证据层:执行日志、指标截图、审计抽样、签署记录齐全。
达到以上 6 项即视为 XR-001 关闭。
---
## 10. 跨域数据库约束(新增)
1. 供应域不是独立孤岛,必须依赖 Core/IAM/Auth/Billing/Audit 五域主表。
2. 供应域关键表必须补齐三类字段:
1. 加密字段:`*_cipher_algo``*_kms_key_alias``*_key_version``*_fingerprint`
2. 单位字段:`quota_unit``price_unit``amount_unit``currency_code`
3. 审计字段:`request_id``idempotency_key``audit_trace_id``created_ip``updated_ip``version`
3. 数据库实施顺序固定:
1. `platform_core_schema_v1.sql`
2. `supply_schema_v1.sql`
3. `supply_schema_v1_patch_2026-03-27.sql`
4. 未完成上述顺序与字段补齐,不得判定 XR-001 关闭。