Compare commits

...

7 Commits

74 changed files with 11900 additions and 131 deletions

View File

@ -1,6 +1,42 @@
# CLAUDE.md
本文件用于指导 Claude Code 在本仓库中的工作方式。
本文件用于指导 Claude Code 及通用代码代理(包括 Codex 类代理)在本仓库中的工作方式。
## Workspace Coordination
本仓库现在是 `water-workspace` 下的文档总控仓,默认作为正式规格、计划、任务、验收与治理台账的单一入口。
### 邻接仓库
- `../water-backend/`:后端实现仓,默认主开发分支为 `develop`
- `../water-frontend/`:前端实现仓,默认主开发分支为 `develop`
### 启动规则
- 需要运行 `/speckit.specify``/speckit.plan``/speckit.tasks`、正式文档修订、治理台账更新、验收结论汇总时,必须从 `water-docs` 根目录启动代理。
- 需要检查正式规格、计划、任务、基线、evidence 时,以 `water-docs/specs/``water-docs/docs/` 为准。
- 未经用户明确要求,不在本仓库直接修改 `../water-backend/``../water-frontend/` 中的业务代码。
### 多仓协作规则
- 本仓库中的 `.specify/` 是唯一正式流程入口backend/frontend 不复制第二套 `.specify/`
- backend/frontend 仓内实现结论,必须回写到本仓库的正式文档或 `specs/` 工件,不能仅停留在代码仓口头说明。
- 重要 feature 默认记录代码基线:
- backend commit SHA
- frontend commit SHA
- 验证日期
### Worktree 约定
- 推荐在 `../worktrees/` 下按 feature 建立平铺 worktree
- `docs-<feature>`
- `backend-<feature>`
- `frontend-<feature>`
- `verify-<feature>`
- 文档 lane 只改 `water-docs`
- backend lane 只改 `water-backend`
- frontend lane 只改 `water-frontend`
- verify lane 负责样本、日志、验收结论和基线固定
## 项目定位
@ -20,14 +56,17 @@
```text
/
├── docs/design/00_Management/ # 项目管理、进度跟踪、交付规范、编写指南
├── docs/design/01_Overview/ # 总体设计:系统概述、系统架构、概要设计、系统图谱
├── docs/design/02_Detailed_Design/ # 详细设计主详设、模块设计、CA 安装设计
├── docs/design/03_Technical_Design/ # 技术专项:数据库、表结构、接口、安全、部署、加密
├── docs/design/04_Appendix/ # 附录与归档资料
├── assets/ # 图片、模板等静态资源
├── docs/ # 研究资料、映射文档、使用指南
├── scripts/ # 文档处理与导出脚本
└── infra/ # 辅助基础设施
├── docs/design/01_Overview/ # 总体设计:系统概述、系统架构、概要设计、系统图谱
├── docs/design/02_Detailed_Design/ # 详细设计主详设、模块设计、CA 安装设计
├── docs/design/03_Technical_Design/ # 技术专项:数据库、表结构、接口、安全、部署、加密
├── docs/design/04_Appendix/ # 附录与归档资料
├── .claude/ # Claude Code 相关配置
├── .omc/ # 项目记忆与代理状态
├── .zed/ # Zed 项目配置
├── assets/ # 图片、模板等静态资源
├── docs/ # 研究资料、映射文档、使用指南
├── scripts/ # 文档处理与导出脚本
└── infra/ # 辅助基础设施
```
## 当前文档组织原则
@ -193,6 +232,9 @@ make export-word
make export-pdf
make export-html
make unified-export
npm run check:marksman
npm run marksman:help
npm run marksman:server
```
如仅改动单篇文档,优先使用较小范围校验,而不是每次都跑全量导出。
@ -256,11 +298,13 @@ make unified-export
- 归档整理与信息整编助手
- 基于现有资料进行保守补完的编辑者
不是:
不是:
- 擅自扩展需求的产品经理
- 无依据发明实现细节的方案生成器
- 动辄新建文件的“版本制造机”
- 动辄新建文件的”版本制造机”
本文件用于指导通用代码代理(包括 Codex 类代理)在本仓库中的工作方式。
## 最终目标

View File

@ -18,9 +18,11 @@
## 推荐阅读顺序
1. `guides/BACKEND_CURRENT_STATUS.md`
2. `guides/BACKEND_TABLE_MAPPING.md`
3. 其他辅助资料
1. `guides/SPECKIT_WORKFLOW_HUMAN_GUIDE.md`
2. `guides/SYSTEM_CAPABILITY_CLOSURE_MAP.md`
3. `guides/BACKEND_CURRENT_STATUS.md`
4. `guides/BACKEND_TABLE_MAPPING.md`
5. 其他辅助资料
## 维护原则

View File

@ -0,0 +1,96 @@
# 营收系统银行缴费接口文档
本目录包含营收系统与银行/第三方支付机构之间的接口 OpenAPI 规范文档。
## 目录结构
```
docs/api/openapi/
├── main/
│ ├── openapi.yaml # OpenAPI 3.0.3 主文档入口
│ ├── components/ # 可复用组件定义
│ │ ├── schemas.yaml # 数据模型定义
│ │ ├── responses.yaml # 响应定义
│ │ ├── parameters.yaml # 参数定义
│ │ ├── headers.yaml # 头部定义
│ │ ├── security.yaml # 安全定义
│ │ └── index.yaml # 组件索引
│ └── paths/ # API 路径定义
│ ├── bill-query.yaml # 账单查询
│ ├── bill-pay.yaml # 账单缴费
│ ├── pay-invalid.yaml # 账单红冲
│ ├── withholding-signing.yaml # 代扣签约
│ ├── withholding-termination.yaml # 代扣解约
│ ├── withholding-send-disc.yaml # 代扣送盘
│ └── withholding-back-disc.yaml # 代扣回盘
├── generated/ # 生成的代码(保留参考)
├── validate.js # 文档验证脚本
└── serve.js # 本地预览服务器
scripts/api-tools/
└── validate-all.js # 批量验证工具
```
## 接口清单
| 接口名称 | 路径 | 交易码 | 描述 |
|---------|------|--------|------|
| 账单查询 | `/api/app/payCeb/getChargeSearch` | Query/QueryRes | 查询客户账单信息 |
| 账单缴费 | `/api/app/payCeb/getChargeOffs` | Pay/PayRes | 处理账单缴费 |
| 账单红冲 | `/api/app/payInvalid/payInvalid` | PayInvalid/PayInvalidRes | 红冲缴费记录 |
| 代扣签约 | `/api/app/bankWithholding/signing` | Signing/SigningRes | 银行代扣签约 |
| 代扣解约 | `/api/app/bankWithholding/termination` | Termination/TerminationRes | 银行代扣解约 |
| 代扣送盘 | `/api/app/bankWithholding/sendDisc` | SendDisc/SendDiscRes | 批量代扣送盘 |
| 代扣回盘 | `/api/app/bankWithholding/backDisc` | BackDisc/BackDiscRes | 接收银行回盘 |
## 核心特性
### 数据格式支持
- **XML格式**GBK编码符合传统银行系统规范
- **JSON格式**UTF-8编码适合现代化系统集成
- **双格式支持**同时支持XML和JSON请求响应
### 安全加密
- **3DES加密**ECB模式PKCS7填充默认
- **SM2加密**支持C1C3C2和C1C2C3模式
- **SM4加密**支持ECB和CBC模式
- **Base64编码**加密后数据进行Base64编码传输
## 使用指南
### 1. 验证文档
```bash
cd docs/api/openapi
node validate.js
```
### 2. 启动本地预览服务
```bash
cd docs/api/openapi
node serve.js
```
访问 http://localhost:3000 查看 API 文档
### 3. 导入到开发工具
#### Swagger Editor
1. 打开 [Swagger Editor](https://editor.swagger.io/)
2. 导入 `docs/api/openapi/main/openapi.yaml` 文件
#### Postman
1. 打开 Postman
2. 点击 Import
3. 选择 `docs/api/openapi/main/openapi.yaml` 文件导入
## 相关文档
- [接口规范设计文档](../../design/04_Appendix/Archive/银行缴费接口规范设计文档.md)
- [接口说明文档](../../design/04_Appendix/Archive/银行缴费接口说明.md)
- [主接口设计文档](../../design/03_Technical_Design/03_Interface_Design.md)
## 来源
本文档从 `water-bank-api-doc` 仓库迁移而来,原始文档基于营收系统缴费接口规范 v1.5。

View File

@ -0,0 +1,32 @@
components:
headers:
# 响应内容类型
ContentType:
description: 响应内容类型
schema:
type: string
enum:
- "application/xml; charset=GBK"
- "application/json; charset=UTF-8"
# 响应时间戳
ResponseTime:
description: 服务端响应时间戳
schema:
type: string
format: date-time
example: "2024-01-01T12:00:00.000Z"
# 请求追踪ID
RequestId:
description: 请求追踪标识
schema:
type: string
example: "req_123456789012"
# 服务版本
ServiceVersion:
description: 服务端版本号
schema:
type: string
example: "1.0.1"

View File

@ -0,0 +1,288 @@
components:
# 数据模型
schemas:
# 基础请求模型
BaseRequest:
type: object
required:
- Version
- InstId
- TranCode
- TranDate
- TranSeq
properties:
Version:
type: string
description: 版本号
example: "1.0.1"
InstId:
type: string
description: 机构编码
example: "00001"
maxLength: 30
TranCode:
type: string
description: 交易码
example: "Query"
maxLength: 20
TranDate:
type: string
description: 交易日期
pattern: '^\d{8}$'
example: "20240101"
TranSeq:
type: string
description: 交易流水号(银行生成的唯一标识)
example: "123456789012"
maxLength: 40
# 基础响应模型
BaseResponse:
type: object
required:
- Version
- InstId
- TranCode
- TranDate
- TranSeq
- RespCode
- RespMessage
properties:
Version:
type: string
description: 版本号
example: "1.0.1"
InstId:
type: string
description: 机构编码
example: "00001"
TranCode:
type: string
description: 交易码
example: "QueryRes"
TranDate:
type: string
description: 交易日期
pattern: '^\d{8}$'
example: "20240101"
TranSeq:
type: string
description: 交易流水号
example: "123456789012"
RespCode:
type: string
description: 返回码
enum:
- "AAAAAAA" # 成功
- "DEF0001" # 无相应记录
- "DEF0002" # 缴费金额不匹配
- "SYS1001" # 系统异常
- "SEC2001" # 加密错误
example: "AAAAAAA"
RespMessage:
type: string
description: 返回消息
example: "成功"
maxLength: 60
# 错误详情
ErrorDetail:
type: object
properties:
ErrorCode:
type: string
description: 错误码
example: "DEF0001"
ErrorMsg:
type: string
description: 错误消息
example: "用户编号123456不存在"
ErrorTime:
type: string
format: date-time
description: 错误时间
example: "2024-01-01T12:00:00.000Z"
# 其他数据模型引用原有文件
BillQueryRequest:
$ref: './schemas.yaml#/components/schemas/BillQueryRequest'
BillQueryResponse:
$ref: './schemas.yaml#/components/schemas/BillQueryResponse'
BillPayRequest:
$ref: './schemas.yaml#/components/schemas/BillPayRequest'
BillPayResponse:
$ref: './schemas.yaml#/components/schemas/BillPayResponse'
# 响应组件
responses:
# 业务错误响应
BusinessError:
description: 业务处理错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 系统错误响应
SystemError:
description: 系统异常错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 安全错误响应
SecurityError:
description: 安全验证错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 网络错误响应
NetworkError:
description: 网络通信错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 参数组件
parameters:
ContentTypeHeader:
name: Content-Type
in: header
required: true
description: 请求内容类型
schema:
type: string
enum:
- application/xml
- application/json
example: "application/xml"
EncryptTypeHeader:
name: X-Encrypt-Type
in: header
required: false
description: 加密算法类型
schema:
type: string
enum:
- "3DES"
- "SM2"
- "SM4"
example: "3DES"
EncryptModeHeader:
name: X-Encrypt-Mode
in: header
required: false
description: 加密模式
schema:
type: string
enum:
- "ECB"
- "CBC"
example: "ECB"
DataTypeHeader:
name: X-Data-Type
in: header
required: false
description: 数据格式类型
schema:
type: string
enum:
- "XML"
- "JSON"
example: "XML"
# 响应头组件
headers:
ContentType:
description: 响应内容类型
schema:
type: string
example: "application/xml; charset=GBK"
ResponseTime:
description: 响应时间(毫秒)
schema:
type: integer
example: 150
RequestId:
description: 请求唯一标识
schema:
type: string
example: "req-123456789012"
# 安全认证组件
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: API密钥认证
EncryptedData:
type: http
scheme: bearer
bearerFormat: encrypted
description: 加密数据传输认证

View File

@ -0,0 +1,67 @@
components:
parameters:
# 内容类型参数
ContentTypeHeader:
name: Content-Type
in: header
required: true
description: 内容类型
schema:
type: string
enum:
- "application/xml; charset=GBK"
- "application/json; charset=UTF-8"
default: "application/xml; charset=GBK"
# 加密类型参数
EncryptTypeHeader:
name: EncryptType
in: header
required: false
description: 加密类型
schema:
type: string
enum:
- "3DES"
- "SM2"
- "SM4"
example: "3DES"
# 加密模式参数
EncryptModeHeader:
name: EncryptMode
in: header
required: false
description: 加密模式
schema:
type: string
enum:
- "ECB"
- "CBC"
- "C1C3C2"
- "C1C2C3"
example: "ECB"
# 数据类型参数
DataTypeHeader:
name: DataType
in: header
required: false
description: 数据类型
schema:
type: string
enum:
- "XML"
- "JSON"
example: "XML"
# 版本号参数
VersionParam:
name: version
in: query
required: false
description: API版本号
schema:
type: string
default: "1.0.1"
example: "1.0.1"

View File

@ -0,0 +1,227 @@
components:
responses:
# 成功响应
Success:
description: 操作成功
content:
application/xml:
schema:
$ref: './schemas.yaml#/components/schemas/BaseResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>成功</RespMessage>
</out>
application/json:
schema:
$ref: './schemas.yaml#/components/schemas/BaseResponse'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "AAAAAAA"
RespMessage: "成功"
# 业务错误响应
BusinessError:
description: 业务处理错误
content:
application/xml:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>DEF0001</RespCode>
<RespMessage>无相应记录</RespMessage>
<ErrorDetail>
<ErrorCode>DEF0001</ErrorCode>
<ErrorMsg>用户编号123456不存在</ErrorMsg>
<ErrorTime>2024-01-01 12:00:00</ErrorTime>
</ErrorDetail>
</out>
application/json:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "DEF0001"
RespMessage: "无相应记录"
ErrorDetail:
ErrorCode: "DEF0001"
ErrorMsg: "用户编号123456不存在"
ErrorTime: "2024-01-01T12:00:00.000Z"
# 系统错误响应
SystemError:
description: 系统异常错误
content:
application/xml:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>SYS1001</RespCode>
<RespMessage>系统异常</RespMessage>
<ErrorDetail>
<ErrorCode>SYS1001</ErrorCode>
<ErrorMsg>数据库连接失败</ErrorMsg>
<ErrorTime>2024-01-01 12:00:00</ErrorTime>
</ErrorDetail>
</out>
application/json:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "SYS1001"
RespMessage: "系统异常"
ErrorDetail:
ErrorCode: "SYS1001"
ErrorMsg: "数据库连接失败"
ErrorTime: "2024-01-01T12:00:00.000Z"
# 安全错误响应
SecurityError:
description: 安全验证错误
content:
application/xml:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>SEC2001</RespCode>
<RespMessage>加密验证失败</RespMessage>
<ErrorDetail>
<ErrorCode>SEC2001</ErrorCode>
<ErrorMsg>数据解密失败</ErrorMsg>
<ErrorTime>2024-01-01 12:00:00</ErrorTime>
</ErrorDetail>
</out>
application/json:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "SEC2001"
RespMessage: "加密验证失败"
ErrorDetail:
ErrorCode: "SEC2001"
ErrorMsg: "数据解密失败"
ErrorTime: "2024-01-01T12:00:00.000Z"
# 网络错误响应
NetworkError:
description: 网络通信错误
content:
application/xml:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>NET3001</RespCode>
<RespMessage>网络超时</RespMessage>
<ErrorDetail>
<ErrorCode>NET3001</ErrorCode>
<ErrorMsg>请求超时,请稍后重试</ErrorMsg>
<ErrorTime>2024-01-01 12:00:00</ErrorTime>
</ErrorDetail>
</out>
application/json:
schema:
allOf:
- $ref: './schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: './schemas.yaml#/components/schemas/ErrorDetail'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "NET3001"
RespMessage: "网络超时"
ErrorDetail:
ErrorCode: "NET3001"
ErrorMsg: "请求超时,请稍后重试"
ErrorTime: "2024-01-01T12:00:00.000Z"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
components:
securitySchemes:
# API密钥认证
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: API密钥认证
# 加密数据认证
EncryptedData:
type: apiKey
in: header
name: X-Encrypted-Data
description: |
加密数据认证方式,支持以下加密算法:
- 3DES: 使用3DES ECB模式PKCS7填充
- SM2: 使用SM2非对称加密支持C1C3C2和C1C2C3模式
- SM4: 使用SM4对称加密支持ECB和CBC模式
加密后的数据需要进行Base64编码传输。
# 机构认证
InstAuth:
type: http
scheme: basic
description: 机构编码认证基于HTTP Basic Auth
# 数字签名认证
DigitalSign:
type: apiKey
in: header
name: X-Digital-Sign
description: |
数字签名认证,使用以下方式:
1. 将请求数据按指定规则排序
2. 使用指定密钥进行签名
3. 将签名结果Base64编码后放入请求头
支持的签名算法:
- SHA256withRSA
- SM3withSM2

View File

@ -0,0 +1,381 @@
openapi: 3.0.3
info:
title: 营收系统接口API
description: |
营收系统与银行/第三方支付机构之间的接口交互API文档
## 特性
- 支持账单查询、缴费、红冲等核心功能
- 支持银行代扣签约、解约、送盘、回盘
- 支持XML和JSON两种数据格式
- 支持多种加密算法3DES、SM2、SM4
- 统一错误码和响应格式
## 安全认证
本API使用多种加密方式保证数据安全传输
- 数据加密后Base64编码传输
- 支持3DES、SM2、SM4等加密算法
- 通过请求头指定加密类型和模式
version: 1.0.0
contact:
name: 营收系统API支持
email: support@billing-system.com
license:
name: 专有许可证
servers:
- url: https://api.billing-system.com
description: 生产环境
- url: https://test-api.billing-system.com
description: 测试环境
- url: https://dev-api.billing-system.com
description: 开发环境
tags:
- name: 账单管理
description: 账单查询、缴费、红冲相关接口
- name: 代扣管理
description: 银行代扣签约、解约、送盘、回盘相关接口
paths:
# 账单查询
/api/app/payCeb/getChargeSearch:
$ref: './paths/bill-query.yaml#/BillQuery'
# 账单缴费
/api/app/payCeb/getChargeOffs:
$ref: './paths/bill-pay.yaml#/BillPay'
# 账单红冲
/api/app/payInvalid/payInvalid:
$ref: './paths/pay-invalid.yaml#/PayInvalid'
# 代扣签约
/api/app/bankWithholding/signing:
$ref: './paths/withholding-signing.yaml#/WithholdingSigning'
# 代扣解约
/api/app/bankWithholding/termination:
$ref: './paths/withholding-termination.yaml#/WithholdingTermination'
# 代扣送盘
/api/app/bankWithholding/sendDisc:
$ref: './paths/withholding-send-disc.yaml#/WithholdingSendDisc'
# 代扣回盘
/api/app/bankWithholding/backDisc:
$ref: './paths/withholding-back-disc.yaml#/WithholdingBackDisc'
# 对账接口
/api/app/payCeb/paymentCheck:
$ref: './paths/payment-check.yaml#/PaymentCheck'
# 取消代扣交易
/api/app/bankWithholding/cancelDisc:
$ref: './paths/withholding-cancel-disc.yaml#/WithholdingCancelDisc'
# 代扣送盘状态查询
/api/app/bankWithholding/sendDiscCheck:
$ref: './paths/withholding-send-disc-check.yaml#/WithholdingSendDiscCheck'
# 代扣回盘状态查询
/api/app/bankWithholding/backDiscCheck:
$ref: './paths/withholding-back-disc-check.yaml#/WithholdingBackDiscCheck'
# 客户基本信息查询
/api/app/customer/check:
$ref: './paths/customer-check.yaml#/CustomerCheck'
components:
# 数据模型
schemas:
# 基础请求模型
BaseRequest:
type: object
required:
- Version
- InstId
- TranCode
- TranDate
- TranSeq
properties:
Version:
type: string
description: 版本号
example: "1.0.1"
InstId:
type: string
description: 机构编码
example: "00001"
maxLength: 30
TranCode:
type: string
description: 交易码
example: "Query"
maxLength: 20
TranDate:
type: string
description: 交易日期
pattern: '^\d{8}$'
example: "20240101"
TranSeq:
type: string
description: 交易流水号(银行生成的唯一标识)
example: "123456789012"
maxLength: 40
# 基础响应模型
BaseResponse:
type: object
required:
- Version
- InstId
- TranCode
- TranDate
- TranSeq
- RespCode
- RespMessage
properties:
Version:
type: string
description: 版本号
example: "1.0.1"
InstId:
type: string
description: 机构编码
example: "00001"
TranCode:
type: string
description: 交易码
example: "QueryRes"
TranDate:
type: string
description: 交易日期
pattern: '^\d{8}$'
example: "20240101"
TranSeq:
type: string
description: 交易流水号
example: "123456789012"
RespCode:
type: string
description: 返回码
enum:
- "AAAAAAA" # 成功
- "DEF0001" # 无相应记录
- "DEF0002" # 缴费金额不匹配
- "SYS1001" # 系统异常
- "SEC2001" # 加密错误
example: "AAAAAAA"
RespMessage:
type: string
description: 返回消息
example: "成功"
maxLength: 60
# 错误详情
ErrorDetail:
type: object
properties:
ErrorCode:
type: string
description: 错误码
example: "DEF0001"
ErrorMsg:
type: string
description: 错误消息
example: "用户编号123456不存在"
ErrorTime:
type: string
format: date-time
description: 错误时间
example: "2024-01-01T12:00:00.000Z"
# 其他数据模型引用原有文件
BillQueryRequest:
$ref: './components/schemas.yaml#/components/schemas/BillQueryRequest'
BillQueryResponse:
$ref: './components/schemas.yaml#/components/schemas/BillQueryResponse'
BillPayRequest:
$ref: './components/schemas.yaml#/components/schemas/BillPayRequest'
BillPayResponse:
$ref: './components/schemas.yaml#/components/schemas/BillPayResponse'
# 响应组件
responses:
# 业务错误响应
BusinessError:
description: 业务处理错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 系统错误响应
SystemError:
description: 系统异常错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 安全错误响应
SecurityError:
description: 安全验证错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 网络错误响应
NetworkError:
description: 网络通信错误
content:
application/xml:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
ErrorDetail:
$ref: '#/components/schemas/ErrorDetail'
# 参数组件
parameters:
ContentTypeHeader:
name: Content-Type
in: header
required: true
description: 请求内容类型
schema:
type: string
enum:
- application/xml
- application/json
example: "application/xml"
EncryptTypeHeader:
name: X-Encrypt-Type
in: header
required: false
description: 加密算法类型
schema:
type: string
enum:
- "3DES"
- "SM2"
- "SM4"
example: "3DES"
EncryptModeHeader:
name: X-Encrypt-Mode
in: header
required: false
description: 加密模式
schema:
type: string
enum:
- "ECB"
- "CBC"
example: "ECB"
DataTypeHeader:
name: X-Data-Type
in: header
required: false
description: 数据格式类型
schema:
type: string
enum:
- "XML"
- "JSON"
example: "XML"
# 响应头组件
headers:
ContentType:
description: 响应内容类型
schema:
type: string
example: "application/xml; charset=GBK"
ResponseTime:
description: 响应时间(毫秒)
schema:
type: integer
example: 150
RequestId:
description: 请求唯一标识
schema:
type: string
example: "req-123456789012"
# 安全认证组件
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: API密钥认证
EncryptedData:
type: http
scheme: bearer
bearerFormat: encrypted
description: 加密数据传输认证
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,140 @@
BillPay:
post:
tags:
- 账单管理
summary: 账单缴费
description: |
执行账单缴费操作,支持多种支付渠道。
## 业务说明
- 支持指定客户编号和缴费金额进行缴费
- 支持多种二级支付渠道(支付宝、微信等)
- 实时更新账单状态和生成交易记录
- 支持XML和JSON两种数据格式
- 缴费金额必须与账单金额完全匹配
## 业务规则
- 缴费金额必须大于0.01元
- 账单状态必须为未缴费状态
- 同一笔账单不能重复缴费
- 缴费成功后账单状态自动更新为已缴费
## 调用频率限制
- 单个客户编号每分钟最多缴费5次
- 单个机构每分钟最多缴费500次
operationId: payBill
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 账单缴费请求数据
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillPayRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Pay</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<PayAmount>150.00</PayAmount>
<SubChannel>1</SubChannel>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillPayRequest'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "Pay"
TranDate: "20240101"
TranSeq: "123456789012"
BillKey: "123456"
CompanyId: "654321"
PayAmount: 150.00
SubChannel: 1
responses:
'200':
description: 缴费成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillPayResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>缴费成功</RespMessage>
<Data>
<Transaction>
<TranSeq>TXN123456789012</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<TranCode>Pay</TranCode>
<PayAmount>150.00</PayAmount>
<PayDate>2024-01-01T12:00:00.000Z</PayDate>
<SubChannel>1</SubChannel>
<TranStatus>1</TranStatus>
<RespCode>AAAAAAA</RespCode>
<RespMessage>成功</RespMessage>
</Transaction>
</Data>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillPayResponse'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "PayRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "AAAAAAA"
RespMessage: "缴费成功"
Data:
Transaction:
TranSeq: "TXN123456789012"
BillKey: "123456"
CompanyId: "654321"
TranCode: "Pay"
PayAmount: 150.00
PayDate: "2024-01-01T12:00:00.000Z"
SubChannel: 1
TranStatus: 1
RespCode: "AAAAAAA"
RespMessage: "成功"
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,136 @@
BillQuery:
post:
tags:
- 账单管理
summary: 账单查询
description: |
根据客户编号查询账单信息,包括客户基本信息和账单详情。
## 业务说明
- 支持根据客户编号(缴费号)查询账单
- 返回客户基本信息和未缴费账单列表
- 支持XML和JSON两种数据格式
- 数据传输支持多种加密方式
## 调用频率限制
- 单个客户编号每分钟最多查询10次
- 单个机构每分钟最多查询1000次
operationId: queryBill
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 账单查询请求数据
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillQueryRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Query</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillQueryRequest'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "Query"
TranDate: "20240101"
TranSeq: "123456789012"
BillKey: "123456"
CompanyId: "654321"
responses:
'200':
description: 查询成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillQueryResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>成功</RespMessage>
<Data>
<Customer>
<BillKey>123456</BillKey>
<CustomerName>张三</CustomerName>
<ContractNo>CONTRACT001</ContractNo>
<CompanyId>654321</CompanyId>
</Customer>
<Bills>
<Bill>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<PayAmount>150.00</PayAmount>
<Balance>0.00</Balance>
<BeginDate>2024-01-01</BeginDate>
<EndDate>2024-01-31</EndDate>
<BillStatus>0</BillStatus>
</Bill>
</Bills>
</Data>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/BillQueryResponse'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "QueryRes"
TranDate: "20240101"
TranSeq: "123456789012"
RespCode: "AAAAAAA"
RespMessage: "成功"
Data:
Customer:
BillKey: "123456"
CustomerName: "张三"
ContractNo: "CONTRACT001"
CompanyId: "654321"
Bills:
- BillKey: "123456"
CompanyId: "654321"
PayAmount: 150.00
Balance: 0.00
BeginDate: "2024-01-01"
EndDate: "2024-01-31"
BillStatus: 0
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,107 @@
CustomerCheck:
post:
tags:
- 账单管理
summary: 客户基本信息查询
description: |
银行向公用事业单位发起的客户基本信息查询请求接口。
用于查询客户的基本信息,包括客户姓名、联系方式、地址等详细信息。
## 交易码说明
- 请求交易码CustomerCheck
- 应答交易码CustomerCheckRes
## 查询信息包括
- 客户基本信息:姓名、身份证号等
- 客户联系信息:电话、地址等
- 客户状态信息:账户状态、服务状态等
- 历史缴费记录摘要
## 使用场景
- 客户信息核验
- 代扣协议签约前的客户信息确认
- 客户服务和支持
- 风险评估和反欺诈检查
operationId: customerCheck
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/CustomerCheckRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>CustomerCheck</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<queryType>0</queryType>
<contractNo>CONTRACT001</contractNo>
<filed1></filed1>
<filed2></filed2>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/CustomerCheckRequest'
responses:
'200':
description: 客户基本信息查询成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/CustomerCheckResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>CustomerCheckRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>查询成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<contractNo>CONTRACT001</contractNo>
<customerName>张三</customerName>
<customerPhone>13812345678</customerPhone>
<customerAddress>北京市朝阳区xxx街道xxx号</customerAddress>
<customerStatus>1</customerStatus>
<serviceStatus>1</serviceStatus>
<registerDate>20200101</registerDate>
<lastPayDate>20231215</lastPayDate>
<totalPayCount>36</totalPayCount>
<totalPayAmount>540000</totalPayAmount>
<filed1></filed1>
<filed2></filed2>
<filed3></filed3>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/CustomerCheckResponse'
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'403':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,192 @@
PayInvalid:
post:
tags:
- 账单管理
summary: 账单红冲
description: |
对已缴费的账单进行红冲(撤销)操作。
## 业务说明
- 支持对已缴费账单进行红冲操作
- 红冲后账单状态恢复为未缴费
- 生成红冲交易记录
- 支持XML和JSON两种数据格式
## 业务规则
- 只能对已缴费的账单进行红冲
- 红冲金额必须与原缴费金额一致
- 同一笔缴费记录只能红冲一次
- 红冲成功后生成负数交易记录
## 调用频率限制
- 单个交易流水号每分钟最多红冲1次
- 单个机构每分钟最多红冲100次
operationId: invalidPayment
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 账单红冲请求数据
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- OriginalTranSeq
- BillKey
- CompanyId
properties:
OriginalTranSeq:
type: string
description: 原交易流水号
example: "TXN123456789012"
maxLength: 40
BillKey:
type: string
description: 客户编号
example: "123456"
maxLength: 35
CompanyId:
type: string
description: 机构编码
example: "654321"
maxLength: 30
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayInvalid</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789013</TranSeq>
<OriginalTranSeq>TXN123456789012</OriginalTranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
</in>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- OriginalTranSeq
- BillKey
- CompanyId
properties:
OriginalTranSeq:
type: string
description: 原交易流水号
example: "TXN123456789012"
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "PayInvalid"
TranDate: "20240101"
TranSeq: "123456789013"
OriginalTranSeq: "TXN123456789012"
BillKey: "123456"
CompanyId: "654321"
responses:
'200':
description: 红冲成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Transaction:
$ref: '../components/schemas.yaml#/components/schemas/Transaction'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayInvalidRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789013</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>红冲成功</RespMessage>
<Data>
<Transaction>
<TranSeq>REV123456789013</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<TranCode>PayInvalid</TranCode>
<PayAmount>-150.00</PayAmount>
<PayDate>2024-01-01T12:00:00.000Z</PayDate>
<TranStatus>1</TranStatus>
<RespCode>AAAAAAA</RespCode>
<RespMessage>成功</RespMessage>
</Transaction>
</Data>
</out>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Transaction:
$ref: '../components/schemas.yaml#/components/schemas/Transaction'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "PayInvalidRes"
TranDate: "20240101"
TranSeq: "123456789013"
RespCode: "AAAAAAA"
RespMessage: "红冲成功"
Data:
Transaction:
TranSeq: "REV123456789013"
BillKey: "123456"
CompanyId: "654321"
TranCode: "PayInvalid"
PayAmount: -150.00
PayDate: "2024-01-01T12:00:00.000Z"
TranStatus: 1
RespCode: "AAAAAAA"
RespMessage: "成功"
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,92 @@
PaymentCheck:
post:
tags:
- 账单管理
summary: 缴费单对账
description: |
代理收费向公用事业单位发起的缴费单对账请求接口。
由代理收费公司每天日切后,进行批处理,自动生成和传送对账文件给公用事业单位。
## 交易码说明
- 请求交易码PayCheck
- 应答交易码PayCheckRes
## 对账文件格式
对账文件为文本文件txt格式编码格式为UTF-8包括明细行和汇总行。
行内每个分项之间以"|"为分隔符。
### 汇总行格式
`交易笔数|总金额`
### 明细行格式
`交易日期|交易流水号|客户编号|缴费金额|二级渠道|交易类型`
operationId: paymentCheck
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/PaymentCheckRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayCheck</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<companyId>654321</companyId>
<payDate>20240101</payDate>
<payCount>10</payCount>
<payMoney>150000</payMoney>
<fileName>654321_20240101.txt</fileName>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/PaymentCheckRequest'
responses:
'200':
description: 对账成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/PaymentCheckResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayCheckRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>对账成功</RespMessage>
<companyId>654321</companyId>
<payDate>20240101</payDate>
<payAmount>150000</payAmount>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/PaymentCheckResponse'
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'403':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,104 @@
WithholdingBackDiscCheck:
post:
tags:
- 代扣管理
summary: 代扣回盘状态查询
description: |
银行向公用事业单位发起的代扣回盘状态查询请求接口。
用于查询已发起的代扣回盘交易的当前处理状态。
## 交易码说明
- 请求交易码BackDiscCheck
- 应答交易码BackDiscCheckRes
## 查询状态说明
- 0: 待处理
- 1: 处理中
- 2: 处理成功
- 3: 处理失败
- 4: 已取消
## 使用场景
- 银行系统需要确认回盘交易状态
- 处理异常情况的状态核查
- 定时批量状态查询
- 对账和清算流程中的状态确认
operationId: withholdingBackDiscCheck
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingBackDiscCheckRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>BackDiscCheck</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<originalTranDate>20240101</originalTranDate>
<contractNo>CONTRACT001</contractNo>
<batchNo>BATCH20240101001</batchNo>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingBackDiscCheckRequest'
responses:
'200':
description: 代扣回盘状态查询成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingBackDiscCheckResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>BackDiscCheckRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>查询成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<discStatus>2</discStatus>
<discStatusDesc>处理成功</discStatusDesc>
<discTime>20240101120000</discTime>
<payAmount>15000</payAmount>
<actualPayAmount>15000</actualPayAmount>
<batchNo>BATCH20240101001</batchNo>
<clearingDate>20240102</clearingDate>
<failReason></failReason>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingBackDiscCheckResponse'
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'403':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,334 @@
WithholdingBackDisc:
post:
tags:
- 代扣管理
summary: 代扣回盘
description: |
银行代扣回盘接口,接收银行返回的代扣结果。
## 业务说明
- 接收银行处理代扣送盘后的结果反馈
- 包含每笔代扣的成功或失败信息
- 根据回盘结果更新账单和交易状态
- 支持XML和JSON两种数据格式
- 自动处理代扣成功和失败的业务逻辑
## 业务规则
- 回盘数据必须与送盘数据对应
- 成功的代扣自动更新账单状态为已缴费
- 失败的代扣保持原账单状态
- 生成相应的交易记录和状态
## 调用频率限制
- 单个批次只能回盘一次
- 支持银行异步回盘处理
operationId: withholdingBackDisc
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 代扣回盘请求数据
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BatchNo
- TotalCount
- SuccessCount
- FailCount
- ResultList
properties:
BatchNo:
type: string
description: 原送盘批次号
example: "BATCH20240101001"
maxLength: 50
TotalCount:
type: integer
description: 总笔数
example: 2
minimum: 1
SuccessCount:
type: integer
description: 成功笔数
example: 1
minimum: 0
FailCount:
type: integer
description: 失败笔数
example: 1
minimum: 0
ResultList:
type: array
description: 代扣结果列表
items:
type: object
required:
- BillKey
- CompanyId
- AgreementNo
- PayAmount
- ResultCode
- ResultMessage
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AgreementNo:
type: string
description: 协议号
example: "AGR001"
PayAmount:
type: number
format: decimal
description: 代扣金额
example: 150.00
ResultCode:
type: string
description: 代扣结果码
enum: ["SUCCESS", "FAIL"]
example: "SUCCESS"
ResultMessage:
type: string
description: 代扣结果信息
example: "代扣成功"
FailReason:
type: string
description: 失败原因结果为FAIL时必填
example: "余额不足"
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>BackDisc</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789017</TranSeq>
<BatchNo>BATCH20240101001</BatchNo>
<TotalCount>2</TotalCount>
<SuccessCount>1</SuccessCount>
<FailCount>1</FailCount>
<ResultList>
<Result>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR001</AgreementNo>
<PayAmount>150.00</PayAmount>
<ResultCode>SUCCESS</ResultCode>
<ResultMessage>代扣成功</ResultMessage>
</Result>
<Result>
<BillKey>123457</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR002</AgreementNo>
<PayAmount>150.00</PayAmount>
<ResultCode>FAIL</ResultCode>
<ResultMessage>代扣失败</ResultMessage>
<FailReason>余额不足</FailReason>
</Result>
</ResultList>
</in>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BatchNo
- TotalCount
- SuccessCount
- FailCount
- ResultList
properties:
BatchNo:
type: string
description: 原送盘批次号
example: "BATCH20240101001"
TotalCount:
type: integer
description: 总笔数
example: 2
SuccessCount:
type: integer
description: 成功笔数
example: 1
FailCount:
type: integer
description: 失败笔数
example: 1
ResultList:
type: array
description: 代扣结果列表
items:
type: object
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AgreementNo:
type: string
description: 协议号
example: "AGR001"
PayAmount:
type: number
format: decimal
description: 代扣金额
example: 150.00
ResultCode:
type: string
description: 代扣结果码
example: "SUCCESS"
ResultMessage:
type: string
description: 代扣结果信息
example: "代扣成功"
FailReason:
type: string
description: 失败原因
example: "余额不足"
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "BackDisc"
TranDate: "20240101"
TranSeq: "123456789017"
BatchNo: "BATCH20240101001"
TotalCount: 2
SuccessCount: 1
FailCount: 1
ResultList:
- BillKey: "123456"
CompanyId: "654321"
AgreementNo: "AGR001"
PayAmount: 150.00
ResultCode: "SUCCESS"
ResultMessage: "代扣成功"
- BillKey: "123457"
CompanyId: "654321"
AgreementNo: "AGR002"
PayAmount: 150.00
ResultCode: "FAIL"
ResultMessage: "代扣失败"
FailReason: "余额不足"
responses:
'200':
description: 回盘处理成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
ProcessedCount:
type: integer
description: 已处理笔数
example: 2
UpdatedBills:
type: integer
description: 更新账单数
example: 1
CreatedTransactions:
type: integer
description: 创建交易数
example: 2
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>BackDiscRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789017</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>回盘处理成功</RespMessage>
<Data>
<BatchNo>BATCH20240101001</BatchNo>
<ProcessedCount>2</ProcessedCount>
<UpdatedBills>1</UpdatedBills>
<CreatedTransactions>2</CreatedTransactions>
</Data>
</out>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
ProcessedCount:
type: integer
description: 已处理笔数
example: 2
UpdatedBills:
type: integer
description: 更新账单数
example: 1
CreatedTransactions:
type: integer
description: 创建交易数
example: 2
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "BackDiscRes"
TranDate: "20240101"
TranSeq: "123456789017"
RespCode: "AAAAAAA"
RespMessage: "回盘处理成功"
Data:
BatchNo: "BATCH20240101001"
ProcessedCount: 2
UpdatedBills: 1
CreatedTransactions: 2
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,96 @@
WithholdingCancelDisc:
post:
tags:
- 代扣管理
summary: 取消代扣交易
description: |
银行向公用事业单位发起的取消代扣交易请求接口。
用于取消已经发起但尚未完成的代扣交易。
## 交易码说明
- 请求交易码CancelDisc
- 应答交易码CancelDiscRes
## 使用场景
- 代扣交易发起后,用户要求取消
- 代扣交易异常需要撤销
- 银行系统故障需要回滚交易
## 注意事项
- 只能取消当天发起的代扣交易
- 已经成功扣款的交易不能取消,需要通过退款流程处理
- 取消成功后,相关的代扣协议仍然有效
operationId: withholdingCancelDisc
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingCancelDiscRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>CancelDisc</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<originalTranDate>20240101</originalTranDate>
<cancelReason>用户申请取消</cancelReason>
<contractNo>CONTRACT001</contractNo>
<payAmount>15000</payAmount>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingCancelDiscRequest'
responses:
'200':
description: 取消代扣交易成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingCancelDiscResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>CancelDiscRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>取消代扣交易成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<cancelStatus>1</cancelStatus>
<cancelTime>20240101120000</cancelTime>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingCancelDiscResponse'
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'403':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,99 @@
WithholdingSendDiscCheck:
post:
tags:
- 代扣管理
summary: 代扣送盘状态查询
description: |
银行向公用事业单位发起的代扣送盘状态查询请求接口。
用于查询已发起的代扣送盘交易的当前处理状态。
## 交易码说明
- 请求交易码SendDiscCheck
- 应答交易码SendDiscCheckRes
## 查询状态说明
- 0: 待处理
- 1: 处理中
- 2: 处理成功
- 3: 处理失败
- 4: 已取消
## 使用场景
- 银行系统需要确认送盘交易状态
- 处理异常情况的状态核查
- 定时批量状态查询
operationId: withholdingSendDiscCheck
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingSendDiscCheckRequest'
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>SendDiscCheck</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<originalTranDate>20240101</originalTranDate>
<contractNo>CONTRACT001</contractNo>
</in>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingSendDiscCheckRequest'
responses:
'200':
description: 代扣送盘状态查询成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingSendDiscCheckResponse'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>SendDiscCheckRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>查询成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<originalTranSeq>ORIG123456789012</originalTranSeq>
<discStatus>2</discStatus>
<discStatusDesc>处理成功</discStatusDesc>
<discTime>20240101120000</discTime>
<payAmount>15000</payAmount>
<failReason></failReason>
</out>
application/json:
schema:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingSendDiscCheckResponse'
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'403':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,287 @@
WithholdingSendDisc:
post:
tags:
- 代扣管理
summary: 代扣送盘
description: |
银行代扣送盘接口,向银行发送批量代扣请求。
## 业务说明
- 支持批量向银行发送代扣请求
- 包含待代扣的账单信息和客户信息
- 支持本行和他行账户代扣
- 送盘成功后等待银行回盘确认
- 支持XML和JSON两种数据格式
## 业务规则
- 只能对已签约且有效的协议进行代扣
- 代扣金额必需与账单金额一致
- 客户账户余额必须充足
- 送盘成功后生成代扣交易记录
## 调用频率限制
- 单个批次最多包含1000笔代扣
- 单个机构每小时最多送盘10次
operationId: withholdingSendDisc
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 代扣送盘请求数据
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BatchNo
- TotalCount
- TotalAmount
- WithholdingList
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
maxLength: 50
TotalCount:
type: integer
description: 总笔数
example: 10
minimum: 1
maximum: 1000
TotalAmount:
type: number
format: decimal
description: 总金额
example: 1500.00
minimum: 0.01
WithholdingList:
type: array
description: 代扣明细列表
items:
type: object
required:
- BillKey
- CompanyId
- AgreementNo
- PayAmount
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AgreementNo:
type: string
description: 协议号
example: "AGR001"
PayAmount:
type: number
format: decimal
description: 代扣金额
example: 150.00
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>SendDisc</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789016</TranSeq>
<BatchNo>BATCH20240101001</BatchNo>
<TotalCount>2</TotalCount>
<TotalAmount>300.00</TotalAmount>
<WithholdingList>
<Withholding>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR001</AgreementNo>
<PayAmount>150.00</PayAmount>
</Withholding>
<Withholding>
<BillKey>123457</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR002</AgreementNo>
<PayAmount>150.00</PayAmount>
</Withholding>
</WithholdingList>
</in>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BatchNo
- TotalCount
- TotalAmount
- WithholdingList
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
TotalCount:
type: integer
description: 总笔数
example: 2
TotalAmount:
type: number
format: decimal
description: 总金额
example: 300.00
WithholdingList:
type: array
description: 代扣明细列表
items:
type: object
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AgreementNo:
type: string
description: 协议号
example: "AGR001"
PayAmount:
type: number
format: decimal
description: 代扣金额
example: 150.00
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "SendDisc"
TranDate: "20240101"
TranSeq: "123456789016"
BatchNo: "BATCH20240101001"
TotalCount: 2
TotalAmount: 300.00
WithholdingList:
- BillKey: "123456"
CompanyId: "654321"
AgreementNo: "AGR001"
PayAmount: 150.00
- BillKey: "123457"
CompanyId: "654321"
AgreementNo: "AGR002"
PayAmount: 150.00
responses:
'200':
description: 送盘成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
ProcessedCount:
type: integer
description: 已处理笔数
example: 2
SuccessCount:
type: integer
description: 成功笔数
example: 2
FailCount:
type: integer
description: 失败笔数
example: 0
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>SendDiscRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789016</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>送盘成功</RespMessage>
<Data>
<BatchNo>BATCH20240101001</BatchNo>
<ProcessedCount>2</ProcessedCount>
<SuccessCount>2</SuccessCount>
<FailCount>0</FailCount>
</Data>
</out>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
BatchNo:
type: string
description: 批次号
example: "BATCH20240101001"
ProcessedCount:
type: integer
description: 已处理笔数
example: 2
SuccessCount:
type: integer
description: 成功笔数
example: 2
FailCount:
type: integer
description: 失败笔数
example: 0
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "SendDiscRes"
TranDate: "20240101"
TranSeq: "123456789016"
RespCode: "AAAAAAA"
RespMessage: "送盘成功"
Data:
BatchNo: "BATCH20240101001"
ProcessedCount: 2
SuccessCount: 2
FailCount: 0
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,243 @@
WithholdingSigning:
post:
tags:
- 代扣管理
summary: 代扣签约
description: |
银行代扣业务签约接口,建立客户与银行的代扣协议。
## 业务说明
- 支持客户与银行签署代扣协议
- 包含客户基本信息和银行账户信息
- 支持本行和他行账户签约
- 签约成功后可进行自动代扣
- 支持XML和JSON两种数据格式
## 业务规则
- 客户编号必须在系统中存在
- 银行账户信息必须真实有效
- 同一客户同一银行账户只能签约一次
- 签约成功后协议状态为已签约
## 调用频率限制
- 单个客户编号每天最多签约5次
- 单个机构每分钟最多签约50次
operationId: withholdingSigning
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 代扣签约请求数据
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BillKey
- CompanyId
- AccountName
- AccountNo
- BankName
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
maxLength: 35
CompanyId:
type: string
description: 机构编码
example: "654321"
maxLength: 30
AccountName:
type: string
description: 开户名
example: "张三"
maxLength: 150
AccountNo:
type: string
description: 开户账号
example: "6222001234567890"
maxLength: 30
BankName:
type: string
description: 银行名称
example: "中国工商银行"
maxLength: 150
ContractNo:
type: string
description: 合同号
example: "CONTRACT001"
maxLength: 150
BankType:
type: integer
description: 银行类型
enum: [0, 1] # 0:本行 1:他行
example: 0
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Signing</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789014</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AccountName>张三</AccountName>
<AccountNo>6222001234567890</AccountNo>
<BankName>中国工商银行</BankName>
<ContractNo>CONTRACT001</ContractNo>
<BankType>0</BankType>
</in>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BillKey
- CompanyId
- AccountName
- AccountNo
- BankName
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AccountName:
type: string
description: 开户名
example: "张三"
AccountNo:
type: string
description: 开户账号
example: "6222001234567890"
BankName:
type: string
description: 银行名称
example: "中国工商银行"
ContractNo:
type: string
description: 合同号
example: "CONTRACT001"
BankType:
type: integer
description: 银行类型
example: 0
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "Signing"
TranDate: "20240101"
TranSeq: "123456789014"
BillKey: "123456"
CompanyId: "654321"
AccountName: "张三"
AccountNo: "6222001234567890"
BankName: "中国工商银行"
ContractNo: "CONTRACT001"
BankType: 0
responses:
'200':
description: 签约成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Agreement:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingAgreement'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>SigningRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789014</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>签约成功</RespMessage>
<Data>
<Agreement>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AccountName>张三</AccountName>
<AccountNo>6222001234567890</AccountNo>
<BankName>中国工商银行</BankName>
<ContractNo>CONTRACT001</ContractNo>
<AgreementNo>AGR001</AgreementNo>
<BankType>0</BankType>
<AgreementStatus>1</AgreementStatus>
<SigningDate>2024-01-01</SigningDate>
</Agreement>
</Data>
</out>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Agreement:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingAgreement'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "SigningRes"
TranDate: "20240101"
TranSeq: "123456789014"
RespCode: "AAAAAAA"
RespMessage: "签约成功"
Data:
Agreement:
BillKey: "123456"
CompanyId: "654321"
AccountName: "张三"
AccountNo: "6222001234567890"
BankName: "中国工商银行"
ContractNo: "CONTRACT001"
AgreementNo: "AGR001"
BankType: 0
AgreementStatus: 1
SigningDate: "2024-01-01"
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

View File

@ -0,0 +1,184 @@
WithholdingTermination:
post:
tags:
- 代扣管理
summary: 代扣解约
description: |
银行代扣业务解约接口,终止客户与银行的代扣协议。
## 业务说明
- 支持客户解除与银行的代扣协议
- 解约后不能再进行自动代扣
- 支持XML和JSON两种数据格式
- 解约后协议状态变更为已解约
## 业务规则
- 只能解约已签约状态的协议
- 解约成功后协议状态更新为已解约
- 同一协议只能解约一次
- 解约不影响已产生的交易记录
## 调用频率限制
- 单个协议号每天最多解约1次
- 单个机构每分钟最多解约20次
operationId: withholdingTermination
parameters:
- $ref: '../components/parameters.yaml#/components/parameters/ContentTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptTypeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/EncryptModeHeader'
- $ref: '../components/parameters.yaml#/components/parameters/DataTypeHeader'
requestBody:
required: true
description: 代扣解约请求数据
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BillKey
- CompanyId
- AgreementNo
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
maxLength: 35
CompanyId:
type: string
description: 机构编码
example: "654321"
maxLength: 30
AgreementNo:
type: string
description: 协议号
example: "AGR001"
maxLength: 150
example: |
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Termination</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789015</TranSeq>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR001</AgreementNo>
</in>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseRequest'
- type: object
required:
- BillKey
- CompanyId
- AgreementNo
properties:
BillKey:
type: string
description: 客户编号
example: "123456"
CompanyId:
type: string
description: 机构编码
example: "654321"
AgreementNo:
type: string
description: 协议号
example: "AGR001"
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "Termination"
TranDate: "20240101"
TranSeq: "123456789015"
BillKey: "123456"
CompanyId: "654321"
AgreementNo: "AGR001"
responses:
'200':
description: 解约成功
headers:
Content-Type:
$ref: '../components/headers.yaml#/components/headers/ContentType'
X-Response-Time:
$ref: '../components/headers.yaml#/components/headers/ResponseTime'
X-Request-Id:
$ref: '../components/headers.yaml#/components/headers/RequestId'
content:
application/xml:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Agreement:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingAgreement'
example: |
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>TerminationRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789015</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>解约成功</RespMessage>
<Data>
<Agreement>
<BillKey>123456</BillKey>
<CompanyId>654321</CompanyId>
<AgreementNo>AGR001</AgreementNo>
<AgreementStatus>2</AgreementStatus>
<TerminationDate>2024-01-01</TerminationDate>
</Agreement>
</Data>
</out>
application/json:
schema:
allOf:
- $ref: '../components/schemas.yaml#/components/schemas/BaseResponse'
- type: object
properties:
Data:
type: object
properties:
Agreement:
$ref: '../components/schemas.yaml#/components/schemas/WithholdingAgreement'
example:
Version: "1.0.1"
InstId: "00001"
TranCode: "TerminationRes"
TranDate: "20240101"
TranSeq: "123456789015"
RespCode: "AAAAAAA"
RespMessage: "解约成功"
Data:
Agreement:
BillKey: "123456"
CompanyId: "654321"
AgreementNo: "AGR001"
AgreementStatus: 2
TerminationDate: "2024-01-01"
'400':
$ref: '../components/responses.yaml#/components/responses/BusinessError'
'500':
$ref: '../components/responses.yaml#/components/responses/SystemError'
'401':
$ref: '../components/responses.yaml#/components/responses/SecurityError'
'408':
$ref: '../components/responses.yaml#/components/responses/NetworkError'
security:
- ApiKeyAuth: []
- EncryptedData: []

119
docs/api/openapi/serve.js Normal file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env node
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const PORT = 3001;
// 静态文件服务
app.use('/docs', express.static(path.join(__dirname)));
// 主页路由
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>营收系统接口文档</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 8px; margin-bottom: 30px; }
.card { background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #007bff; }
.btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 10px 5px; }
.btn:hover { background: #0056b3; }
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
.feature { margin: 10px 0; }
.feature strong { color: #2c3e50; }
</style>
</head>
<body>
<div class="header">
<h1>🏦 营收系统接口OpenAPI文档</h1>
<p>银行/第三方支付机构接口交互API文档 - 基于OpenAPI 3.0.3规范</p>
</div>
<div class="card">
<h2>📖 文档访问</h2>
<a href="https://editor.swagger.io/?url=${req.protocol}://${req.get('host')}/docs/main/openapi.yaml" class="btn" target="_blank">
在Swagger Editor中打开
</a>
<a href="/docs/main/openapi.yaml" class="btn" target="_blank">
查看原始YAML
</a>
<a href="/docs/README.md" class="btn" target="_blank">
使用说明
</a>
</div>
<div class="card">
<h2>🚀 主要功能</h2>
<div class="feature"><strong>账单管理:</strong> </div>
<div class="feature"><strong>代扣管理:</strong> </div>
<div class="feature"><strong>多格式支持:</strong> XML (GBK) JSON (UTF-8)</div>
<div class="feature"><strong>安全认证:</strong> 3DESSM2SM4</div>
<div class="feature"><strong>错误处理:</strong> </div>
</div>
<div class="card">
<h2>📋 API概览</h2>
<ul>
<li><strong>POST</strong> /api/app/billQuery/query - </li>
<li><strong>POST</strong> /api/app/billPay/pay - </li>
<li><strong>POST</strong> /api/app/payInvalid/payInvalid - </li>
<li><strong>POST</strong> /api/app/bankWithholding/signing - </li>
<li><strong>POST</strong> /api/app/bankWithholding/termination - </li>
<li><strong>POST</strong> /api/app/bankWithholding/sendDisc - </li>
<li><strong>POST</strong> /api/app/bankWithholding/backDisc - </li>
</ul>
</div>
<div class="card">
<h2>🔧 快速测试</h2>
<p>账单查询示例JSON格式:</p>
<pre>{
"Version": "1.0.1",
"InstId": "00001",
"TranCode": "Query",
"TranDate": "20240101",
"TranSeq": "123456789012",
"BillKey": "123456",
"CompanyId": "654321"
}</pre>
</div>
<div class="card">
<h2>🛠 开发工具</h2>
<p>推荐使用以下工具进行API开发和测试</p>
<ul>
<li><a href="https://www.postman.com/" target="_blank">Postman</a> - API</li>
<li><a href="https://insomnia.rest/" target="_blank">Insomnia</a> - REST</li>
<li><a href="https://editor.swagger.io/" target="_blank">Swagger Editor</a> - 线</li>
</ul>
</div>
<footer style="text-align: center; margin-top: 50px; color: #666;">
<p>营收系统接口API v1.0.0 | OpenAPI 3.0.3</p>
</footer>
</body>
</html>
`);
});
// 启动服务器
app.listen(PORT, () => {
console.log('🚀 营收系统接口文档服务已启动!');
console.log(`📖 访问地址: http://localhost:${PORT}`);
console.log(`📋 Swagger Editor: https://editor.swagger.io/?url=http://localhost:${PORT}/docs/main/openapi.yaml`);
console.log('');
console.log('📁 文档结构:');
console.log('├── docs/main/openapi.yaml # 主入口文档');
console.log('├── docs/main/components/ # 通用组件');
console.log('└── docs/main/paths/ # API路径定义');
console.log('');
console.log('按 Ctrl+C 停止服务');
});
module.exports = app;

View File

@ -0,0 +1,119 @@
#!/usr/bin/env node
const YAML = require('yaml');
const fs = require('fs');
const path = require('path');
/**
* 验证OpenAPI文档的基本语法和结构
*/
function validateOpenAPIDoc() {
try {
console.log('🔍 开始验证营收系统OpenAPI文档...\n');
// 读取主文档
const mainDocPath = path.join(__dirname, 'main', 'openapi.yaml');
const mainContent = fs.readFileSync(mainDocPath, 'utf8');
const mainDoc = YAML.parse(mainContent);
// 验证基本结构
console.log('✅ 主文档语法正确');
console.log(`📋 API标题: ${mainDoc.info.title}`);
console.log(`📋 API版本: ${mainDoc.info.version}`);
console.log(`📋 OpenAPI版本: ${mainDoc.openapi}`);
// 验证服务器配置
if (mainDoc.servers && mainDoc.servers.length > 0) {
console.log(`🌐 配置了 ${mainDoc.servers.length} 个服务器环境:`);
mainDoc.servers.forEach((server, index) => {
console.log(` ${index + 1}. ${server.description}: ${server.url}`);
});
}
// 验证标签
if (mainDoc.tags && mainDoc.tags.length > 0) {
console.log(`🏷️ 定义了 ${mainDoc.tags.length} 个标签:`);
mainDoc.tags.forEach((tag, index) => {
console.log(` ${index + 1}. ${tag.name}: ${tag.description}`);
});
}
// 验证路径
if (mainDoc.paths) {
const pathCount = Object.keys(mainDoc.paths).length;
console.log(`🛣️ 定义了 ${pathCount} 个API路径:`);
Object.keys(mainDoc.paths).forEach((path, index) => {
console.log(` ${index + 1}. ${path}`);
});
}
// 验证组件文件
console.log('\n🔧 验证组件文件:');
const componentFiles = [
'components/schemas.yaml',
'components/responses.yaml',
'components/parameters.yaml',
'components/headers.yaml',
'components/security.yaml'
];
componentFiles.forEach(file => {
const filePath = path.join(__dirname, 'main', file);
if (fs.existsSync(filePath)) {
try {
const content = fs.readFileSync(filePath, 'utf8');
YAML.parse(content);
console.log(`${file}`);
} catch (error) {
console.log(`${file}: ${error.message}`);
}
} else {
console.log(` ⚠️ ${file}: 文件不存在`);
}
});
// 验证路径文件
console.log('\n🛤 验证路径文件:');
const pathFiles = [
'paths/bill-query.yaml',
'paths/bill-pay.yaml',
'paths/pay-invalid.yaml',
'paths/withholding-signing.yaml',
'paths/withholding-termination.yaml',
'paths/withholding-send-disc.yaml',
'paths/withholding-back-disc.yaml'
];
pathFiles.forEach(file => {
const filePath = path.join(__dirname, 'main', file);
if (fs.existsSync(filePath)) {
try {
const content = fs.readFileSync(filePath, 'utf8');
YAML.parse(content);
console.log(`${file}`);
} catch (error) {
console.log(`${file}: ${error.message}`);
}
} else {
console.log(` ⚠️ ${file}: 文件不存在`);
}
});
console.log('\n🎉 OpenAPI文档验证完成!');
console.log('\n📖 使用方法:');
console.log('1. 在Swagger Editor中打开 docs/main/openapi.yaml');
console.log('2. 或使用命令: npx swagger-ui-serve docs/main/openapi.yaml');
console.log('3. 或导入到Postman等API测试工具中');
} catch (error) {
console.error('❌ 验证失败:', error.message);
process.exit(1);
}
}
// 如果直接运行此脚本
if (require.main === module) {
validateOpenAPIDoc();
}
module.exports = { validateOpenAPIDoc };

View File

@ -116,7 +116,7 @@
> 说明:本表中的历史记录按当时原始表述保留;当前正式数据库口径统一以“达梦数据库 8.0+”为准。
| 2026-03-24 | PostgreSQL 16 容灾资源申请专题新增 | 1新增 `docs/design/03_Technical_Design/07_PostgreSQL16_DR_Resource_Application.md`形成面向甲方的独立资源申请说明2明确单中心主备、同城双可用区、同城双中心+异地灾备、主备+PITR 四类容灾形态3补充备库作为同步热备、同步温备、异步热备、异步温备、异地灾备时的 CPU、内存、存储资源配比建议4补充资源申请清单、网络与存储要求、部署实施说明、切换与恢复要求5补充 `Patroni + HAProxy + PgBouncer` 代理接入架构、配置样例与主备切换接入逻辑说明6补充计划切换、故障切换、回切、切换前检查表与切换后验证表。 | 用户希望形成独立文档,用于向甲方申请 PostgreSQL 16 容灾资源,并明确多种部署形态、备库资源比例、代理搭建方式、切换方式和部署方式。 | 正面影响,仓库内新增了一份可独立提交、可直接用于资源审批沟通的数据库容灾专题文档;后续可在不改动数据库主文档主口径的前提下,为 PostgreSQL 16 方案提供单独的申请依据、代理建设建议、切换实施细则与实施说明。 |
| 2026-03-26 | 方案二整体部署方案独立成文 | 1新增 `docs/design/03_Technical_Design/08_Integrated_Deployment_Design_PlanB.md`,将已采纳 PostgreSQL 16 方案二后的整体部署方案独立成文2结合当前前后端分层部署形态补齐应用、数据库、中间件、静态存储的一体化部署结构3新增软件拓扑图、网络拓扑图、主机角色划分、资源配置建议、网络需求和端口访问建议4`07_PostgreSQL16_DR_Resource_Application.md` 增加独立文档引用说明,在技术专项目录增加入口。 | 用户明确要求不要仅在 `07` 文档内保留方案二内容,而是需要形成独立文件,并作为已采纳数据库方案后的整体部署方案提交评审。 | 正面影响,数据库资源申请专题与整体部署方案实现职责分离;后续甲方可分别评审“数据库容灾资源申请”和“系统整体部署方案”,减少口径混杂并提升部署审批的可读性。 |
| 2026-03-18 | REV-005 统计模板补齐 | 1`specs/002-rev005-invoice-flow/verification.md``T055``T060``T061``T062``T063` 新增可直接填写的样本记录模板2将 SC-001 ~ SC-004 的建议统计口径细化为表格字段与待补说明避免后续只剩抽象待办3保持“模板已补齐但实际统计结果仍待联调/测试环境补录”的真实状态,不虚构样本结果。 | 用户继续推进 REV-005希望把剩余统计类待办进一步收敛成可执行模板便于后续直接补录真实样本而不是重新设计统计格式。 | 正面影响REV-005 当前已具备统一的统计与日志抽样记录模板;后续补 `T055``T060 ~ T063` 时可直接按模板填充真实环境数据,减少再次整理验证文档结构的成本。 |
| 2026-03-18 | REV-005 verify 执行入口补齐 | 1`specs/002-rev005-invoice-flow/verification.md` 补齐 `/business/invoice/apply``/query``/query/compensate``/write-back``/customer/query``/customer/download``/customer/push``/invalidate``/red-ink` 的最小请求模板2继续补齐 `T055``T060 ~ T063` 的执行命令草稿与样本采集顺序,明确仅作为测试/联调环境占位模板真实地址、鉴权信息、业务主键与统计结果均待后续替换和回填3同步 `03_Task_Checklist.md`,将 verify 阶段推进到“替换真实环境参数即可执行”的状态。 | 用户继续推进 REV-005希望不要停留在抽象验证建议而是把剩余 verify 工作推进到可直接执行、可直接补样本的程度。 | 正面影响REV-005 当前已具备统一的验证入口、请求模板、命令草稿与采样顺序;后续在测试或联调环境中可直接替换参数发起请求并回填 `T055``T060 ~ T063` 的真实结果,减少重复梳理接口和命令的成本。 |

View File

@ -147,6 +147,8 @@
- [x] 已补齐网络、存储、切换与恢复要求 ✅
- [x] 已补齐 PostgreSQL 代理架构、`HAProxy` / `PgBouncer` 配置样例与应用接入建议 ✅
- [x] 已补齐计划切换、故障切换、回切、检查表与验证表 ✅
- [x] 已将原方案二抽出为整体部署方案,补齐前端、后端、中间件、数据库、静态存储和网络需求 ✅
- [x] 已新增独立文档 `08_Integrated_Deployment_Design_PlanB.md`,沉淀已采纳方案二后的整体部署方案 ✅
### 📋 010-bank-transfer-config 银行文件传输配置能力

View File

@ -147,7 +147,7 @@ retrieval_priority: P0
- 业务服务层SYS-001 统一平台、SYS-002 营收业务系统、SYS-003 手机抄表APP、SYS-004 微网厅系统、SYS-005 工单管理系统、SYS-006 表务管理系统、SYS-007 报装业务系统
- 基础服务层SYS-008 发票服务子系统统一开票、SYS-009 支付与银行结算子系统(统一聚合支付/退款/渠道适配/第三方支付平台/银行代扣/夜间批量代扣/对账/加解密/支付回调、SYS-010 消息服务子系统(统一短信/邮件/站内信/模板消息/微信通知/数科系统对接)、注册/配置中心、任务调度等基础服务
- 基础服务层SYS-008 发票服务子系统统一开票、SYS-009 支付与银行结算子系统(统一聚合支付/退款/渠道适配/第三方支付平台/银行实时收费、代扣/托收签解约、批次与对账契约、加解密与支付回调,夜间批量代扣/完整对账结算按后续完善项管理、SYS-010 消息服务子系统(统一短信/邮件/站内信/模板消息/微信通知/数科系统对接)、注册/配置中心、任务调度等基础服务
&emsp;&emsp;通过系统的建设,实现福建省水投数字科技有限公司客户服务管理领域的业务流程梳理再造、组织架构的优化、管理制度的建设、绩效考核标准的建设。构建以客户为中心的一体化服务体系,将客户的所有信息进行有机的关联,方便企业对营收信息进行综合分析和管理,为客户提供更多、更便捷、更主动的个性化服务,提高客户服务的质量和客户满意度。
@ -161,7 +161,7 @@ retrieval_priority: P0
- **表务管理系统**:设备档案、表务全生命周期管理
- **报装业务系统**:覆盖报装申请、踏勘、施工、验收、通水与档案归档的端到端流程,支持调用泛微进行合同签订,电子签章,支持各租户自定义报装流程和表单定义
- **发票服务子系统**(基础服务):统一开票网关与供应商适配(现优先对接航天,预留博思),回执与存证
- **支付与银行对接子系统**(基础服务):统一支付/退款、银行代扣送盘/回盘、对账处理、加解密签名,第三方支付平台(微信、支付宝),支持夜间进行批量代扣
- **支付与银行对接子系统**(基础服务):统一支付/退款、银行实时收费、代扣/托收签解约、送盘/回盘/对账契约、加解密签名与第三方支付平台(微信、支付宝);夜间批量代扣和完整结算闭环按后续完善项管理
- **消息服务子系统**(基础服务):统一短信/邮件/站内信/模板消息下行推送与到达回执供各业务子系统调用如营收业务系统催缴微信信息通知对接数科已建系统通知OA、智水擎水投数科 app
### 功能范围
@ -206,6 +206,7 @@ retrieval_priority: P0
#### SYS-009 支付与银行结算子系统(基础服务)
- 聚合支付/退款、渠道适配(微信/支付宝/银联聚合等)、第三方支付平台(微信、支付宝)、支付结果通知、银行代扣(送盘/回盘)、支持夜间进行批量代扣、对账文件处理、加解密/签名
- 当前实现侧已确认聚合支付基础能力、银行欠费查询/缴费、`BankWithholding` 六条银行入口(客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询)、代扣/托收签约解约和后台资源管理具备证据;其中 `BankCollection` 平行链路、夜间批量代扣、完整对账与结算仍按部分实现或后续完善项管理
#### SYS-010 消息服务子系统(基础服务)
@ -747,7 +748,7 @@ graph TB
| SYS-006 | 表务管理系统 | 设备档案、表务全生命周期管理 | 自行开发 |
| SYS-007 | 报装业务系统 | 报装流程管理、合同签订与电子签章、工程管理、档案管理 | 自行开发 |
| SYS-008 | 发票服务子系统 | 统一开票网关、供应商适配器(优先对接航天)、回执处理与存证 | 自行开发 |
| SYS-009 | 支付与银行结算子系统 | 聚合支付/退款、渠道适配(微信/支付宝/银联)、支付通知、银行代扣送盘/回盘、夜间批量代扣、对账处理、加解密签名 | 自行开发 |
| SYS-009 | 支付与银行结算子系统 | 聚合支付/退款、渠道适配(微信/支付宝/银联)、支付通知、银行实时收费、代扣/托收签解约、送盘/回盘/对账契约、加解密签名;夜间批量代扣与完整结算闭环按后续完善项管理 | 自行开发 |
| SYS-010 | 消息服务子系统 | 短信/邮件/站内信模板与发送、回执查询、批量推送 | 自行开发 |
### 子系统间关系
@ -3675,14 +3676,14 @@ graph TD
## 任务概述
&emsp;&emsp;支付与银行结算子系统SYS-009统一承载聚合支付/退款、渠道适配(微信/支付宝/银联聚合)、支付回调验签入账,以及银行代扣(送盘/回盘)、支持夜间进行批量代扣、对账文件处理与安全加解密/签名,向上对营收/微网厅等系统提供标准化支付与结算能力。
&emsp;&emsp;支付与银行结算子系统SYS-009统一承载聚合支付/退款、渠道适配(微信/支付宝/银联聚合)、支付回调验签入账,以及银行实时收费、代扣/托收签约解约、送盘/回盘/对账契约与安全加解密/签名,向上对营收/微网厅等系统提供标准化支付与结算能力。夜间批量代扣、完整回盘状态补偿、对账差异处理与结算确认当前仍按后续完善项管理。
## 设计概述
### 总体约束
- 多渠道聚合:统一接入微信/支付宝/银联聚合
- 统一结算:批量代扣送回盘、批量对账文件处理、差异对齐
- 统一结算:统一维护批次、回盘、对账和差异语义,完整结算闭环按后续完善项推进
- 安全合规:签名/验签、加解密、回调防重放、幂等
- 高可用:基础重试补偿机制
@ -3692,7 +3693,7 @@ graph TD
### 子系统外部接口SYS-009
&emsp;&emsp;支付与银行结算子系统作为统一的支付能力中心,为上游业务系统提供聚合支付、银行代扣、退款处理、对账管理等全方位的支付结算服务。系统与微信支付、支付宝、银联聚合及各大银行系统对接,提供安全可靠的支付解决方案
&emsp;&emsp;支付与银行结算子系统作为统一的支付能力中心,为上游业务系统提供聚合支付、银行实时收费、代扣/托收签解约、退款处理、对账管理等支付结算服务。以下接口表表达的是正式设计目标边界,其中聚合支付、实时收费查询/缴费、代扣/托收签解约已具备较明确实现证据;`BankWithholding` 六条银行入口已形成最小实现态闭环,托收平行链路、对账和完整结算闭环仍按部分实现或后续完善项管理
| 接口编号 | 接口名称 | 功能描述 | 调用方 | 接口协议 | 输入参数 | 输出结果 |
|---|---|---|---|---|---|---|
@ -3700,9 +3701,9 @@ graph TD
| IF-PAY-002 | 统一关单接口 | 订单关闭和撤销处理 | 营收系统/微网厅 | HTTP/REST | 订单号、关闭原因、操作类型 | 关单状态、退款信息、处理结果 |
| IF-PAY-003 | 统一退款接口 | 原路退款和部分退款处理 | 营收系统/微网厅 | HTTP/REST | 原订单号、退款金额、退款原因 | 退款单号、退款状态、预计到账时间 |
| IF-PAY-004 | 支付回调接口 | 接收渠道支付结果回调 | 微信/支付宝/银联 | HTTP/REST | 回调数据、签名、时间戳 | 处理确认、业务更新状态 |
| IF-PAY-005 | 批量代扣送盘接口 | 银行批量代扣文件发送 | 营收业务系统 | HTTP/REST/SFTP | 代扣清单、客户签约信息、扣款金额 | 送盘状态、批次号、预计处理时间 |
| IF-PAY-006 | 批量代扣回盘接口 | 银行代扣结果回盘处理 | 银行系统 | HTTP/REST/SFTP | 回盘文件、处理结果、失败原因 | 解析结果、状态更新、异常处理 |
| IF-PAY-007 | 批量对账文件接口 | 银行对账文件处理分析 | 银行系统/营收系统 | HTTP/REST/SFTP | 对账文件、对账日期、差异规则 | 对账结果、差异明细、调整建议 |
| IF-PAY-005 | 批量代扣送盘接口 | 银行批量代扣文件发送 | 营收业务系统 | HTTP/REST/SFTP | 代扣清单、客户签约信息、扣款金额 | 送盘状态、批次号、预计处理时间`BankWithholding` 已具备最小实现态闭环;真实文件通道联调仍待补证) |
| IF-PAY-006 | 批量代扣回盘接口 | 银行代扣结果回盘处理 | 银行系统 | HTTP/REST/SFTP | 回盘文件、处理结果、失败原因 | 解析结果、状态更新、异常处理`BankWithholding` 已具备最小实现态闭环;真实文件解析与异常补偿仍待补证) |
| IF-PAY-007 | 批量对账文件接口 | 银行对账文件处理分析 | 银行系统/营收系统 | HTTP/REST/SFTP | 对账文件、对账日期、差异规则 | 对账结果、差异明细、调整建议(当前仍按部分实现或后续完善项管理) |
| IF-PAY-008 | 加解密签名接口 | 支付数据安全处理 | 内部模块调用 | HTTP/REST | 待处理数据、加密类型、签名算法 | 处理结果、安全凭证、验证状态 |
## 子系统架构设计
@ -3831,6 +3832,7 @@ graph TD
- 差异识别与异常记录
- 冲正/补记建议生成
- 对账报告与追踪链路告警
- 当前阶段按目标能力保留正式设计口径,不直接表述为已形成完整闭环
#### PAY-006: 加解密/签名

View File

@ -1223,6 +1223,8 @@ retrieval_priority: P0
| `bk_reconcile_diff` | `batch_id`, `trade_no`, `diff_type`, `diff_amount` | 对账差异 |
| `bk_settlement_batch` | `batch_no`, `channel_code`, `settlement_date`, `settlement_status` | 结算批次 |
> 当前实现边界说明:`bk_*` 表族已形成较完整的对象承接口径,且签约、解约、交易流水与后台资源管理具备明确实现证据;但送盘、回盘、对账、结算等业务编排仍缺少完整闭环与统一迁移证据,数据库专项不得据此倒推出“银行协同已全部落地”。
</details>
<a id="sec-meter-inst-topic"></a>

View File

@ -28,14 +28,15 @@ retrieval_priority: P1
4. 容灾形态说明
5. 资源配比原则
6. 推荐部署方案
7. 甲方审批汇总表
8. 详细资源申请清单
9. 网络与存储要求
10. 数据库代理与连接接入方案
11. 部署实施说明
12. 切换与恢复说明
13. 切换实施细则
14. 申请结论
7. 方案二独立整体部署方案
8. 甲方审批汇总表
9. 详细资源申请清单
10. 网络与存储要求
11. 数据库代理与连接接入方案
12. 部署实施说明
13. 切换与恢复说明
14. 切换实施细则
15. 申请结论
## 编制目的
@ -212,6 +213,185 @@ flowchart TD
本项目建议优先向甲方申请方案 B如甲方同时要求区域级灾难恢复能力建议直接申请方案 C。
## 方案二独立整体部署方案
> 本节内容已独立沉淀为 `docs/design/03_Technical_Design/08_Integrated_Deployment_Design_PlanB.md`。如需评审“已采纳方案二后的整体部署方案”,以独立文档为准。
### 方案定位
本方案基于原“方案 B正式生产推荐方案”进一步展开作为适配当前福建水务营收系统前后端分层部署形态的独立整体部署方案。
本方案的核心约束如下:
1. 前端服务与后端服务保持分层部署。
2. 中间件集中部署在 1 台主机上。
3. 数据库控制组件与中间件部署在同一台主机上。
4. 数据库采用同城双可用区主备架构。
5. 静态存储采用 MinIO并与中间件一并集中部署。
### 适用场景
本方案适用于以下场景:
1. 当前项目已具备前端 Nginx + Vue3、后端 Spring Boot 的分层部署模式。
2. 甲方希望在控制主机数量的前提下,完成应用层、数据库层、中间件层与静态存储层的整体建设。
3. 甲方接受“中间件集中部署、数据库双节点容灾、前后端分层部署”的资源结构。
### 整体部署拓扑
```mermaid
flowchart LR
subgraph CLIENT[访问入口]
U1[PC端用户]
U2[移动端用户]
U3[第三方系统]
end
subgraph LB[负载均衡层]
LB1[负载均衡/VIP]
end
subgraph FE[前端服务层]
FE1[前端服务器 1<br/>Nginx + Vue3]
FE2[前端服务器 2<br/>Nginx + Vue3]
end
subgraph BE[后端服务层]
BE1[后端服务器 1<br/>Spring Boot]
BE2[后端服务器 2<br/>Spring Boot]
BE3[后端服务器 3<br/>Spring Boot]
BE4[后端服务器 4<br/>Spring Boot]
end
subgraph MW[中间件与数据库控制层]
MW1[中间件服务器<br/>Redis + Nacos + MinIO<br/>HAProxy + PgBouncer + Patroni]
end
subgraph DBA[数据库可用区 A]
PG1[(PostgreSQL 16 主库)]
end
subgraph DBB[数据库可用区 B]
PG2[(PostgreSQL 16 同城热备库)]
end
CLIENT --> LB
LB --> FE
FE --> BE
BE --> MW1
MW1 --> PG1
MW1 --> PG2
PG1 -->|同步复制| PG2
PG1 --> BK[备份归档存储]
PG2 --> BK
```
### 主机角色划分
| 层级 | 主机角色 | 数量 | 主要部署内容 | 说明 |
|---|---|---:|---|---|
| 负载均衡层 | 负载均衡/VIP | 1 组 | 统一访问入口、SSL 卸载、转发控制 | 对外统一入口 |
| 前端服务层 | 前端服务器 | 2 台 | Nginx、Vue3 静态页面 | 承担 PC/H5 前端访问 |
| 后端服务层 | 后端服务器 | 4 台 | Spring Boot 业务服务 | 承担营收、账务、表务、发票、银行等业务 |
| 中间件层 | 中间件服务器 | 1 台 | Redis、Nacos、MinIO | 中间件集中部署 |
| 数据库控制层 | 数据库控制服务器 | 与中间件同机 | HAProxy、PgBouncer、Patroni | 不单独新增主机,部署在中间件服务器上 |
| 数据库层 | 主库服务器 | 1 台 | PostgreSQL 16 Primary | 部署在可用区 A |
| 数据库层 | 热备服务器 | 1 台 | PostgreSQL 16 Standby | 部署在可用区 B |
| 备份层 | 备份归档存储 | 1 套 | 基础备份、WAL 归档、恢复副本 | 独立存储空间 |
### 部署说明
#### 1. 前端部署
1. 前端采用 2 台服务器部署 `Nginx + Vue3` 静态资源。
2. 前端服务器不直接访问数据库,仅通过 HTTP/HTTPS 调用后端服务。
3. 负载均衡层对前端节点做健康检查和请求分发。
#### 2. 后端部署
1. 后端采用 4 台 Spring Boot 应用服务器集群部署。
2. 所有后端节点统一连接中间件与数据库控制主机暴露的数据库代理地址。
3. 后端节点统一访问 Redis、Nacos 和 MinIO不直接连接数据库主机 IP。
#### 3. 中间件与静态存储部署
1. 中间件统一部署在 1 台主机上。
2. 中间件主机承载以下组件:
- Redis缓存、会话、轻量级消息能力
- Nacos配置中心、注册治理能力
- MinIO静态文件与附件存储
3. MinIO 作为本项目静态存储与对象存储统一入口,承接附件、票据文件、导出文件及其他静态资源。
#### 4. 数据库控制组件部署
1. 数据库控制组件不单独新增主机。
2. `HAProxy``PgBouncer``Patroni` 与中间件部署在同一台主机。
3. 后端应用通过该主机上的统一数据库代理地址访问 PostgreSQL 主备集群。
4. 数据库切换时,由 Patroni 负责主备角色管理,由 HAProxy 完成入口切换,由 PgBouncer 完成连接池接管。
#### 5. 数据库部署
1. PostgreSQL 16 采用 1 主 1 热备模式。
2. 主库与热备库分别部署在同城不同可用区或不同故障域。
3. 主备之间采用同步复制,保障核心收费、账务、对账数据的一致性要求。
4. 主库和热备库同时将基础备份与 WAL 归档写入备份归档存储。
### 资源建议
| 主机角色 | 数量 | 建议配置 | 备注 |
|---|---:|---|---|
| 前端服务器 | 2 台 | 8 核 CPU / 16 GB 内存 / 200 GB SSD | 支撑 Nginx 与静态页面发布 |
| 后端服务器 | 4 台 | 16 核 CPU / 32 GB 内存 / 300 GB SSD | 对齐当前主文档后端服务集群口径 |
| 中间件与数据库控制服务器 | 1 台 | 16 核 CPU / 64 GB 内存 / 2 TB SSD | 同机承载 Redis、Nacos、MinIO、HAProxy、PgBouncer、Patroni |
| PostgreSQL 主库服务器 | 1 台 | 16 核 CPU / 64 GB 内存 / 2 TB NVMe SSD | 生产写入节点 |
| PostgreSQL 热备服务器 | 1 台 | 16 核 CPU / 64 GB 内存 / 2 TB NVMe SSD | 正式切换接管节点 |
| 备份归档存储 | 1 套 | 可用空间 4 TB 至 6 TB | 存放全量备份与 WAL 归档 |
### 网络需求
#### 1. 网络分区建议
| 网络分区 | 部署对象 | 访问要求 |
|---|---|---|
| 接入区 | 负载均衡、前端服务器 | 对外提供 HTTPS 访问 |
| 应用区 | 后端服务器 | 仅允许接入区与运维区访问 |
| 中间件区 | Redis、Nacos、MinIO、数据库控制组件 | 仅允许后端服务器与运维区访问 |
| 数据区 | PostgreSQL 主库、热备库、备份存储 | 严格限制,仅允许中间件控制主机和运维区访问 |
#### 2. 带宽与时延要求
| 网络链路 | 建议要求 | 说明 |
|---|---|---|
| 前端到后端 | 千兆网络 | 满足业务访问与接口调用 |
| 后端到中间件 | 千兆网络 | 满足缓存、配置、文件访问 |
| 中间件控制主机到数据库 | 千兆网络以上 | 满足数据库代理与健康检查 |
| 主备复制链路 | 不低于 10 Gbps | 支撑同步复制 |
| 同城主备时延 | 建议低于 2 ms | 支撑稳定同步复制 |
| 数据库到备份存储 | 千兆网络以上 | 满足基础备份与 WAL 归档传输 |
#### 3. 端口与访问控制建议
| 服务 | 典型端口 | 访问范围 |
|---|---|---|
| Nginx/HTTPS | 443 | 对外开放 |
| 后端 HTTP/HTTPS | 项目自定义端口 | 仅前端与内网调用 |
| Redis | 6379 | 仅后端与运维可访问 |
| Nacos | 8848 | 仅后端与运维可访问 |
| MinIO | 9000/9001 | 仅后端与运维可访问 |
| PgBouncer | 6432 | 仅后端可访问 |
| HAProxy | 5000 或项目定义端口 | 仅 PgBouncer 与运维可访问 |
| PostgreSQL | 5432 | 仅数据库控制主机与运维可访问 |
### 正式方案结论
结合当前项目前后端部署形态,建议以本方案作为 PostgreSQL 16 正式生产推荐部署方案。其总体特点为:
1. 前端与后端保持现有分层集群部署模式。
2. 中间件与数据库控制组件集中部署在 1 台主机,控制主机数量。
3. PostgreSQL 16 采用同城双可用区 1 主 1 热备部署,满足高可用要求。
4. MinIO 作为静态存储统一入口,与中间件同机部署,便于统一运维。
5. 网络上采用应用区、中间件区、数据区分层隔离,保障安全性与可维护性。
## 甲方审批汇总表
以下汇总表用于甲方进行资源审批、预算评估和实施范围确认。建议优先按“方案 B正式生产推荐方案”审批如甲方同时要求区域级灾难恢复能力可按“方案 C正式生产增强方案”审批。

View File

@ -57,7 +57,7 @@ retrieval_priority: P1
5. 两台业务应用节点内均部署 `Spring Boot Gateway`,作为微服务集群统一接入入口,并同时承载业务服务。
6. 中间件集中部署在 1 台主机上。
7. 数据库控制组件与中间件部署在同一台主机上。
8. `MinIO` 作为静态存储与对象存储统一入口,与中间件同机部署。
8. `RustFS` 作为静态存储与对象存储统一入口,与中间件同机部署。
9. 一期口径下,原“数据与中间件节点”和“文件存储节点”合并为 1 台综合节点。
10. 银行文件交换采用独立 `SFTP/FTP` 文件交换服务器,作为银行送盘、回盘、对账文件交换专用前置机。
11. 当前已知可用于本方案的业务与基础设施主机资源如下:
@ -88,11 +88,18 @@ flowchart LR
classDef access fill:#fff7ed,stroke:#ea580c,stroke-width:1.4px,color:#7c2d12;
classDef app fill:#eff6ff,stroke:#2563eb,stroke-width:1.4px,color:#1e3a8a;
U1[PC 端用户]:::source
U2[移动端用户]:::source
U3[第三方系统]:::source
G1[公网入口薄转发]:::access
G2[内网 Nginx]:::access
subgraph OUTER[公网域]
direction TB
U2[移动端用户]:::source
U3[第三方系统]:::source
G1[公网接入代理]:::access
end
subgraph INNER[内部网络域]
direction TB
U1[PC 端用户]:::source
G2[内网 Nginx]:::access
end
subgraph APP1[业务应用节点 1]
direction TB
@ -109,7 +116,7 @@ flowchart LR
U1 -->|内网访问| G2
U2 -->|HTTPS 443| G1
U3 -->|接口访问| G1
G1 -->|开放 API 转发| G2
G1 -->|跨公网/内网边界转发| G2
G2 -->|负载到节点 1| A1G
G2 -->|负载到节点 2| A2G
A1G --> A1B
@ -118,11 +125,12 @@ flowchart LR
图说明:
1. PC 端用户通过内网直接访问内网 Nginx。
2. 移动端用户和第三方系统先经公网入口薄转发,再由公网入口转发到内网 Nginx。
3. 内网 Nginx 将开放 API 端点负载到两台业务应用节点。
4. 两台业务应用节点内均部署 Spring Boot Gateway作为微服务集群统一接入入口。
5. 每台业务应用节点内的 Gateway 再向本节点业务服务转发请求。
1. 图中已明确区分公网域与内部网络域。
2. PC 端用户通过内网直接访问内部网络域中的内网 Nginx。
3. 移动端用户和第三方系统位于公网域,先经公网接入代理,再跨边界转发到内部网络域中的内网 Nginx。
4. 内网 Nginx 将开放 API 端点负载到两台业务应用节点。
5. 两台业务应用节点内均部署 Spring Boot Gateway作为微服务集群统一接入入口。
6. 每台业务应用节点内的 Gateway 再向本节点业务服务转发请求。
**图 4-2 综合服务软件拓扑图**
@ -136,7 +144,7 @@ flowchart TB
subgraph M[综合节点]
direction TB
M2[Redis / Nacos / MinIO]:::service
M2[Redis / Nacos / RustFS]:::service
M3[HAProxy / PgBouncer / Patroni]:::service
end
@ -160,7 +168,7 @@ flowchart LR
classDef backup fill:#fffbeb,stroke:#d97706,stroke-width:1.4px,color:#78350f;
classDef bank fill:#fef2f2,stroke:#dc2626,stroke-width:1.4px,color:#7f1d1d;
M2[MinIO / 附件文件]:::service
M2[RustFS / 文件归档]:::service
M3[HAProxy / PgBouncer / Patroni]:::service
F1[SFTP / FTP 文件交换服务器]:::bank
D1[(PostgreSQL 主库)]:::data
@ -181,7 +189,7 @@ flowchart LR
1. 数据库控制组件经由主库提供数据库访问能力。
2. 主库与热备之间通过同步复制保持一致性。
3. 银行文件交换服务器承接送盘、回盘、对账文件交换能力。
4. MinIO 与银行文件交换服务器分别将各自数据归档到备份归档存储。
4. RustFS 与银行文件交换服务器分别将各自数据归档到备份归档存储。
## 主机部署图
@ -217,7 +225,7 @@ flowchart LR
1. 内网 Nginx 入口节点作为我方统一接入入口,承接 API 转发与内部负载均衡能力。
2. 业务应用节点 1 和业务应用节点 2 组成应用主机集群,节点内同时部署 Spring Boot Gateway 与核心业务服务。
3. 综合节点集中承载 Redis、Nacos、MinIO、HAProxy、PgBouncer、Patroni并同时访问数据库主库与热备服务器。
3. 综合节点集中承载 Redis、Nacos、RustFS、HAProxy、PgBouncer、Patroni并同时访问数据库主库与热备服务器。
4. 独立的 SFTP/FTP 文件交换服务器承接银行送盘、回盘、对账文件交换,并由业务应用节点直接访问。
5. 数据库主库服务器与数据库热备服务器独立部署,满足数据库高可用要求。
@ -225,7 +233,7 @@ flowchart LR
1. 内网 Nginx 入口节点部署 `Nginx`,负责开放 API 转发和内部负载均衡。
2. 两台业务应用节点分别部署 `Spring Boot Gateway` 与业务服务实例。
3. 综合节点部署 `Redis``Nacos``MinIO`、`HAProxy``PgBouncer``Patroni`,并负责访问数据库主库与热备服务器。
3. 综合节点部署 `Redis``Nacos``RustFS`、`HAProxy``PgBouncer``Patroni`,并负责访问数据库主库与热备服务器。
4. SFTP/FTP 文件交换服务器部署银行文件交换服务及送盘、回盘、对账目录,由业务应用节点直接访问。
5. 数据库主库服务器部署 PostgreSQL 主库实例,数据库热备服务器部署 PostgreSQL 热备实例。
@ -268,107 +276,165 @@ flowchart LR
flowchart LR
DB1[(PostgreSQL 主库)] --> BK[备份归档存储]
DB2[(PostgreSQL 热备)] --> BK
MID[综合节点 / MinIO] --> BK
MID[综合节点 / RustFS] --> BK
BK --> REC[恢复与演练入口]
```
图说明:
1. 主库和热备均向备份归档存储输出备份数据。
2. 综合节点中的 MinIO 文件数据也纳入备份范围。
2. 综合节点中的 RustFS 文件数据也纳入备份范围。
3. 备份归档存储作为数据库恢复与文件恢复的统一入口。
## 网络拓扑图
**图 4-7 网络拓扑图**
> 说明:如需生成正式网络分区图与网络连接图,统一以 `scripts/generate_planb_diagrams.py` 生成的产物为准;其他脚本仅用于布局试验,不作为正式交付依据。
**图 4-7 网络分区图**
```mermaid
flowchart LR
classDef user fill:#ecfeff,stroke:#0891b2,stroke-width:1.2px,color:#083344;
classDef edge fill:#fff7ed,stroke:#ea580c,stroke-width:1.2px,color:#7c2d12;
classDef zone fill:#f8fafc,stroke:#94a3b8,stroke-width:1.2px,color:#0f172a;
classDef svc fill:#f5f3ff,stroke:#7c3aed,stroke-width:1.2px,color:#4c1d95;
classDef data fill:#ecfdf5,stroke:#059669,stroke-width:1.2px,color:#064e3b;
classDef bank fill:#fef2f2,stroke:#dc2626,stroke-width:1.2px,color:#7f1d1d;
classDef backup fill:#fffbeb,stroke:#d97706,stroke-width:1.2px,color:#78350f;
U[用户与外部系统]:::user
PGW[公网接入代理]:::edge
subgraph Z1[内网接入区]
IGW[内网 Nginx]
end
subgraph Z2[应用区]
APP[业务应用集群]
end
subgraph Z3[综合服务区]
MID[综合节点]
end
subgraph Z4[数据区]
DBM[(PostgreSQL 主库)]
DBS[(PostgreSQL 热备)]
end
subgraph Z6[互联网银行交换区]
FX[SFTP / FTP 文件交换服务器]
end
subgraph Z5[机房接入区]
BK[备份归档存储]
end
class Z1,Z2,Z3,Z4,Z5,Z6 zone;
class IGW edge;
class APP svc;
class MID svc;
class DBM,DBS data;
class FX bank;
class BK backup;
U --> PGW
U --> IGW
PGW --> IGW
IGW --> APP
APP --> FX
APP --> MID
MID --> DBM
MID --> DBS
DBM --> BK
DBS --> BK
MID --> BK
```
图说明:
1. 公网接入代理不纳入我方主机资源,但作为公网访问进入内网的前置接入层予以保留。
2. 网络分区图按服务器和服务器集群粒度表达,不展开单机内部软件组件。
3. PC 端用户可直接通过内网访问内网 Nginx移动端和第三方系统经公网接入代理后进入内网 Nginx。
4. 业务应用集群、综合服务区、数据区、机房接入区、互联网银行交换区相互隔离,通过授权链路互通。
**图 4-8 网络连接图**
```mermaid
flowchart TB
classDef user fill:#ecfeff,stroke:#0891b2,stroke-width:1.2px,color:#083344;
classDef access fill:#fff7ed,stroke:#ea580c,stroke-width:1.2px,color:#7c2d12;
classDef app fill:#eff6ff,stroke:#2563eb,stroke-width:1.2px,color:#1e3a8a;
classDef ext fill:#fef3c7,stroke:#d97706,stroke-width:1.2px,color:#78350f;
classDef middle fill:#f5f3ff,stroke:#7c3aed,stroke-width:1.2px,color:#4c1d95;
classDef data fill:#ecfdf5,stroke:#059669,stroke-width:1.2px,color:#064e3b;
classDef backup fill:#fdf2f8,stroke:#db2777,stroke-width:1.2px,color:#831843;
classDef backup fill:#fffbeb,stroke:#d97706,stroke-width:1.2px,color:#78350f;
classDef bank fill:#fef2f2,stroke:#dc2626,stroke-width:1.2px,color:#7f1d1d;
subgraph U[访问来源]
subgraph OUTER[公网域]
direction LR
PC[PC 端用户]
M[移动端用户]
T[第三方系统]
M[移动端用户]:::user
T[第三方系统]:::user
FX[SFTP/FTP文件交换服务器]:::bank
end
subgraph A[接入层]
subgraph INNER[内部网络域]
direction TB
PGW[公网入口薄转发]
IGW[内网 Nginx]
subgraph CLIENT[客户端]
PC[PC 端用户]:::user
end
subgraph ACCESS[接入层]
PGW[公网接入代理]:::access
IGW[内网 Nginx]:::access
end
subgraph SVC[应用与中间件层]
APP[业务应用集群<br/>Spring Boot Gateway + Biz]:::app
CACHE[Redis / Nacos / RustFS]:::middle
CTRL[HAProxy / PgBouncer / Patroni]:::middle
end
subgraph DATA[数据层]
DBM[(PostgreSQL 主库)]:::data
DBS[(PostgreSQL 热备)]:::data
end
subgraph STORAGE[存储层]
STORE[备份归档存储]:::backup
end
end
subgraph B[应用层]
direction LR
APP1[业务应用节点 1<br/>Spring Boot Gateway]
APP2[业务应用节点 2<br/>Spring Boot Gateway]
end
subgraph E[外部交换区]
direction TB
FX[SFTP/FTP 文件交换服务器]
end
subgraph C[综合服务层]
direction TB
MID[综合节点]
end
subgraph D[数据层]
direction LR
DBM[(PostgreSQL 主库)]
DBS[(PostgreSQL 热备)]
end
subgraph BK[备份归档层]
direction TB
STORE[备份归档存储]
end
%% 公网访问链路
M -->|HTTPS 443| PGW
T -->|HTTPS 443 / 接口| PGW
PGW -->|请求转发| IGW
%% 内网访问链路
PC -->|内网访问| IGW
M -->|公网 HTTPS| PGW
T -->|公网 HTTPS/接口| PGW
PGW -->|转发| IGW
IGW -->|网关/应用端口| APP
IGW -->|负载均衡| APP1
IGW -->|负载均衡| APP2
APP1 -->|文件交换| FX
APP2 -->|文件交换| FX
APP1 -->|业务访问| MID
APP2 -->|业务访问| MID
MID -->|数据库访问| DBM
MID -->|状态感知/只读访问| DBS
%% 应用层依赖
APP -->|6379 / 8848 / 9000| CACHE
APP -->|文件传输| FX
APP -->|6432 / 5000| CTRL
CTRL -->|5432| DBM
CTRL -.->|5432 状态探测| DBS
DBM -->|主备同步| DBS
DBM -->|备份/WAL 归档| STORE
DBS -->|备份副本| STORE
MID -->|文件归档| STORE
class PC,M,T user;
class PGW,IGW access;
class APP1,APP2 app;
class FX ext;
class MID middle;
class DBM,DBS data;
class STORE backup;
%% 数据备份与归档
CACHE -->|归档文件| STORE
DBM -->|备份 / WAL 归档| STORE
DBS -.->|备份副本| STORE
```
图说明:
1. PC 端用户通过内网直接访问内网 Nginx移动端用户和第三方系统通过公网入口薄转发再转发至内网 Nginx。
2. 内网 Nginx 统一负载到两台业务应用节点,两台节点内部均部署 Spring Boot Gateway。
3. 业务应用节点一方面直接访问 SFTP/FTP 文件交换服务器,另一方面访问综合节点获取缓存、配置、文件和数据库代理能力。
4. 综合节点访问 PostgreSQL 主库与热备,数据库主备及综合节点均接入备份归档存储。
1. 图中已明确区分公网域与内部网络域,公网访问通过公网接入代理跨边界进入内部网络。
2. PC 端用户位于内部网络域,直接访问内网 Nginx移动端和第三方系统位于公网域。
3. 内网 Nginx 统一将 API / Gateway 请求负载到业务应用集群。
4. 业务应用集群分别访问综合服务、银行文件交换服务器和数据库控制组件。
5. 综合服务中的数据库控制组件同时访问 PostgreSQL 主库与热备;数据库与综合服务均向备份归档存储输出归档数据。
## 主机角色与部署内容
@ -376,7 +442,7 @@ flowchart TB
|---|---|---:|---|---|
| 接入层 | 内网 Nginx 入口节点 | 1 台 | Nginx、API 转发、内部负载均衡 | 作为我方统一内网接入入口,承接开放 API 转发 |
| 应用层 | 业务应用节点(主) | 2 台 | Spring Boot Gateway、Spring Boot 业务服务 | 承载微服务集群接入与表务、抄表、收费、账务、发票、报表等核心应用服务 |
| 综合支撑层 | 数据、中间件与文件存储综合节点 | 1 台 | Redis、Nacos、MinIO、HAProxy、PgBouncer、Patroni | 中间件、数据库控制与文件存储同机部署 |
| 综合支撑层 | 数据、中间件与文件存储综合节点 | 1 台 | Redis、Nacos、RustFS、HAProxy、PgBouncer、Patroni | 中间件、数据库控制与文件存储同机部署 |
| 银行交换层 | SFTP/FTP 文件交换服务器 | 1 台 | SFTP/FTP 服务、送盘目录、回盘目录、对账目录、归档目录 | 作为银行文件交换专用前置机 |
| 数据库层 | PostgreSQL 主库服务器 | 1 台 | PostgreSQL 16 Primary | 部署在可用区 A |
| 数据库层 | PostgreSQL 热备服务器 | 1 台 | PostgreSQL 16 Standby | 部署在可用区 B |
@ -419,7 +485,7 @@ flowchart TB
|---|---:|---:|---|---|---|---|
| 1 | 内网 Nginx 入口节点 | 1 台 | 8 核 CPU / 16 GB 内存 / 300 GB 存储 | Nginx、API 转发、内部负载均衡 | 我方统一内网接入入口 | 可利旧优先 |
| 2 | 业务应用节点(主) | 2 台 | 32 核 CPU / 64 GB 内存 / 300 GB 存储 | Spring Boot Gateway、Spring Boot 核心业务服务 | 承载微服务接入与核心业务服务 | 现有资源纳入正式方案 |
| 3 | 数据、中间件与文件存储综合节点 | 1 台 | 16 核 CPU / 32 GB 内存 / 2300 GB 存储 | Redis、Nacos、MinIO、HAProxy、PgBouncer、Patroni | 承载缓存、配置治理、数据库控制、图片、附件、导出文件等能力 | 现有资源纳入正式方案 |
| 3 | 数据、中间件与文件存储综合节点 | 1 台 | 16 核 CPU / 32 GB 内存 / 2300 GB 存储 | Redis、Nacos、RustFS、HAProxy、PgBouncer、Patroni | 承载缓存、配置治理、数据库控制、图片、附件、导出文件等能力 | 现有资源纳入正式方案 |
| 4 | SFTP/FTP 文件交换服务器 | 1 台 | 2 核 CPU / 8 GB 内存 / 200 GB 存储 | SFTP/FTP 服务、送盘/回盘/对账目录、归档目录 | 承载银行文件交换与目录隔离 | 按最节约一期规划建议新增或单独利旧 |
| 5 | PostgreSQL 主库服务器 | 1 台 | 24 核 CPU / 48 GB 内存 / 1 TB NVMe SSD | PostgreSQL 16 Primary | 承载生产写入 | 建议单独配置 |
| 6 | PostgreSQL 热备服务器 | 1 台 | 24 核 CPU / 48 GB 内存 / 1 TB NVMe SSD | PostgreSQL 16 Standby | 承载正式切换接管 | 建议单独配置 |
@ -436,7 +502,7 @@ flowchart TB
| 类别 | 软件名称 | 建议版本 | 部署位置 | 说明 |
|---|---|---|---|---|
| 操作系统 | Linux 发行版 | openEuler 20.03+ / CentOS 7.9+ | 全部服务器 | 按甲方基础环境标准选定 |
| 操作系统 | Linux 发行版 | 银河麒麟Kylin V10X86 | 全部服务器 | 按甲方当前基础环境标准选定 |
| 运行环境 | JDK | 17 | 业务应用节点 | Spring Boot 运行环境 |
| 接入层 | Nginx | 1.20+ | 内网 Nginx 入口节点 | 内网 API 转发、内部负载均衡 |
| 网关服务 | Spring Boot Gateway | 3.x | 业务应用节点 | 微服务集群统一接入 |
@ -445,7 +511,7 @@ flowchart TB
| 数据库 | PostgreSQL | 16 | 主库、热备库 | 主备数据库部署 |
| 缓存 | Redis | 6.2+ | 综合节点 | 缓存、会话、轻量级消息能力 |
| 配置中心 | Nacos | 2.x | 综合节点 | 配置治理与注册管理 |
| 对象存储 | MinIO | RELEASE 稳定版 | 综合节点 | 图片、附件、导出文件存储 |
| 对象存储 | RustFS | 以项目实际版本为准 | 综合节点 | 图片、附件、导出文件存储 |
| 数据库代理 | HAProxy | 2.x | 综合节点 | 数据库读写入口代理 |
| 连接池 | PgBouncer | 1.2x+ | 综合节点 | 统一数据库连接池 |
| 高可用控制 | Patroni | 稳定版 | 综合节点 | 主备状态管理与切换 |
@ -474,7 +540,7 @@ flowchart TB
| HAProxy | 综合节点 | Docker 部署 | 是,优先建议 | 作为数据库代理入口,适合 `host` 模式 |
| PgBouncer | 综合节点 | Docker 部署 | 是,优先建议 | 作为数据库连接池入口,适合 `host` 模式 |
| Patroni | 综合节点 | 物理机/虚拟机直接部署 | 否 | 建议与系统服务集成,便于运维与切换控制 |
| MinIO | 综合节点 | Docker 部署 | 否 | 更关注数据盘挂载与对象存储目录管理 |
| RustFS | 综合节点 | Docker 部署 | 否 | 更关注数据盘挂载与对象存储目录管理 |
| PostgreSQL 主库 | 主库服务器 | 物理机/虚拟机直接部署 | 否 | 核心数据库本体不建议容器化 |
| PostgreSQL 热备 | 热备服务器 | 物理机/虚拟机直接部署 | 否 | 高可用数据库本体不建议容器化 |
@ -497,7 +563,7 @@ flowchart TB
以下组件不建议采用 `Docker + host` 模式:
1. `Spring Boot` 业务应用:应保留容器网络隔离,便于扩容和端口治理。
2. `MinIO`:应以存储挂载与数据目录管理为重点,不依赖 `host` 模式。
2. `RustFS`:应以存储挂载与数据目录管理为重点,不依赖 `host` 模式。
3. `PostgreSQL 主库 / 热备`:数据库本体优先直接部署,不建议容器化。
4. `Patroni`:建议直接部署,便于与数据库高可用控制链路协同。
@ -509,7 +575,7 @@ flowchart TB
|---|---|---|
| 公网接入区 | 公网入口薄转发 | 对外提供 HTTPS 访问,不纳入我方主机资源 |
| 应用区 | 业务应用节点 | 仅允许内网接入区与运维区访问 |
| 中间件与文件存储区 | Redis、Nacos、MinIO、数据库控制组件 | 仅允许业务应用节点与运维区访问 |
| 中间件与文件存储区 | Redis、Nacos、RustFS、数据库控制组件 | 仅允许业务应用节点与运维区访问 |
| 银行文件交换区 | SFTP/FTP 文件交换服务器 | 仅允许业务应用节点、银行专线或授权外联链路访问 |
| 数据区 | PostgreSQL 主库、热备库、备份存储 | 严格限制,仅允许中间件控制主机和运维区访问 |
@ -535,7 +601,7 @@ flowchart TB
| 对外访问 | 开通 443 端口 | 提供统一 HTTPS 入口 |
| 公网入口薄转发访问内网 Nginx | 开通公网入口到内网 Nginx 的转发端口 | 仅授权链路访问 |
| 内网 Nginx 访问应用 | 开通内网 Nginx 到业务应用的 API / 网关端口 | 仅内网访问 |
| 应用访问综合节点 | 开通 Redis、Nacos、MinIO、PgBouncer 相关端口 | 仅应用区访问 |
| 应用访问综合节点 | 开通 Redis、Nacos、RustFS、PgBouncer 相关端口 | 仅应用区访问 |
| 业务应用访问 SFTP/FTP 服务器 | 开通 SFTP/FTP 相关端口 | 用于银行文件送盘、回盘、对账交换 |
| 银行链路访问 SFTP/FTP 服务器 | 开通 FTP/SFTP 服务端口 | 仅银行授权链路访问 |
| 综合节点访问数据库 | 开通 PostgreSQL 5432 及健康检查相关端口 | 仅综合节点访问 |
@ -551,7 +617,7 @@ flowchart TB
| 业务应用节点(主) | 综合节点 | TCP/6379 | Redis 访问 | 内网放通 |
| 业务应用节点(主) | 综合节点 | TCP/8848 | Nacos 访问 | 内网放通 |
| 业务应用节点(主) | 综合节点 | TCP/6432 | PgBouncer 访问 | 内网放通 |
| 业务应用节点(主) | 综合节点 | TCP/9000,9001 | MinIO 文件访问 | 内网放通 |
| 业务应用节点(主) | 综合节点 | RustFS 对象存储端口 | RustFS 文件访问 | 内网放通 |
| 业务应用节点(主) | SFTP/FTP 文件交换服务器 | TCP/21 或 22 | 银行送盘、回盘、对账文件交换 | 内网放通 |
| 银行专线或授权外联链路 | SFTP/FTP 文件交换服务器 | TCP/21 或 22 | 银行文件目录投递与回收 | 仅授权链路放通 |
| 综合节点 | PostgreSQL 主库服务器 | TCP/5432 | 数据库代理与控制访问 | 内网放通 |
@ -569,7 +635,7 @@ flowchart TB
| Spring Boot Gateway / 业务应用端口 | 项目自定义端口 | 仅内网 Nginx 与内网调用 |
| Redis | 6379 | 仅业务应用与运维可访问 |
| Nacos | 8848 | 仅业务应用与运维可访问 |
| MinIO | 9000/9001 | 仅业务应用与运维可访问 |
| RustFS | 按 RustFS 实际部署端口 | 仅业务应用与运维可访问 |
| SFTP/FTP 服务 | 21 或 22 | 仅业务应用节点、运维和银行授权链路可访问 |
| PgBouncer | 6432 | 仅业务应用可访问 |
| HAProxy | 5000 或项目定义端口 | 仅 PgBouncer 与运维可访问 |
@ -588,7 +654,7 @@ flowchart TB
1. 安装操作系统并完成安全基线加固。
2. 在业务应用节点安装 JDK、部署业务运行环境。
3. 在内网 Nginx 入口节点安装 Nginx。
4. 在综合节点安装 Redis、Nacos、HAProxy、PgBouncer、Patroni、MinIO
4. 在综合节点安装 Redis、Nacos、RustFS、HAProxy、PgBouncer、Patroni。
5. 在 SFTP/FTP 文件交换服务器安装 SFTP/FTP 服务并配置目录结构。
6. 在数据库主备节点安装 PostgreSQL 16。
@ -604,9 +670,9 @@ flowchart TB
1. 在业务应用节点部署 Spring Boot Gateway 和业务应用服务。
2. 在内网 Nginx 入口节点配置 API 转发和内部负载均衡规则。
3. 在综合节点初始化 MinIO 存储桶和访问策略。
3. 在综合节点初始化 RustFS 存储桶和访问策略。
4. 在 SFTP/FTP 文件交换服务器配置送盘、回盘、对账、归档目录。
5. 配置业务应用到 Redis、Nacos、PgBouncer、MinIO 的连接参数。
5. 配置业务应用到 Redis、Nacos、RustFS、PgBouncer 的连接参数。
### 5. 联调与演练
@ -631,7 +697,7 @@ flowchart TB
| 业务应用可用性 | 表务、抄表、收费、账务、发票、报表等核心服务正常 | 业务主流程可执行 |
| Redis 可用性 | 缓存读写正常 | 中间件能力正常 |
| Nacos 可用性 | 配置下发正常 | 配置治理能力正常 |
| MinIO 可用性 | 图片、附件上传下载正常 | 文件存储能力正常 |
| RustFS 可用性 | 图片、附件上传下载正常 | 文件存储能力正常 |
| SFTP/FTP 文件交换 | 送盘、回盘、对账目录读写正常 | 银行文件交换能力正常 |
| 数据库连接能力 | 业务应用可通过 PgBouncer 正常连接数据库 | 代理接入正常 |
| PostgreSQL 主备同步 | 主备复制正常,无严重延迟 | 高可用基础正常 |
@ -647,7 +713,18 @@ flowchart TB
1. 公网入口薄转发不纳入我方主机资源;我方实际部署的统一接入入口为内网 Nginx 入口节点。
2. 2 台业务应用节点作为核心业务集群,节点内同时部署 Spring Boot Gateway 和业务服务,承接微服务统一接入与核心业务处理。
3. 综合节点集中承载 Redis、Nacos、MinIO 及数据库控制组件,进一步压缩一期主机数量并降低部署复杂度。
3. 综合节点集中承载 Redis、Nacos、RustFS 及数据库控制组件,进一步压缩一期主机数量并降低部署复杂度。
## RustFS 产品介绍与选型说明
RustFS 是一款支持私有化部署的 S3 兼容对象存储方案可用于承接图片、附件、导出文件和归档文件等非结构化数据场景。结合本项目当前整体部署方案RustFS 作为综合节点上的对象存储组件,可替代 MinIO 承接对象存储能力。
本项目采用 RustFS 的原因如下:
1. RustFS 具备对象存储能力,适合图片、附件、导出文件和归档文件统一存储。
2. RustFS 支持私有化部署,适合甲方对自主可控和本地化部署的要求。
3. RustFS 可作为独立对象存储组件部署在综合节点中,与缓存、配置治理和数据库控制能力协同运行。
4. 当前方案中,银行文件交换仍由独立的 SFTP/FTP 文件交换服务器承接RustFS 不承担银行送盘、回盘和对账文件交换职责。
4. 新增 1 台 SFTP/FTP 文件交换服务器,作为银行送盘、回盘、对账文件交换专用前置机。
5. PostgreSQL 16 采用同城双可用区 1 主 1 热备部署,满足高可用要求。
6. 网络采用公网接入区、内网接入区、应用区、中间件与文件存储区、银行文件交换区、数据区分层隔离模式,以满足安全性、可维护性和资源审批要求。

View File

@ -16,6 +16,7 @@
- `02_Table_Specs.md`:单表规格补充(历史映射,非主口径)
- `06_Sensitive_Data_Encryption.md`:敏感数据加密方案
- `07_PostgreSQL16_DR_Resource_Application.md`PostgreSQL 16 容灾与资源申请专题说明
- `08_Integrated_Deployment_Design_PlanB.md`:采纳 PostgreSQL 16 方案二后的整体部署方案
## 维护原则

View File

@ -0,0 +1,573 @@
# 营收系统接口规范设计文档
## 1. 文档概述
### 1.1 文档信息
- **文档名称**:营收系统接口规范设计文档
- **版本**1.0
- **编写日期**2024年12月
- **基于原始文档**:营收系统缴费接口 v1.5
### 1.2 设计目标
本文档旨在为营收系统与银行/第三方支付机构之间的接口交互提供标准化、规范化的设计指导,确保系统的高可用性、安全性和可扩展性。
### 1.3 适用范围
- 公用事业单位(水司、电力等)
- 银行机构
- 第三方支付平台
- 系统集成商
## 2. 系统架构设计
### 2.1 整体架构
```
┌─────────────────┐ HTTP/HTTPS ┌─────────────────┐
│ │<──────────────────>│ │
│ 银行/支付平台 │ │ 营收系统 │
│ │ │ │
└─────────────────┘ └─────────────────┘
│ │
│ │
v v
┌─────────────────┐ ┌─────────────────┐
│ 对账文件处理 │ │ 业务数据库 │
└─────────────────┘ └─────────────────┘
```
### 2.2 接口分层设计
```
┌─────────────────────────────────────────────────────────┐
│ 表示层 (Presentation Layer) │
│ HTTP/HTTPS + XML/JSON │
├─────────────────────────────────────────────────────────┤
│ 业务层 (Business Layer) │
│ 查询服务 | 缴费服务 | 代扣服务 | 对账服务 │
├─────────────────────────────────────────────────────────┤
│ 数据层 (Data Layer) │
│ 用户数据 | 账单数据 | 交易数据 │
└─────────────────────────────────────────────────────────┘
```
## 3. 接口设计规范
### 3.1 RESTful设计原则
虽然原系统使用XML格式但建议遵循RESTful设计原则
| 功能模块 | HTTP方法 | 资源路径 | 描述 |
|----------|----------|----------|------|
| 账单查询 | POST | `/api/app/billQuery/query` | 查询用户账单 |
| 账单缴费 | POST | `/api/app/billPay/pay` | 执行缴费操作 |
| 账单红冲 | POST | `/api/app/payInvalid/payInvalid` | 红冲已缴费账单 |
| 代扣签约 | POST | `/api/app/bankWithholding/signing` | 代扣签约 |
| 代扣解约 | POST | `/api/app/bankWithholding/termination` | 代扣解约 |
| 代扣送盘 | POST | `/api/app/bankWithholding/sendDisc` | 代扣送盘 |
| 代扣回盘 | POST | `/api/app/bankWithholding/backDisc` | 代扣回盘 |
### 3.2 数据格式规范
#### 3.2.1 请求格式
- **内容类型**`application/xml``application/json`
- **字符编码**GBKXML或 UTF-8JSON
- **请求方法**POST
#### 3.2.2 响应格式
- **状态码**200 OK业务成功/失败通过返回码区分)
- **内容类型**:与请求格式保持一致
- **响应结构**:统一的响应格式
### 3.3 安全设计规范
#### 3.3.1 加密策略
```
┌─────────────────┐ 加密传输 ┌─────────────────┐
│ 客户端 │ ──────────────> │ 服务端 │
│ │ │ │
│ 1. 数据加密 │ │ 1. 数据解密 │
│ 2. Base64编码 │ │ 2. Base64解码 │
│ 3. HTTP传输 │ │ 3. 业务处理 │
└─────────────────┘ └─────────────────┘
```
#### 3.3.2 支持的加密算法
| 加密类型 | 加密模式 | 填充方式 | 安全等级 |
|----------|----------|----------|----------|
| 3DES | ECB | PKCS7 | 中等 |
| SM2 | C1C3C2/C1C2C3 | - | 高 |
| SM4 | ECB/CBC | PKCS7 | 高 |
#### 3.3.3 请求头设计
```http
Content-Type: application/xml; charset=GBK
EncryptType: 3DES
EncryptMode: ECB
DataType: XML
```
### 3.4 错误处理规范
#### 3.4.1 统一错误码设计
```
AAAAAAA: 成功
DEF0xxx: 业务错误 (0001-0999)
SYS1xxx: 系统错误 (1000-1999)
SEC2xxx: 安全错误 (2000-2999)
NET3xxx: 网络错误 (3000-3999)
```
#### 3.4.2 错误响应格式
```xml
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20240101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>DEF0001</RespCode>
<RespMessage>无相应记录</RespMessage>
<ErrorDetail>
<ErrorCode>DEF0001</ErrorCode>
<ErrorMsg>用户编号123456不存在</ErrorMsg>
<ErrorTime>2024-01-01 12:00:00</ErrorTime>
</ErrorDetail>
</out>
```
## 4. 数据模型设计
### 4.1 核心实体模型
#### 4.1.1 用户实体 (Customer)
```sql
CREATE TABLE customer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bill_key VARCHAR(35) NOT NULL UNIQUE COMMENT '客户编号',
customer_name VARCHAR(150) NOT NULL COMMENT '客户姓名',
contract_no VARCHAR(30) COMMENT '合同号',
company_id VARCHAR(30) NOT NULL COMMENT '机构编码',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bill_key (bill_key),
INDEX idx_company_id (company_id)
);
```
#### 4.1.2 账单实体 (Bill)
```sql
CREATE TABLE bill (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bill_key VARCHAR(35) NOT NULL COMMENT '客户编号',
company_id VARCHAR(30) NOT NULL COMMENT '机构编码',
pay_amount DECIMAL(16,2) NOT NULL COMMENT '缴费金额',
balance DECIMAL(16,2) DEFAULT 0.00 COMMENT '余额',
begin_date DATE COMMENT '账单开始日期',
end_date DATE COMMENT '账单结束日期',
bill_status TINYINT DEFAULT 0 COMMENT '账单状态 0:未缴费 1:已缴费',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bill_key (bill_key),
INDEX idx_company_id (company_id),
INDEX idx_status (bill_status)
);
```
#### 4.1.3 交易记录 (Transaction)
```sql
CREATE TABLE transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tran_seq VARCHAR(40) NOT NULL UNIQUE COMMENT '交易流水号',
bill_key VARCHAR(35) NOT NULL COMMENT '客户编号',
company_id VARCHAR(30) NOT NULL COMMENT '机构编码',
tran_code VARCHAR(20) NOT NULL COMMENT '交易码',
pay_amount DECIMAL(16,2) NOT NULL COMMENT '交易金额',
pay_date DATETIME NOT NULL COMMENT '交易时间',
sub_channel TINYINT COMMENT '二级渠道 1:支付宝 2:微信 6:其它',
tran_status TINYINT DEFAULT 0 COMMENT '交易状态 0:处理中 1:成功 2:失败',
resp_code VARCHAR(7) COMMENT '返回码',
resp_message VARCHAR(60) COMMENT '返回消息',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_tran_seq (tran_seq),
INDEX idx_bill_key (bill_key),
INDEX idx_pay_date (pay_date),
INDEX idx_status (tran_status)
);
```
### 4.2 代扣相关实体
#### 4.2.1 代扣协议 (WithholdingAgreement)
```sql
CREATE TABLE withholding_agreement (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bill_key VARCHAR(35) NOT NULL COMMENT '客户编号',
company_id VARCHAR(30) NOT NULL COMMENT '机构编码',
account_name VARCHAR(150) NOT NULL COMMENT '开户名',
account_no VARCHAR(30) NOT NULL COMMENT '开户账号',
bank_name VARCHAR(150) COMMENT '银行名称',
contract_no VARCHAR(150) COMMENT '合同号',
agreement_no VARCHAR(150) COMMENT '协议号',
bank_type TINYINT COMMENT '银行类型 0:本行 1:他行',
agreement_status TINYINT DEFAULT 0 COMMENT '协议状态 0:未签约 1:已签约 2:已解约',
signing_date DATE COMMENT '签约日期',
termination_date DATE COMMENT '解约日期',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bill_key (bill_key),
INDEX idx_account_no (account_no),
INDEX idx_status (agreement_status)
);
```
## 5. 接口实现规范
### 5.1 查询接口实现
#### 5.1.1 业务流程
```
Client Request → Parameter Validation → Business Logic → Data Query → Response Format → Client Response
```
#### 5.1.2 核心逻辑
```java
@Service
public class BillQueryService {
public QueryResponse queryBill(QueryRequest request) {
// 1. 参数校验
validateRequest(request);
// 2. 业务逻辑处理
List<Bill> bills = billRepository.findByBillKeyAndCompanyId(
request.getBillKey(),
request.getCompanyId()
);
// 3. 构造响应
return buildQueryResponse(bills);
}
private void validateRequest(QueryRequest request) {
if (StringUtils.isEmpty(request.getBillKey())) {
throw new BusinessException("DEF0001", "客户编号不能为空");
}
// 其他校验逻辑...
}
}
```
### 5.2 缴费接口实现
#### 5.2.1 业务流程
```
Client Request → Parameter Validation → Balance Check → Payment Processing → Transaction Record → Response
```
#### 5.2.2 事务处理
```java
@Service
@Transactional
public class BillPayService {
public PayResponse payBill(PayRequest request) {
// 1. 参数校验
validatePayRequest(request);
// 2. 账单查询
Bill bill = billRepository.findByBillKeyAndCompanyId(
request.getBillKey(),
request.getCompanyId()
);
// 3. 金额校验
if (bill.getPayAmount().compareTo(request.getPayAmount()) != 0) {
throw new BusinessException("DEF0002", "缴费金额不匹配");
}
// 4. 更新账单状态
bill.setBillStatus(1);
billRepository.save(bill);
// 5. 记录交易
Transaction transaction = createTransaction(request);
transactionRepository.save(transaction);
// 6. 构造响应
return buildPayResponse(request);
}
}
```
## 6. 性能设计规范
### 6.1 性能指标
| 指标类型 | 要求 | 说明 |
|----------|------|------|
| 响应时间 | < 3秒 | 95%的请求在3秒内响应 |
| 并发量 | 1000 TPS | 支持1000笔/秒的交易处理 |
| 可用性 | 99.9% | 年度可用性不低于99.9% |
| 错误率 | < 0.1% | 系统错误率控制在0.1%以内 |
### 6.2 缓存策略
```java
@Service
public class BillQueryService {
@Cacheable(value = "billCache", key = "#billKey + '_' + #companyId")
public List<Bill> queryBillWithCache(String billKey, String companyId) {
return billRepository.findByBillKeyAndCompanyId(billKey, companyId);
}
}
```
### 6.3 数据库优化
#### 6.3.1 索引设计
- 主要查询字段建立索引
- 复合索引优化多条件查询
- 定期分析索引使用情况
#### 6.3.2 分表策略
- 按时间分表:每月一张交易表
- 按机构分库:不同机构使用不同数据库
## 7. 监控与日志规范
### 7.1 日志规范
#### 7.1.1 日志级别
- ERROR: 系统错误,需要立即处理
- WARN: 业务警告,需要关注
- INFO: 关键业务流程记录
- DEBUG: 调试信息
#### 7.1.2 日志格式
```
[时间戳] [日志级别] [线程名] [类名] [方法名] - [交易流水号] [业务描述] [详细信息]
```
示例:
```
2024-01-01 12:00:00.123 [INFO] [http-thread-1] [BillQueryService] [queryBill] - [TXN123456789012] 查询账单开始 {"billKey":"123456","companyId":"654321"}
```
### 7.2 监控指标
#### 7.2.1 业务监控
- 交易成功率
- 平均响应时间
- 接口调用量
- 错误码分布
#### 7.2.2 系统监控
- CPU使用率
- 内存使用率
- 数据库连接数
- 网络IO
## 8. 部署架构规范
### 8.1 生产环境架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 负载均衡器 │────>│ Web服务器1 │ │ 数据库主库 │
│ (Nginx/F5) │ │ (Tomcat) │────>│ (MySQL) │
│ │ └─────────────────┘ │ │
│ │ ┌─────────────────┐ └─────────────────┘
│ │────>│ Web服务器2 │ ┌─────────────────┐
└─────────────────┘ │ (Tomcat) │────>│ 数据库从库 │
└─────────────────┘ │ (MySQL) │
└─────────────────┘
```
### 8.2 容器化部署
#### 8.2.1 Docker配置
```dockerfile
FROM openjdk:8-jre-alpine
VOLUME /tmp
ADD app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
```
#### 8.2.2 Kubernetes配置
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-api
spec:
replicas: 3
selector:
matchLabels:
app: billing-api
template:
metadata:
labels:
app: billing-api
spec:
containers:
- name: billing-api
image: billing-api:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
```
## 9. 测试规范
### 9.1 测试策略
#### 9.1.1 单元测试
- 覆盖率 >= 80%
- 核心业务逻辑 100% 覆盖
- Mock 外部依赖
#### 9.1.2 集成测试
- 接口层面的集成测试
- 数据库集成测试
- 第三方服务集成测试
#### 9.1.3 性能测试
- 压力测试:测试系统极限
- 负载测试:测试正常负载下的性能
- 稳定性测试:长时间运行测试
### 9.2 测试用例设计
#### 9.2.1 查询接口测试用例
```java
@Test
public void testQueryBill_Success() {
// Given
QueryRequest request = new QueryRequest();
request.setBillKey("123456");
request.setCompanyId("654321");
// When
QueryResponse response = billQueryService.queryBill(request);
// Then
assertEquals("AAAAAAA", response.getRespCode());
assertNotNull(response.getData());
}
@Test
public void testQueryBill_NotFound() {
// Given
QueryRequest request = new QueryRequest();
request.setBillKey("999999");
request.setCompanyId("654321");
// When & Then
BusinessException exception = assertThrows(
BusinessException.class,
() -> billQueryService.queryBill(request)
);
assertEquals("DEF0001", exception.getCode());
}
```
## 10. 安全审计规范
### 10.1 安全审计要求
#### 10.1.1 审计内容
- 所有接口调用记录
- 敏感操作日志
- 异常访问记录
- 系统配置变更
#### 10.1.2 审计日志格式
```json
{
"timestamp": "2024-01-01T12:00:00.123Z",
"event_type": "API_CALL",
"user_id": "bank_001",
"ip_address": "192.168.1.100",
"endpoint": "/api/app/billQuery/query",
"request_id": "TXN123456789012",
"response_code": "AAAAAAA",
"execution_time": 1500,
"data_accessed": {
"bill_key": "123456",
"company_id": "654321"
}
}
```
### 10.2 安全控制措施
#### 10.2.1 访问控制
- IP白名单机制
- API密钥认证
- 请求频率限制
#### 10.2.2 数据保护
- 敏感数据加密存储
- 传输过程加密
- 数据脱敏处理
## 11. 运维规范
### 11.1 发布流程
```
开发环境 → 测试环境 → 预生产环境 → 生产环境
↓ ↓ ↓ ↓
单元测试 集成测试 性能测试 灰度发布
```
### 11.2 回滚策略
#### 11.2.1 快速回滚
- 保留前一版本的部署包
- 数据库版本管理
- 配置文件版本控制
#### 11.2.2 回滚触发条件
- 系统错误率超过阈值
- 响应时间超过预期
- 业务功能异常
## 12. 总结
本规范设计文档为营收系统接口的设计、开发、测试、部署和运维提供了全面的指导。通过遵循这些规范,可以确保系统的:
1. **可靠性**:通过完善的错误处理和事务管理
2. **安全性**:通过多层次的安全控制措施
3. **性能**:通过合理的架构设计和优化策略
4. **可维护性**:通过标准化的代码和文档规范
5. **可扩展性**:通过模块化和微服务架构设计
建议在实际项目中根据具体需求对本规范进行适当调整和完善。

View File

@ -0,0 +1,385 @@
# 营收系统缴费接口文档
**版本:** 1.5
**作者:** 曹红强
**最后更新:** 2023/03/10
## 修订记录
| 版本号 | 修改日期 | 修改人 | 修改内容 |
|--------|----------|--------|----------|
| v1.0 | 2021/03/24 | 曹红强 | 初始版本 |
| v1.1 | 2021/04/04 | 曹红强 | 新增代扣相关接口 |
| v1.2 | 2021/12/21 | 曹红强 | 完善文档结构及说明 |
| v1.3 | 2022/01/25 | 晋腾飞 | 完善文档结构及说明 |
| v1.4 | 2022/05/02 | 晋腾飞 | 添加托收相关接口 |
| v1.5 | 2023/03/10 | 晋腾飞 | 添加加密方式说明 |
## 概述
本文档主要是针对营收系统和银行(或代收机构)间的实时收费、银行代扣、银行托收(小额支付)协议。
## 术语
1. **商户/第三方支付平台/支付平台/第三方/支付公司**:指第三方支付公司(比如支付宝、微信等)或者银行(比如招商银行、平安银行等)。
2. **公用事业单位**:指接入的缴费事业单位,例如自来水公司、电力公司等。第三方支付发起的实时交易经总行和分行转发最终到公用事业单位进行处理。
## 通讯模式
- **通讯协议**HTTP
- **提交方式**POST
- **加密方式**在请求头Header中填写
- **参数格式**HTTP包体中采用XML或JSON格式默认XML格式
- **服务端口和地址**:由公用事业单位提供
- **超时时间**银行方接收接口返回超时时间建议设置为30秒
## 报文说明
### 报文格式
#### XML格式示例
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>缴费渠道</InstId>
<TranCode>交易码</TranCode>
<TranDate>交易日期</TranDate>
<TranSeq>交易流水号</TranSeq>
<TAG1>VALUE1</TAG1>
<TAG2>VALUE2</TAG2>
</in>
```
#### 报文说明
1. **编码方式**:报文中固定写"GBK"实际编码采用GBK
2. **in**:根节点
3. **Version**:固定写"1.0.1"
4. **InstId**缴费渠道最大长度5个字节区别不同缴费渠道
5. **TranCode**:交易码
- 账单查询Query/QueryRes
- 账单缴费Pay/PayRes
6. **TranDate**交易日期格式YYYYMMDD
7. **TranSeq**交易流水号由银行系统产生最大长度40个字节同一天内不可重复
### 报文加密
#### 加密方式说明
加密方式填写在HTTP请求头Header里面支持的加密方式3DES、SM2、SM4
| 字段 | Header字段名 | 长度 | 约束条件 | 说明 |
|------|-------------|------|----------|------|
| 加密类型 | EncryptType | char(20) | 必填 | 3DES,SM2,SM4 (默认3DES) |
| 加密模式 | EncryptMode | char(20) | 必填 | 3DES(ECB), SM2(C1C2C3,C1C3C2), SM4(ECB,CBC) |
| 数据格式 | DataType | char(20) | 必填 | XML或JSON默认XML |
#### 默认加密方式
如果HTTP请求头HeaderEncryptType不填写加密方式默认为XML报文整体采用3DES函数加密ECB模式PKCS7填充。加密后的报文用base64编码。
#### SM2加密方式
HTTP请求头Header字段EncryptType填写SM2默认加密模式为C1C3C2默认报文body数据格式为XML。
#### SM4加密方式
HTTP请求头Header字段EncryptType填写SM4默认加密模式为ECB默认报文body数据格式为XML。
## 报文头
所有接口报文开始都有以下5个固定域
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|----------|--------|------|----------|------|
| 版本号 | Version | char(20) | 必填 | 固定为1.0.1 |
| 缴费渠道 | InstId | char(5) | 必填 | 由水司软件方提供 |
| 交易码 | TranCode | char(20) | 必填 | 见交易码说明 |
| 交易日期 | TranDate | char(8) | 必填 | YYYYMMDD |
| 流水号 | TranSeq | char(40) | 必填 | 银行系统产生的唯一流水号 |
### 交易码说明
- 账单查询Query/QueryRes
- 账单缴费Pay/PayRes
- 代扣签约Signing/SigningRes
- 代扣解约Termination/TerminationRes
- 代扣送盘SendDisc/SendDiscRes
- 代扣回盘BackDisc/BackDiscRes
- 取消代扣CancelDisc/CancelDiscRes
## 接口规范
### 1. 账单查询接口
#### 请求接口
- **接口地址**`/api/app/billQuery/query`
- **请求方法**POST
- **交易码**Query
#### 请求参数
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|----------|--------|------|----------|------|
| 客户编号 | billKey | char(35) | 必填 | 用水户的唯一标识 |
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 开始条数 | beginNum | Num(5) | 可选 | 默认1 |
| 查询条数 | queryNum | Num(5) | 可选 | 默认1最大100 |
| 扩展字段1 | filed1 | char(100) | 可选 | 扩展字段 |
| 扩展字段2 | filed2 | char(100) | 可选 | 扩展字段 |
| 扩展字段3 | filed3 | char(100) | 可选 | 扩展字段 |
| 扩展字段4 | filed4 | char(100) | 可选 | 扩展字段 |
#### 请求示例
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Query</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<beginNum>1</beginNum>
<queryNum>1</queryNum>
<filed1></filed1>
<filed2></filed2>
<filed3></filed3>
<filed4></filed4>
</in>
```
#### 应答参数
| 中文域名 | 标签名 | 长度 | 说明 |
|----------|--------|------|------|
| 返回代码 | RespCode | char(7) | 查询返回代码 |
| 返回说明 | RespMessage | char(60) | 查询返回说明 |
| 客户编号 | billKey | char(35) | 原样返回 |
| 机构编码 | companyId | char(30) | 原样返回 |
| 总条数 | totalNum | Num(5) | 查询结果总条数 |
#### Data数据结构
| 中文域名 | 标签名 | 长度 | 说明 |
|----------|--------|------|------|
| 合同号 | contractNo | char(30) | 合同号码 |
| 客户姓名 | customerName | char(150) | 客户姓名 |
| 余额 | balance | Number(16,2) | 账户余额 |
| 缴费金额 | payAmount | Number(16,2) | 应缴费金额 |
| 开始日期 | beginDate | char(8) | 账单开始日期 |
| 结束日期 | endDate | char(8) | 账单结束日期 |
#### 应答示例
```xml
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>查询成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<totalNum>1</totalNum>
<Data>
<contractNo>123456</contractNo>
<customerName>张三</customerName>
<balance></balance>
<payAmount>2314</payAmount>
<beginDate></beginDate>
<endDate></endDate>
</Data>
</out>
```
### 2. 缴费接口
#### 请求接口
- **接口地址**`/api/app/billPay/pay`
- **请求方法**POST
- **交易码**Pay
#### 请求参数
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|----------|--------|------|----------|------|
| 客户编号 | billKey | char(35) | 必填 | 用水户的唯一标识 |
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 缴费日期 | payDate | char(14) | 必填 | YYYYMMDDHHMMSS |
| 客户姓名 | customerName | char(150) | 必填 | 客户姓名 |
| 缴费金额 | payAmount | Number(16,2) | 必填 | 缴费金额 |
| 合同号 | contractNo | char(30) | 必填 | 合同号码 |
| 查询类型 | queryType | char(1) | 可选 | 默认0客户编号查询1条形码查询 |
| 二级渠道 | subChannel | char(1) | 可选 | 1:支付宝 2:微信 6:其它 |
#### 请求示例
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Pay</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<payDate>20110513081540</payDate>
<customerName>张三</customerName>
<payAmount>5555</payAmount>
<contractNo>123456</contractNo>
</in>
```
#### 应答参数
| 中文域名 | 标签名 | 长度 | 说明 |
|----------|--------|------|------|
| 返回代码 | RespCode | char(7) | 缴费返回代码 |
| 返回说明 | RespMessage | char(60) | 缴费返回说明 |
| 客户编号 | billKey | char(35) | 原样返回 |
| 机构编码 | companyId | char(30) | 原样返回 |
| 交易日期 | payDate | char(14) | 原样返回 |
| 缴费金额 | payAmount | Number(16,2) | 原样返回 |
#### 应答示例
```xml
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayRes</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>缴费成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<payDate>20110513081540</payDate>
<payAmount>5555</payAmount>
</out>
```
### 3. 账单红冲接口
#### 请求接口
- **接口地址**`/api/app/payInvalid/payInvalid`
- **请求方法**POST
- **交易码**PayInvalid
#### 请求参数
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|----------|--------|------|----------|------|
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 交易日期 | payDate | char(14) | 必填 | YYYYMMDDHHMMSS |
| 红冲流水号 | agencyBillNo | char(30) | 必填 | 缴费时的账单流水号 |
| 缴费金额 | payMoney | char(30) | 必填 | 缴费金额 |
**说明**:只可红冲当天收费且未对账交易,红冲成功后银行需发起自动退款。
### 4. 代扣签约接口
#### 请求接口
- **接口地址**`/api/app/bankWithholding/signing`
- **请求方法**POST
- **交易码**Signing
#### 请求参数
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|----------|--------|------|----------|------|
| 客户编号 | billKey | char(35) | 必填 | 用水户的唯一标识 |
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 签约日期 | signingDate | char(8) | 必填 | YYYYMMDD |
| 开户名 | accountName | char(150) | 必填 | 开户名 |
| 开户账号 | accountNo | char(30) | 必填 | 用户的账号 |
| 银行名称 | bankName | char(150) | 可选 | 总行名称 |
| 合同号 | contractNo | char(150) | 可选 | 合同号 |
| 协议号 | agreementNo | char(150) | 可选 | 协议号 |
| 银行类型 | bankType | char(1) | 可选 | 0本行 1他行 |
### 5. 代扣解约接口
#### 请求接口
- **接口地址**`/api/app/bankWithholding/termination`
- **请求方法**POST
- **交易码**Termination
### 6. 代扣送盘接口
#### 请求接口
- **接口地址**`/api/app/bankWithholding/sendDisc`
- **请求方法**POST
- **交易码**SendDisc
### 7. 代扣回盘接口
#### 请求接口
- **接口地址**`/api/app/bankWithholding/backDisc`
- **请求方法**POST
- **交易码**BackDisc
## 对账文件
### 对账文件格式
对账文件为文本文件txt格式编码格式为UTF-8包括明细行和汇总行。行内每个分项之间以"|"为分隔符。
#### 汇总行格式
```
交易笔数|总金额
```
#### 明细行格式
```
交易日期|交易流水号|客户编号|缴费金额|二级渠道|交易类型
```
#### 文件命名规则
```
机构编码(companyId)_对账日期.txt
```
其中对账日期为交易当天日期非当前时间。
## 返回代码说明
| 序号 | 错误码 | 错误说明 |
|------|--------|----------|
| 0 | AAAAAAA | 成功 |
| 1 | DEF0001 | 无相应记录 |
| 2 | DEF0002 | 用户未欠费 |
| 3 | DEF0003 | 与第三方通讯失败 |
| 4 | DEF0004 | 超过受理期,银行不予受理,请至缴费单位缴费 |
## 附录
### 扣款结果标志说明
- **0**:扣款成功,其余状态按失败处理
- **1**:扣款失败,余额不足
- **2**:账号、户名错误
- **3**:账号不存在
- **4**:重复扣款
- **99**:其他原因
### 二级渠道说明
- **1**:支付宝
- **2**:微信
- **6**:其它
### 银行类型说明
- **0**:本行
- **1**:他行

View File

@ -0,0 +1,606 @@
# 营收系统缴费接口
**版本:** 1.5
**作者:** 曹红强
## 修订记录
| 版本号 | 修改日期 | 修改人 | 修改内容 |
|---|---|---|---|
| v1.0 | 2021/03/24 | 曹红强 | 初始版本 |
| v1.1 | 2021/04/04 | 曹红强 | 新增代扣相关接口 |
| v1.2 | 2021/12/21 | 曹红强 | 完善文档结构及说明 |
| v1.3 | 2022/01/25 | 晋腾飞 | 完善文档结构及说明 |
| v1.4 | 2022/05/02 | 晋腾飞 | 添加托收相关接口 |
| v1.5 | 2023/03/10 | 晋腾飞 | 添加加密方式说明 |
## 概述
本文档主要是针对营收系统和银行(或代收机构)间的实时收费、银行代扣、银行托收(小额支付)协议。
## 术语
1. 文档中提到的商户、第三方支付平台、支付平台、第三方、支付公司指的就是第三方支付公司(比如支付宝,微信等)或者银行(比如招商银行,平安银行等)。
2. 公用事业单位:是指从接入的缴费事业单位,例如:自来水公司,电力公司等,第三方支付发的实时交易经总行和分行转发最终到公用事业单位进行处理。
## 通讯模式
公用事业单位和银行系统之间采用 Http 进行通讯,提交方式为 POST加密方式在请求头 Header 填写,接口参数在 Http 包体中采用 XML 或 JSON 格式,默认 XML 格式。服务端口和服务地址,由公用事业单位提供。银行方接收接口返回超时时间建议设置为 30 秒。
## 报文说明
### 报文格式
#### XML
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>缴费渠道</InstId>
<TranCode>交易码</TranCode>
<TranDate>交易日期</TranDate>
<TranSeq>交易流水号</TranSeq>
<TAG1>VALUE1</TAG1>
<TAG2>VALUE2</TAG2>
</in>
```
如上所示为 XML 报文示例:
1. 编码方式在报文中固定写“GBK”但是实际的编码采用 GBK
2. `in`:根节点;
3. `Version`固定写“1.0.1”。
4. `InstId`:缴费渠道,长度最大长度 5 个字节,区别不同缴费渠道
5. `TranCode`:交易码,账单查询时交易码为 Query,查询应答给银行为 QueryRes账单缴费时交易码为 Pay缴费应答给银行时为 PayRes。
6. `TranDate`交易日期YYYYMMDD如 20210101当前交易时间。
7. `TranSeq`:交易流水号,由银行系统产生,用作标识每笔交易的请求,最大长度 40 个字节,并且同一天内该值不可重复。
8. `TAG1`:节点标签名;
9. `VALUE1`:节点值。
### 报文模版
#### 请求
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Query</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>客户编号</billKey>
……
</in>
```
#### 应答
```xml
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>请求的日期原样返回</TranDate>
<TranSeq>请求的日期原样返回</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>成功</RespMessage >
<billKey>客户编号</billKey>
……
<totalNum>1</totalNum>
<Data>
<contractNo>合同号</contractNo>
</Data>
</out>
```
### 报文样例
#### 查询请求
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Query</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<beginNum>1</beginNum>
<queryNum>1</queryNum>
<filed1></filed1>
<filed2></filed2>
<filed3></filed3>
<filed4></filed4>
</in>
```
#### 查询应答
```xml
<?xml version="1.0" encoding="GBK"?>
<out>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>QueryRes</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAAA</RespCode>
<RespMessage>查询成功
</ RespMessage >
<billKey>123456</billKey>
<companyId>654321</companyId>
<item1></item1>
<item2></item2>
<item3></item3>
<item4></item4>
<item5></item5>
<item6></item6>
<item7></item7>
<totalNum>1</totalNum>
<Data>
<contractNo>123456</contractNo>
<customerName>张三</customerName>
<balance></balance>
<payAmount>2314</payAmount>
<beginDate></beginDate>
<endDate></endDate>
<filed1></filed1>
<filed2></filed2>
<filed3></filed3>
<filed4></filed4>
<filed5></filed5>
</Data>
</out>
```
### 销账请求
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>Pay</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<billKey>123456</billKey>
<companyId>654321</companyId>
<payDate>20110513081540</payDate>
<filed1> </filed1>
<filed2> </filed2>
<filed3> </filed3>
<filed4> </filed4>
<customerName>张三</customerName>
<payAmount>5555</payAmount>
<contractNo>123456</contractNo>
</in>
```
### 销账应答
```xml
<?xml version="1.0" encoding="GBK"?>
<in>
<Version>1.0.1</Version>
<InstId>00001</InstId>
<TranCode>PayRes</TranCode>
<TranDate>20180101</TranDate>
<TranSeq>123456789012</TranSeq>
<RespCode>AAAAAA</RespCode>
<RespMessage>缴费成功</RespMessage>
<billKey>123456</billKey>
<companyId>654321</companyId>
<payDate>20110513081540</payDate>
<payAmount>5555</payAmount>
</in>
```
## 报文编码
报文中可能有汉字,需要对报文进行 GBK 编码才能正常显示。
1. 编码方式在报文中固定写“GBK”
## 报文加密
### 加密方式说明
加密方式填写在 http 请求头 Header 里面加密方式有3DES,SM2,SM4加密方式对 body 数据整体进行加密
| 中文域名 | 字段 | 长度 | 约束条件 | 说明 |
|---|---|---|---|---|
| 加密类型 | EncryptType | char(20) | 必填 | 3DES,SM2,SM4 (默认 3DES) |
| 加密模式 | EncryptMode | char(20) | 必填 | 3DES(ECB), SM2(C1C2C3,C1C3C2), SM4(ECB,CBC) |
| 数据格式 | DataType | char(20) | 必填 | XML 或 JOSN (默认 XML) |
### 默认加密方式
如果 http 请求头 HeaderEncryptType不填写加密方式默认为 XML 报文整体采用 3DES 函数加密ECB 模式 PKCS7P 填充。
加密之后的报文,用 base64 编码。
### SM2 加密方式
http 请求头 Header 字段 EncryptType 填写 SM2默认加密模式为 C1C3C2,默认报文 body 数据格式为 XML
### SM4 加密方式
http 请求头 Header 字段 EncryptType 填写 SM4默认加密模式为 ECB,默认报文 body 数据格式为 XML
### 加密请求示例
加密方式请求头填写示例:
```
[Image Placeholder for Encrypt Header Example]
```
加密 body 请求示例:
```
[Image Placeholder for Encrypt Body Example]
```
## 报文头
### 报文头固定域
所有接口报文开始,都有这 5 个域,这里统一说明。查询和缴费的章节,不再说明报文头这 5 个域。
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 版本号 | Version | char(20) | | 必填 | 固定为 1.0.1 |
| 缴费渠道 | InstId | char(5) | | 必填 | 由水司软件方提供 |
| 交易码 | TranCode | char(20) | | 必填 | 见说明 |
| 交易日期 | TranDate | char(8) | | 必填 | YYYYMMDD |
| 流水号 | TranSeq | char(40) | Input2 | 必填 | 见说明 |
说明:
* **交易码:**
* 账单查询时交易码为 Query,查询应答给予银行为 QueryRes
* 账单缴费时交易码为 Pay,缴费应答时为 PayRes
* 代扣签约请求时交易码为 Signing,签约应答时为 SigningRes
* 代扣解约请求时交易码为 Termination,解约应答时为 TerminationRes
* 代扣送盘请求时交易码为 SendDisc,送盘应答时交易码为 SendDiscRes
* 代扣回盘请求时交易码为 BackDisc,回盘应答时交易码为 BackDiscRes
* 取消代扣交易请求时交易码为 CancelDisc,应答码为 CancelDiscRes
* 代扣送盘状态查询交易码为 SendDiscCheck,应答交易码为 SendDiscCheckRes
* 代扣回盘状态查询交易码为 BackDiscCheck,应答交易码为 BackDiscCheckRes
* 客户基本信息查询交易码为 CustomerCheck, 应答交易码为 CustomerCheckRes;
* 实时收费红冲查询交易码为 PayInvalid,应答交易码为 PayInvalidRes;
* 代理收费向公用事业单位发起缴费单对账请求交易码为 PayCheck应答交易码为 PayCheckRes。
* **交易流水号:** 由银行系统产生,用作标识每笔交易的请求,最大长度 40 个字节,并且值不可重复。
## 请求图例
### PostMan
```
[Image Placeholder for PostMan Example]
```
### Swagger
```
[Image Placeholder for Swagger Example]
```
说明:
* 请求返回 415 或 400 Body 加密内容请前后添加双引号
* 请求返回 500 请联系运维人员参与对接
## 1.1 实时收费
### 欠费查询
Query 是从银行向公用事业单位发起的查询缴费单信息的请求报文。
**请求地址:** `/api/app/payCeb/getChargeSearch`
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 客户编号 | billKey | char(35) | Input1 | 必填 | 客户编号 |
| 机构编码 | companyId | char(30) | | 必填 | 由水司软件方提供 |
| 查询类型 | billType | char(10) | | 必填 | 见说明 |
| 起始笔数 | beginNum | Num(3) | | | 查询起始笔数,从 1 开始 |
| 查询笔数 | queryNum | Num(3) | | | 查询笔数 |
| 备用字段 | filed1 | char(100) | Input2 | | 二级渠道,默认 6见说明 |
| 备用字段 | filed2 | char(100) | Input3 | | 预留字段 |
| 备用字段 | filed3 | char(100) | Input4 | | 预留字段 |
| 备用字段 | filed4 | char(100) | Input5 | | 预留字段 |
说明:
* **查询类型:** 默认 0客户编号查询1 条形码查询,客户编号为条形码;
* **二级渠道:**
* 1:支付宝;
* 2微信
* 6实时收费
### 应答报文
QueryRes 是公用事业单位返回给银行的查询应答报文。
| 中文域名 | 标签名 | 长度 | 对应字段 | 说明 |
|---|---|---|---|---|
| 返回代码 | RespCode | 7 | | 查询返回代码 |
| 返回说明 | RespMessage | 60 | | 查询返回说明 |
| 客户编号 | billKey | char(35) | Output1 | 原样返回 |
| 机构编码 | companyId | Num(30) | | 原样返回 |
| 备用字段 | item1 | Num(100) | Output2 | 预留数据域 |
| 备用字段 | item2 | char(100) | Output3 | 预留数据域 |
| 备用字段 | item3 | char(100) | Item1 | 预留数据域 |
| 备用字段 | item4 | char(100) | Item2 | 预留数据域 |
| 备用字段 | item5 | char(100) | Item3 | 预留数据域 |
| 备用字段 | item6 | char(100) | Item4 | 预留数据域 |
| 备用字段 | item7 | char(100) | Item5 | 预留数据域 |
| 总笔数 | totalNum | char(3) | TotalNum | 返回总查询笔数 |
| 合同号 | contractNo | char(30) | ContractNo | 账单唯一标识 |
| 客户姓名 | customerName | char(100) | CustomerName | 循环域 |
| 余额 | balance | Number | Balance | 循环域(单位:分) |
| 应缴金额 | payAmount | Number | Amount | 循环域(单位:分) |
| 起始日期 | beginDate | char(10) | BeginDate | 循环域,停用字段 |
| 截至日期 | endDate | char(10) | EndDate | 循环域,停用字段 |
| 备用字段 | filed1 | char(100) | FormItem1 | 循环域 账期 |
| 备用字段 | filed2 | char(100) | FormItem2 | 循环域 滞纳金 |
| 备用字段 | filed3 | char(100) | FormItem3 | 循环域 |
| 备用字段 | filed4 | char(100) | FormItem4 | 循环域 |
| 备用字段 | filed5 | char(100) | FormItem5 | 循环域 客户地址 |
### 欠费缴纳
Pay 是银行向公用事业单位发起的欠费缴纳的请求报文。
**请求地址:** `/api/app/payCeb/getChargeOffs`
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 客户编号 | billKey | char(35) | Input1 | 必填 | 客户编号 |
| 机构编码 | companyId | char(30) | | 必填 | 由水司软件方提供 |
| 查询类型 | billType | char(10) | | 必填 | 见说明 |
| 交易日期 | payDate | Num(3) | | 必填 | 见说明 |
| 备用字段 | filed1 | char(100) | Input2 | | 帐期 |
| 备用字段 | filed2 | char(100) | Input3 | | 滞纳金(单位:分) |
| 备用字段 | filed3 | char(100) | Item1 | 必填 | 二级渠道,见说明 |
| 备用字段 | filed4 | char(100) | Item2 | | 预留数据域 |
| 客户姓名 | customerName | char(100) | CustomerName | | 查询应答报文取 |
| 缴费金额 | payAmount | Number | Amount | 必填 | (单位:分) |
| 合同号 | contractNo | char(30) | ContractNo | 必填 | 查询接口应答报文取 |
说明:
* **查询类型:** 默认 0客户编号查询1 条形码查询,客户编号为条形码;
* **交易日期:** YYYYMMDDHHMMSS(支付缴费单时系统当前时间)
* **二级渠道:**
* 1支付宝
* 2微信
* 6其它
### 应答报文
PayRes 公用事业单位返回银行的缴费应答报文。
| 中文域名 | 标签名 | 长度 | 对应字段 | 说明 |
|---|---|---|---|---|
| 返回代码 | RespCode | 7 | | 缴费返回代码 |
| 返回说明 | RespMessage | 60 | | 缴费返回说明 |
| 客户编号 | billKey | char(35) | | 原样返回 |
| 机构编码 | companyId | Num(30) | | 原样返回 |
| 交易日期 | payDate | Num(14) | | 原样返回 |
| 缴费金额 | payAmount | Number(16,2) | | 原样返回 |
## 账单红冲
### 请求报文
PayInvalid 是从银行向公用事业单位发起的红冲缴费单信息的请求报文,只可红冲当天收费且未对账交易,红冲成功后银行需发起自动退款。
**请求接口:** `/api/app/payInvalid/payInvalid`
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|---|---|---|---|---|
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 交易日期 | payDate | char(30) | | 交易日期 |
| 红冲流水号 | agencyBillNo | char(30) | | 缴费时的账单流水号 |
| 缴费金额 | payMoney | char(30) | | 缴费金额 |
说明:
* **交易日期:** YYYYMMDDHHMMSS(支付缴费单时系统时间)
### 应答报文
PayInvalidRes 是公用事业单位返回给银行的红冲缴费单信息的应答报文。
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 返回代码 | RespCode | char(30) | | 必填 | 返回代码 |
| 返回说明 | RespMessage | char(30) | | 必填 | 返回说明 |
| 机构编码 | companyId | char(30) | | 必填 | 由水司软件方提供 |
| 交易日期 | payDate | char(30) | | | 交易日期 |
| 红冲流水号 | agencyBillNo | char(30) | | | 缴费时的账单流水号 |
| 缴费金额 | payMoney | char(30) | | | 缴费金额 |
## 对账文件上传
### 账单说明
银行每天日结后,进行批处理,自动生成和传送(通过 FTP 或 SFTP对账文件给公用事业单位。
### 对账文件格式
对账文件为文本文件 txt 格式,编码格式为 UTF-8包括明细行和汇总行。行内每个分项之间以“|”为分隔符。无交易数据时,对账文件有且只有一条汇总行。汇总行放在文件前面,定义如下:
`交易笔数|总金额`
明细行的定义如下:
`交易日期|交易流水号|客户编号|缴费金额|二级渠道|交易类型`
注释:对账文件内的交易流水号要与缴费时的流水号保持一致。
说明:
1. **关于文件命名:**
T 日所有交易按收费单位生成 n 个对账文件,文件名:机构编码(companyId)_对账日期.txt其中对账日期为交易当天日期非当前时间。
例如某水司机构编码companyId为 yhcs_yh则 2021 年 1 月 1 日对账文件yhcs_yh_20210101.txt
2. 文件中只含缴费成功的交易;
3. 缴费金额单位是分;
4. 交易类型:默认 0客户编号查询1 条形码查询;
5. 文件编码格式采用 UTF-8。
### 对账接口
#### 对账说明
由代理收费公司每天日切后,进行批处理,自动生成和传送对账文件给公用事业单位。
#### 请求报文
PayCheck 代理收费向公用事业单位发起的缴费单对账的请求报文。
**请求接口:** `/api/app/payCeb/paymentCheck`
| 中文域名 | 标签名 | 长度 | 约束条件 | 说明 |
|---|---|---|---|---|
| 机构编码 | companyId | char(30) | 必填 | 由水司软件方提供 |
| 对账日期 | payDate | char(8) | 必填 | 需要对账的日期,非实时日期 |
| 交易笔数 | payCount | char(8) | 必填 | 交易总笔数 |
| 交易总金额 | payMoney | char(10) | 必填 | 交易总金额到分 |
| 对账文件名 | fileName | Char(100) | 必填 | 见说明 |
说明:
* **对账文件名:** 机构编码(companyId)_对账日期.txt其中对账日期为交易当天日期非当前时间。
* **交易日期:** YYYYMMDDHHMMSS(支付缴费单时系统时间)
#### 应答报文
PayCheckRes 公用事业单位返回的缴费对账应答报文。
| 中文域名 | 标签名 | 长度 | 约束条件 |
|---|---|---|---|
| 返回代码 | RespCode | 7 | 缴费返回代码 |
| 返回说明 | RespMessage | 60 | 缴费返回说明 |
| 机构编码 | companyId | char(30) | 原样返回 |
| 交易日期 | payDate | char(14) | 原样返回 |
| 缴费金额 | payAmount | Number(16,2) | 原样返回 |
## 1.2 银行代扣
### 代扣签约
Signing 是从银行向公用事业单位发起的代扣签约信息的请求报文。
**请求接口:** `/api/app/bankWithholding/signing`
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 客户编号 | billKey | char(35) | Input1 | 必填 | 客户编号 |
| 机构编码 | companyId | char(30) | | 必填 | 由水司软件方提供 |
| 开户名称 | accountName | char(100) | | 必输 | 开户名称 |
| 开户账号 | accountNo | char(30) | | 必填 | 开户账号 |
| 开户行 | branchName | char(150) | | 必填 | 开户行 |
| 合同号 | contractNo | char(50) | | | 合同号 |
| 备用字段 | filed1 | char(100) | | | 预留字段 |
| 备用字段 | filed2 | char(100) | | | 预留字段 |
| 备用字段 | filed3 | char(100) | | | 预留字段 |
### 应答报文
SigningRes 是公用事业单位返回给银行的查询应答报文。
| 中文域名 | 标签名 | 长度 | 对应字段 | 说明 |
|---|---|---|---|---|
| 返回代码 | RespCode | 7 | | 查询返回代码 |
| 返回说明 | RespMessage | 60 | | 查询返回说明 |
| 客户编号 | billKey | char(35) | | 原样返回 |
| 备用字段 | filed1 | char(100) | | 循环域 |
| 备用字段 | filed2 | char(100) | | 循环域 |
| 备用字段 | filed3 | char(100) | | 循环域 |
### 代扣解约
Termination 是从银行向公用事业单位发起的代扣解约信息的请求报文。
**请求地址:** `/api/app/bankWithholding/termination`
| 中文域名 | 标签名 | 长度 | 对应字段 | 约束条件 | 说明 |
|---|---|---|---|---|---|
| 客户编号 | billKey | char(35) | Input1 | 必填 | 客户编号 |
| 机构编码 | companyId | char(30) | | 必填 | 由水司软件方提供 |
| 开户名称 | accountName | char(100) | | 必输 | 开户名称 |
| 开户账号 | accountNo | char(30) | | 必填 | 开户账号 |
| 开户行 | branchName | char(150) | | 必填 | 开户行 |
| 合同号 | contractNo | char(50) | | | |
| 备用字段 | filed1 | char(100) | | | 预留字段 |
| 备用字段 | filed2 | char(100) | | | 预留字段 |

View File

@ -0,0 +1,487 @@
# REV-004 旧账务数据迁移实施方案 v0
## 1. 文档定位
本文档用于起草福建水务营收系统中“旧账务模型迁移到新系统模型”的第一版实施方案,重点聚焦 `REV-004` 账务处理一期及其直接关联的收费、预存、退款、冲正、坏账、发票追溯对象。
本文档不是最终迁移脚本,也不是最终数据口径裁决书,而是后续正式立项、迁移设计、实现拆解和验收校对的基础方案。
## 2. 背景与目标
当前仓库已经明确:
- `REV-004` 当前正式模型是“账务处理控制模型”
- 旧系统数据字典体现的是“营业账 + 收费 + 预存账户 + 调整申请单 + 发票”的业务财务模型
- 新系统不计划机械复制旧系统的全部精细账务台账表族
- 但若存在旧业务继续迁移与历史查询要求,则必须保证迁移后数据可承接、可追溯、可核对
因此,本方案的目标不是“把旧表名原样搬过来”,而是:
1. 识别旧账务对象与新系统能力边界
2. 明确哪些数据进入新系统在线主模型
3. 明确哪些数据作为历史只读保留
4. 明确哪些数据只保留映射与追溯关系
5. 形成可执行的迁移批次、校验规则和风险控制方案
## 3. 迁移范围
### 3.1 本轮纳入范围
- `AT_CHARGES``AT_CHARGE_DETAILS`
- `CT_ACCOUNTS``CT_ACCOUNT_LOGS`
- `AT_REFUNDS`
- `PM_ACCOUNT_RECORDS``PM_ACCOUNT_RECORD_DETAILS`
- `PM_AMOUNT_RECORDS``PM_AMOUNT_RECORD_DETAILS`
- `PM_PRICE_RECORDS``PM_PRICE_RECORD_DETAILS`
- `PM_PAYMENT_RECORDS``PM_PAYMENT_RECORD_DETAILS`
- `PM_KNOTTY_RECORDS``PM_KNOTTY_RECORD_DETAILS`
- `PM_LATEFEE_RECORDS``PM_LATEFEE_RECORD_DETAILS`
- `PM_PAY_COLLECTS``PM_PAY_SUBTOTALS``PM_PAY_DETAILS`
- `IV_INVOICE_INFOS``IV_INVOICE_DETAIL_INFOS``IV_CHARGE_INVOICE_MAPPINGS`
### 3.2 本轮关联参考范围
- `营收数据字典.md`
- `docs/guides/BACKEND_TABLE_MAPPING.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md``REV-004` 迁移补充
- `docs/design/03_Technical_Design/03_Interface_Design.md` 中“历史查询与迁移校验接口口径”
### 3.3 本轮暂不纳入范围
- 独立总账、明细账、会计分录体系重建
- 独立账务中台或账本引擎
- 旧审批流引擎的完整在线复刻
- 超出 `REV-004` 一期范围的新业务规则扩展
## 4. 迁移原则
### 4.1 语义迁移优先于表结构平移
迁移对象首先是业务语义、状态语义、追溯语义和查询语义,而不是旧表结构本身。
### 4.2 在线承接与历史保留分层
新系统中必须明确区分:
- 在线主模型承接
- 历史只读承接
- 映射追溯承接
不得把所有历史表都作为在线业务表原样重建。
### 4.3 汇总对账与明细追溯并重
迁移验收必须同时支持:
- 汇总级核对
- 明细级追溯
避免只对总数、不支持差异定位。
### 4.4 新旧标识必须保留映射
迁移后至少应保留以下映射能力:
- 原账单号 ↔ 新账单号
- 原流水号 ↔ 新流水号
- 原申请单 ↔ 新业务单号
- 原发票号 ↔ 新发票对象
- 原处理记录 ↔ 新留痕记录
### 4.5 历史查询只读
历史查询接口只承担查询、比对和审计职责,不承担迁移修正、状态补写或在线业务处理职责。
## 5. 新旧模型差异判断
### 5.1 旧模型特征
旧模型主要表现为:
- 以 `FeeId` 为核心的营业账模型
- 以 `AccountId / AccLogId` 为核心的预存账户与流水模型
- 以“汇总表 + 明细表 + 流程字段”构成的账务申请单模型
- 以收费汇总、小计、明细、实时收费日志构成的收费结果模型
- 以发票主表、发票明细、账单映射表构成的发票模型
### 5.2 新模型特征
当前 `REV-004` 正式口径主要表现为:
- 统一入口:`IF-REV-007`
- 统一场景:水量调整、金额调整、退款、冲正、坏账申请
- 统一共性控制:范围校验、状态校验、原交易校验、留痕、审批边界
- 统一结果表达:`AccountingResult`
- 统一留痕对象:`OperationLog`
- 在线主模型承接以 `biz_charge*``bk_transaction*``biz_operat_log*` 为主
### 5.3 迁移结论
本次迁移不应追求“旧表 = 新表”的结构兼容,而应采用三层承接模型:
1. 标准业务承接层
由新系统正式在线对象承接当前有效业务数据与后续处理能力。
2. 兼容映射层
记录旧标识、新标识、状态映射、来源类型和迁移批次,支撑核对和追溯。
3. 历史只读层
保留旧系统难以在线抽象但必须可查的历史明细、审批痕迹与台账信息。
## 6. 迁移分层设计
### 6.1 在线主模型承接层
建议由以下对象主承接:
- `biz_charge` / `biz_charge_detail`
- `bk_transaction*`
- `biz_operat_log*`
- `biz_invoice*`
- `biz_cust_invoice`
- `biz_collection` / `bk_*` 支付与对账对象
适合进入在线主模型的典型数据:
- 当前有效账单
- 当前有效账单明细
- 当前有效收费结果
- 当前有效发票结果
- 当前可继续参与账务处理的退款、冲正、坏账关联对象
### 6.2 兼容映射层
建议新增或规划一组“迁移映射对象”,至少覆盖:
- `legacy_charge_mapping`
- `legacy_account_log_mapping`
- `legacy_finance_record_mapping`
- `legacy_invoice_mapping`
- `legacy_business_trace_mapping`
每条映射建议至少保留:
- `legacySystem`
- `legacyTable`
- `legacyId`
- `legacyBizNo`
- `targetDomain`
- `targetId`
- `targetBizNo`
- `mappingType`
- `migrationBatchNo`
- `migrationTime`
- `mappingStatus`
### 6.3 历史只读层
以下对象默认优先按历史只读保留:
- 旧审批节点痕迹
- 旧汇总/明细台账中的页面型冗余字段
- 旧系统专门为菜单、导出、统计、补打而拆出的历史辅助表
历史只读层必须满足:
- 能按原单号、客户号、账期、处理类型查询
- 能返回新旧标识映射
- 能支持迁移验收和审计追溯
## 7. 迁移对象分类与策略
| 对象类别 | 典型旧表 | 建议策略 | 说明 |
| --- | --- | --- | --- |
| 账单主明细 | `AT_CHARGES``AT_CHARGE_DETAILS` | 在线承接 + 映射 | 属于新系统核心主对象,必须进入在线模型 |
| 账户余额与流水 | `CT_ACCOUNTS``CT_ACCOUNT_LOGS` | 在线承接 + 映射 + 历史补充 | 预存相关场景必须可追溯 |
| 收费结果 | `PM_PAY_*`、实时收费日志 | 在线承接 + 历史只读 | 主结果进入在线模型,细粒度日志可保留只读 |
| 退款对象 | `AT_REFUNDS``PM_ACCOUNT_RECORD*` | 在线承接场景语义 + 历史明细只读 | 统一挂到退款场景,不强求一对一新实体 |
| 调整对象 | `PM_AMOUNT_*``PM_PRICE_*``PM_PAYMENT_*` | 场景归并 + 映射 + 历史只读 | 进入金额/水量调整、冲正等统一场景 |
| 坏账对象 | `PM_KNOTTY_*` | 场景归并 + 映射 | 统一纳入坏账申请场景 |
| 滞纳金减免 | `PM_LATEFEE_*` | 场景归并 + 历史只读 | 视为账务处理衍生场景 |
| 发票对象 | `IV_*` | 在线承接 + 历史映射 | 发票主对象与账单关系必须保留 |
## 8. 推荐迁移批次
### 批次 1基础主数据与主业务对象
目标:
- 锁定客户、账户、账单主明细的迁移基线
对象:
- 客户相关主数据
- `CT_ACCOUNTS`
- `AT_CHARGES`
- `AT_CHARGE_DETAILS`
输出:
- 账单主键映射
- 账户主键映射
- 批次级基础对账报告
### 批次 2收费结果与交易对象
目标:
- 锁定收费、核销、支付、对账相关迁移基线
对象:
- `PM_PAY_COLLECTS`
- `PM_PAY_SUBTOTALS`
- `PM_PAY_DETAILS`
- 实时收费日志
- `bk_transaction*` 对应交易承接对象
输出:
- 收费汇总对账
- 明细流水映射
- 异常流水清单
### 批次 3账务处理历史对象
目标:
- 锁定 `REV-004` 相关旧账务处理历史的承接方式
对象:
- `PM_ACCOUNT_RECORD*`
- `PM_AMOUNT_*`
- `PM_PRICE_*`
- `PM_PAYMENT_*`
- `PM_KNOTTY_*`
- `PM_LATEFEE_*`
- `AT_REFUNDS`
输出:
- 场景归并结果表
- 新旧状态映射表
- 历史只读查询口径
### 批次 4发票对象与账单关系
目标:
- 锁定账单与发票的历史关系、结果状态和追溯路径
对象:
- `IV_INVOICE_INFOS`
- `IV_INVOICE_DETAIL_INFOS`
- `IV_CHARGE_INVOICE_MAPPINGS`
输出:
- 发票映射表
- 账单-发票关系校验报告
- 历史补打与历史查询口径
## 9. 必要映射矩阵
本方案落地前,至少需要产出以下四份矩阵:
### 9.1 旧表到新对象映射矩阵
字段建议:
- 旧表名
- 旧对象语义
- 新对象域
- 新对象名
- 承接方式
- 是否在线承接
- 是否历史只读
- 风险说明
### 9.2 旧字段到新字段映射矩阵
字段建议:
- 旧字段
- 新字段
- 转换规则
- 默认值规则
- 是否允许为空
- 是否需要枚举转换
- 是否需要补充追溯字段
### 9.3 旧状态到新状态映射矩阵
字段建议:
- 旧表名
- 旧状态字段
- 旧状态值
- 新状态字段
- 新状态值
- 是否完全等价
- 若不等价的补充字段
### 9.4 新旧标识映射矩阵
字段建议:
- 原系统单号
- 原表主键
- 新系统业务单号
- 新系统主键
- 迁移批次
- 映射状态
- 是否核验通过
## 10. 最小兼容字段建议
若新模型需要完整支撑迁移与追溯,建议至少预留以下兼容字段或扩展属性:
- `legacySystem`
- `legacyTable`
- `legacyId`
- `legacyBizNo`
- `legacyState`
- `sourceFeeId`
- `sourceAccLogId`
- `sourceInvoiceId`
- `targetBizNo`
- `migrationBatchNo`
- `migrationRemark`
- `tracePayload`
这些字段不一定都进入核心业务主表,但必须有稳定承接位置。
## 11. 迁移校验方案
### 11.1 数量校验
至少校验:
- 账单总数
- 账单明细总数
- 账户总数
- 账户流水总数
- 收费明细总数
- 发票总数
- 迁移历史记录总数
### 11.2 金额校验
至少校验:
- 应收金额汇总
- 实收金额汇总
- 预存余额汇总
- 退款金额汇总
- 坏账金额汇总
- 发票金额汇总
### 11.3 关系校验
至少校验:
- 账单 ↔ 账单明细
- 账单 ↔ 收费明细
- 账单 ↔ 发票映射
- 账户 ↔ 账户流水
- 调整记录 ↔ 原账单 / 新账单
- 退款记录 ↔ 原流水 / 目标流水
### 11.4 业务抽样校验
建议每类场景至少抽样:
- 1 笔预存退款
- 1 笔已销调整
- 1 笔价差调整
- 1 笔坏账申请
- 1 笔发票回写
- 1 笔收费红冲
要求在新系统中能完整回答:
- 原记录是谁
- 对应新记录是谁
- 状态是否一致
- 金额是否一致
- 关联关系是否完整
- 审计追溯是否可查
## 12. 查询与验收接口要求
迁移后至少应满足以下查询能力:
- 按原账单号 / 新账单号查询迁移关系
- 按原流水号 / 新流水号查询收费与退款关系
- 按客户号、账期、处理类型查询历史账务处理记录
- 按发票号、申请单号查询账单与发票关系
- 按迁移批次查看汇总对账结果与差异明细
这些查询能力应优先挂靠现有 `IF-REV-*` 接口族扩展,不建议为迁移单独发明新的正式接口编号体系。
## 13. 风险清单
### 风险 1旧模型一表多义
部分旧表同时承载业务申请、审批流程、结果记录和页面展示字段,难以直接映射到新系统单一对象。
应对:
- 拆分“在线承接字段”和“历史只读字段”
- 不强行一对一建表
### 风险 2状态机不完全等价
旧系统中的 `State``ProcType``BusinessType`、审批节点状态与新系统结果状态不一定一一对应。
应对:
- 必做状态映射矩阵
- 对不等价状态保留 `legacyState`
### 风险 3旧审批流无法在线复刻
旧模型中大量存在 `TaskId``StepId``FlowRemark`,但当前 `REV-004` 只保留审批能力位。
应对:
- 流程痕迹优先按历史只读保留
- 在线模型仅保留审批边界和结果位
### 风险 4收费、发票、退款链条断链
若只迁主对象,不迁关联映射,后续核对、审计和客户查询会断链。
应对:
- 强制保留新旧标识映射
- 发票、收费、退款场景必须建立关系校验
## 14. 推荐实施顺序
1. 完成旧对象清单与迁移分层判断
2. 完成四类映射矩阵
3. 补齐新模型兼容字段与映射承接设计
4. 设计迁移批次与回滚策略
5. 再进入迁移脚本开发与试迁
6. 最后执行汇总校验、明细抽样校验和差异修正
## 15. 当前待补充项
本方案作为 v0 草稿,下一轮至少还需要补以下内容:
- 旧表到当前 backend 真实表的逐表映射
- `REV-004` 新模型兼容字段的正式落位位置
- 迁移批次的执行窗口与冻结策略
- 试迁环境、脚本入口和回滚方案
- 差异处理与复迁规则
## 16. 参考来源
- `docs/design/04_Appendix/Archive/05_Data_Dictionary/营收数据字典.md`
- `docs/guides/BACKEND_TABLE_MAPPING.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/00_Management/07_Migration_Mapping_Template.md`

View File

@ -0,0 +1,888 @@
# Speckit 工作流的人类友好教学文档
## 1. 先说结论
`Speckit` 的理论基础,不是“先写一堆文档”,而是先把一个需求拆成一连串可验证的小闭环,再让每个闭环都留下明确工件。
在这个工作区里,它本质上是在解决四个老问题:
1. 需求很容易一开始就说大了,最后越做越散。
2. 文档、代码、验证和台账经常各说各话。
3. 多仓协作时,大家都在干活,但没人能准确回答“现在到底做到哪了”。
4. 交付时只能靠口头解释,缺少稳定的追溯链。
所以 `Speckit` 不是“文档优先”这么简单,它更准确的说法是:
**用规格驱动的方式,把需求澄清、方案收敛、任务拆解、实现验证、结论回写做成一个个可追溯闭环。**
---
## 2. 它基于什么理论
结合本仓库的 `constitution`、模板和工作流说明,`Speckit` 的底层思想主要来自以下几类工程方法。
### 2.1 单一真源
核心思想是:正式结论必须有唯一归属,不能让多个版本并行竞争。
在本仓库里,这被明确成:
- 正式内容优先回写主文档
- `specs/` 是过程工件和治理工件
- `Archive` 是来源,不是正式结论
- backend/frontend 负责实现,不负责定义正式规格
这对应的是经典的软件工程思想:**single source of truth**。
### 2.2 逐步求精
同一个需求,不在一开始就展开到代码细节,而是逐层收敛:
1. 先确认到底要解决什么问题
2. 再确认边界和约束
3. 再确认要怎么拆
4. 再进入实现和验证
这对应的是:**stepwise refinement**,也就是逐步求精。
### 2.3 需求到交付的可追溯性
每个阶段都要能回答两个问题:
- 你这个结论是从哪里来的?
- 你这个结果后来落到哪里去了?
所以它天然要求:
- spec 能追到 source of truth
- plan 能追到 spec
- tasks 能追到 user story / requirement
- implementation / evidence 能追到 tasks
- 最终结论能回写正式文档和台账
这对应的是:**traceability**。
### 2.4 小步闭环,而不是大步推进
很多团队的问题,不是不会做,而是喜欢一次推进太多内容,导致:
- 范围没锁住
- 任务没拆清
- 验证没前置
- 回写没人做
`Speckit` 的做法是把系统构建看成多个连续闭环,而不是一个大瀑布。
这更接近:**iterative delivery** 和 **feedback loop**
---
## 3. 它到底怎么通过一个个闭环构建系统
可以把 `Speckit` 理解成 6 个连续闭环。
前一个闭环的输出,是后一个闭环的输入。
### 闭环 1范围闭环
目标:先回答“这次到底做什么,不做什么”。
输入:
- 用户意图
- 主文档
- Archive 历史资料
- 当前代码现状
输出:
- `spec.md`
- 明确的纳入范围
- 明确的排除范围
- 明确的验收口径
闭环成功的标志:
- 评审者只看 `spec.md` 就能判断是否超范围
- 团队不会把“以后可能做”误当成“这轮必须做”
这一步解决的是“方向漂移”。
### 闭环 2决策闭环
目标:把范围内仍然模糊的关键问题收敛成可执行决策。
输入:
- `spec.md`
- 现有设计文档
- 冲突来源
- 需要裁决的问题
输出:
- `research.md`
- 一组关键决策和理由
闭环成功的标志:
- 后续规划不再反复争论同一个问题
- 每个重要选择都能说清“为什么这样定”
这一步解决的是“大家都觉得自己理解对,但没有正式裁决”。
### 闭环 3设计闭环
目标:把“需求”转成“可落地结构”。
输入:
- `spec.md`
- `research.md`
输出:
- `plan.md`
- `data-model.md`
- `contracts/*`
- `quickstart.md`
闭环成功的标志:
- 已经知道涉及哪些文档、哪些仓、哪些接口、哪些数据对象
- 已经知道最小验证动作是什么
- 已经知道正式主文档后续该怎么回写
这一步解决的是“知道要做什么,但还不知道怎么组织”。
### 闭环 4任务闭环
目标:把设计转成可分配、可执行、可检查的任务。
输入:
- `plan.md`
- `data-model.md`
- `contracts/*`
- `quickstart.md`
输出:
- `tasks.md`
闭环成功的标志:
- 每个任务都能映射到具体文件、具体仓、具体验证动作
- 每个 user story 都可以独立评审
- 任务不是“继续完善一下”这种抽象话,而是明确动作
这一步解决的是“设计有了,但执行还是靠临场发挥”。
### 闭环 5实现与验证闭环
目标:把任务变成实际改动,并证明改动有效。
输入:
- `tasks.md`
- docs/backend/frontend 各 lane 的执行结果
输出:
- 正式文档修改
- 代码改动
- 校验结果
- evidence / final verdict
闭环成功的标志:
- 改动完成后有对应验证
- 验证结果能追溯到具体基线
- 不是“我本地看起来可以”,而是有可复核证据
这一步解决的是“做了,但没人能确定真的完成了什么”。
### 闭环 6治理回写闭环
目标:把阶段结果沉淀回正式体系,而不是停留在聊天记录或临时分支里。
输入:
- 已完成的文档和代码结果
- 验证结果
- feature 基线
输出:
- 主文档回写
- `01_Project_Progress.md`
- `03_Task_Checklist.md`
- 必要的 evidence 与最终结论
闭环成功的标志:
- 别人不看聊天记录,也能知道当前状态
- 后续 feature 可以直接站在当前结论上继续推进
这一步解决的是“这次做完了,但组织记忆没有留下来”。
---
## 4. 一个需求是怎么一步步长成系统的
可以把整个过程理解成下面这条链:
```text
用户问题
-> spec 锁范围
-> research 锁关键决策
-> plan 锁实施结构
-> tasks 锁执行清单
-> implementation 锁实际改动
-> verification 锁完成证据
-> ledger/main docs 锁组织记忆
```
这条链最重要的地方在于:
**每一步都不是为了“写文档”,而是为了减少下一步的不确定性。**
举例:
- `spec` 不是为了写需求书,而是为了防止做着做着变成另一个项目
- `plan` 不是为了写方案书,而是为了让任务拆解有稳定骨架
- `tasks` 不是为了列清单,而是为了让 lane 执行可分配、可验收
- `verification` 不是为了走流程,而是为了把“我觉得好了”变成“证据表明好了”
---
## 5. 为什么它适合复杂系统,而不只是适合写文档
因为复杂系统最怕的不是工作量大,而是下面这四种失控:
1. 需求失控:边界不清,越做越大
2. 结构失控:模块、接口、数据口径互相冲突
3. 执行失控:任务拆不稳,协作互相阻塞
4. 认知失控:做完后没人说得清当前状态
`Speckit` 的价值,就在于它把这四种失控分别放进不同闭环里处理:
- `spec` 管需求失控
- `plan` / `contracts` / `data-model` 管结构失控
- `tasks` 管执行失控
- `verification` / `ledger sync` 管认知失控
所以它不是“文档流程”,而是一个 **复杂系统治理流程**
---
## 6. 在这个仓库里Speckit 的真实落地方式
`water-workspace` 里,`Speckit` 不是独立于实现的平行世界,而是正式治理层。
职责边界是:
- `water-docs`:负责正式 `spec/plan/tasks/evidence` 和主文档回写
- `water-backend`:负责后端实现与后端验证
- `water-frontend`:负责前端实现与前端验证
- `tmux + worktree`:负责并行执行现场
也就是说:
```text
Speckit 定义“应该做什么、如何验收”
lane 执行“具体怎么做出来”
water-docs 负责“最终正式结论落到哪里”
```
这就是为什么仓库里一直强调:
- `.specify/` 只保留在 `water-docs`
- backend/frontend 不复制第二套 spec-kit
- 正式工件统一回到 `water-docs`
---
## 7. 人最容易误解的几个点
### 误解 1Speckit 就是先写文档再开发
不准确。
更准确的说法是:**先把决策和验收条件固定,再进入开发。**
如果一个需求很小,完全可以不走完整流程。
### 误解 2有了 spec就等于已经设计好了
不对。
`spec` 主要回答“做什么”和“做到什么算完成”。
真正回答“怎么组织、怎么拆、怎么验证”的,是 `plan``data-model``contracts``tasks`
### 误解 3tasks 只是项目管理清单
不对。
`tasks` 在这里是执行接口,作用是把规格层结论翻译成 lane 可以直接消费的动作。
### 误解 4验证是收尾动作
不对。
`Speckit` 里,验证条件应该从 `spec``plan` 阶段就开始定义,而不是等实现完才临时想。
---
## 8. 对新人最实用的理解方式
如果你不想一上来记所有命令,可以只记下面四句话:
1. `spec`:先把边界锁住。
2. `plan`:再把结构和依赖锁住。
3. `tasks`:再把执行动作锁住。
4. `verify + 回写`:最后把结果锁进正式系统。
把它翻成更口语的话就是:
```text
先确认做什么
再确认怎么做
再确认谁去做
最后确认做完没有,并把结论留下来
```
这就是 `Speckit` 最朴素、也最重要的本质。
---
## 9. 一张总图
```text
用户需求
specify / clarify
产出:范围、边界、验收口径
闭环:范围闭环
plan
产出:方案、决策、模型、合同、最小验证
闭环:决策闭环 + 设计闭环
tasks
产出可执行任务、lane 映射、独立验收切片
闭环:任务闭环
implement / verify
产出:代码、文档、验证证据
闭环:实现与验证闭环
ledger sync / main docs update
产出:正式结论、项目进度、任务状态、基线
闭环:治理回写闭环
```
---
## 10. 推荐给团队的教学方式
如果你要拿这套流程教别人,建议不要从命令开始讲,而是按下面顺序讲:
1. 先讲为什么团队会失控:范围、结构、执行、认知四种失控。
2. 再讲 `Speckit` 用哪些闭环分别处理这四种失控。
3. 再讲 `spec -> plan -> tasks -> verify -> 回写` 的工件链。
4. 最后才讲仓库里具体怎么落地到 `water-docs``water-backend``water-frontend`
这样新人先理解“为什么”,再理解“怎么做”,吸收速度会快很多。
---
## 11. 与本仓库口径对应的参考来源
本说明基于以下仓内材料整理:
- `water-docs/.specify/memory/constitution.md`
- `water-docs/.specify/templates/spec-template.md`
- `water-docs/.specify/templates/plan-template.md`
- `water-docs/.specify/templates/tasks-template.md`
- `docs/TMUX_WORKTREE_TEAM_WORKFLOW.md`
- `water-docs/AGENTS.md`
- 现有 `water-docs/specs/*` 工件样例
如果这些基础材料后续更新,本教学文档也应同步修订。
---
## 12. Speckit 与工程控制论的关系
如果从方法论上看,`Speckit` 和钱学森提出的工程控制论确实很接近。
但要说得准确一些,不是“完全一样”,而是:
**`Speckit` 可以理解为一种面向软件研发治理的工程控制论实践。**
### 12.1 相似之处
两者都关心同一件事:
**怎样让一个复杂系统朝目标稳定演化,而不是边做边失控。**
`Speckit` 里,这种相似性主要体现在下面几项。
#### 1. 都先定义目标状态
工程控制论强调,控制之前必须先明确目标。
`Speckit` 里对应的是:
- `spec` 定义目标
- `spec` 定义边界
- `spec` 定义完成条件
也就是说,系统不是“先干起来再说”,而是先定义希望到达什么状态。
#### 2. 都强调观测当前状态
控制不能脱离观测。
`Speckit` 在进入规划和实施前,都会先看:
- 主文档现状
- Archive 来源
- backend / frontend 当前实现
- 验证结果
- 台账状态
这相当于先观察系统当前状态,而不是只看目标。
#### 3. 都依赖偏差分析
控制的核心不是“下命令”,而是发现偏差、修正偏差。
`Speckit` 实际上一直在比较:
- 目标和现状是否一致
- 文档和代码是否一致
- 接口、数据、设计之间是否一致
- 任务完成状态和验收标准是否一致
只要不一致,就说明存在偏差,需要继续修正。
#### 4. 都通过反馈回路持续校正
这和传统“一路写到最后再看结果”的思路不同。
`Speckit` 的典型反馈方式是:
- 范围不清,回到 `spec`
- 关键决策冲突,回到 `research``plan`
- 设计与接口不一致,回到 `contracts` / `data-model`
- 验证不过,回到任务或实现
- 结论未沉淀,回到主文档和台账
这本质上就是反馈控制。
#### 5. 都有分层控制结构
工程控制论往往不是单层控制,而是多层协同。
`Speckit` 也一样:
- `spec` 控目标层
- `plan` 控结构层
- `tasks` 控执行层
- `verify` 控结果层
- `ledger sync` 控组织记忆层
每一层控制的对象不同,但共同服务于整体稳定性。
### 12.2 不同之处
两者也有明显区别。
钱学森的工程控制论,更多研究的是:
- 物理工程系统
- 工业过程
- 技术系统运行中的控制规律
`Speckit` 控制的不是设备本身,而是:
- 需求如何收敛
- 设计如何对齐
- 任务如何拆解
- 实现如何验证
- 结论如何沉淀
所以它控制的是:
**研发过程和交付过程。**
不是狭义的设备控制,而是过程治理。
### 12.3 为什么这个类比有价值
`Speckit` 理解成“工程控制系统”,会比把它理解成“文档流程”更准确。
因为这样你会自然意识到:
1. `spec` 不是装饰品,而是目标设定器。
2. `plan` 不是空洞方案,而是结构控制器。
3. `tasks` 不是待办清单,而是执行调度器。
4. `verify` 不是收尾动作,而是反馈传感器。
5. `ledger` 不是行政记录,而是组织状态记忆。
这样一来,团队就不会把每个工件当成独立文件,而会把它们看成控制链上的不同节点。
### 12.4 一个更贴切的理解
可以把 `Speckit` 理解成下面这个模型:
```text
目标设定
-> 状态观测
-> 偏差识别
-> 方案校正
-> 执行调节
-> 结果验证
-> 组织回写
-> 下一轮控制
```
这条链本身就是一个闭环控制系统。
`Speckit` 的价值,正是在于它把软件研发从“经验驱动、口头协调、事后补救”,变成了“目标明确、过程可观测、偏差可修正、结果可追溯”的受控过程。
### 12.5 教学时可以怎么讲
如果要对团队解释这层关系,建议直接用这句话:
**`Speckit` 不是让大家多写文档,而是用工程控制论的思路,把复杂研发过程变成一个有目标、有反馈、有校正的闭环系统。**
这句话通常比单纯讲命令和模板更容易让人真正理解它的价值。
---
## 13. 小闭环是怎么组织成大闭环的
这是理解 `Speckit` 最关键的一步。
很多人会以为:
- `spec` 是一个闭环
- `plan` 是一个闭环
- `tasks` 是一个闭环
- `implement` 是一个闭环
然后这些闭环只是顺序排开。
其实不是。
更准确的理解是:
**小闭环是分层嵌套的,前一个闭环的输出会变成后一个闭环的控制输入;很多 feature 闭环继续累积,才会形成系统级大闭环。**
### 13.1 先看四层结构
在这个工作区里,可以把闭环分成四层。
#### 第一层:动作闭环
这是最小闭环,通常是一组非常具体的动作:
```text
修改
-> 校验
-> 修正
-> 记录结果
```
例如:
- 改 `12_REV_Detailed.md`
- 执行 `make validate-file`
- 如有链接变更再跑 `make check-links`
- 结果写入 quickstart 或最终结论
这一层解决的是“这一个动作到底有没有做实”。
#### 第二层:用户故事闭环
这是把多个动作闭环拼成一个可以独立评审的能力片段:
```text
一个用户故事
-> 若干文档动作
-> 若干代码动作
-> 独立验证
-> 形成局部结论
```
这一层解决的是“这一个业务片段是否已经独立成立”。
#### 第三层feature 闭环
这是把多个用户故事闭环拼成一个 feature 的完成链:
```text
spec
-> plan
-> tasks
-> implement
-> verify
-> ledger sync
```
这一层解决的是“这个 feature 是否已经成为系统中的稳定能力单元”。
#### 第四层:系统交付闭环
这是最大的闭环:
```text
需求池
-> feature 闭环 1
-> feature 闭环 2
-> feature 闭环 3
-> 主文档与台账持续更新
-> 系统能力版图逐步收敛
```
这一层解决的是“整个系统是否在有秩序地长出来”。
### 13.2 最重要的一句话
可以把这四层关系记成一句话:
**动作闭环保证局部真实故事闭环保证片段成立feature 闭环保证能力落地,系统闭环保证整体演进。**
---
## 14. 用你当前系统做案例讲解
下面直接用你仓库里的 `REV-004``REV-005` 来讲。
### 14.1 系统目标不是一次性实现,而是逐块实现
你现在这个营收系统,不是靠“一次大开发”完成的,而是靠多个 feature 闭环逐步长成。
从当前仓库可以看到几个典型能力块:
- `REV-004`:账务处理一期
- `REV-005`:发票业务流
- `REV-006`:催缴与通知
- `REV-007`:统计分析
这四个 feature 不是平行孤岛,而是在共同补齐营收系统的核心业务版图。
也就是说,系统级大闭环大致是:
```text
营收系统目标
-> 一个个 feature 进入 Speckit
-> 每个 feature 形成可控闭环
-> 每轮闭环结果回写主文档和台账
-> 系统整体能力逐步成形
```
### 14.2 先看 `REV-004`:它在补什么系统能力
`REV-004` 的本质,不是“写一份账务说明”,而是在给系统补一块能力底座:
- 明确账务处理一期边界
- 统一 `IF-REV-007`
- 统一留痕、原始依据、结果状态
- 再按场景拆到账务调整、退款、冲正、坏账申请
这意味着 `REV-004` 的价值不是单一功能点,而是:
**先把账务处理域里的共性控制骨架搭起来。**
如果这块不闭合,后面的退款、冲正、坏账等就会各自长成不同口径。
所以 `REV-004` 这个 feature 闭环,本质上是在给系统建立:
- 账务场景边界
- 账务接口边界
- 账务留痕边界
- 账务状态边界
这是一块“平台型能力闭环”。
### 14.3 再看 `REV-005`:它在补什么系统能力
`REV-005` 的计划更接近实现闭环。
从 [002-rev005-invoice-flow/plan.md](/Volumes/Dpan/github/water-workspace/water-docs/specs/002-rev005-invoice-flow/plan.md) 可以看出,它要补的是:
- 后台发票申请
- 后台单笔/批量开票
- `SYS-008` 异步申请与查询兜底
- 发票结果回写
- 账单与发票关联
- 客户侧查询/下载/推送
这说明 `REV-005` 不是一个接口修补,而是在补一个完整业务结果链:
```text
收费账单
-> 发票申请
-> 外部发票服务协同
-> 查询补偿
-> 结果回写
-> 客户消费结果
```
这是一块“流程型能力闭环”。
### 14.4 为什么 `REV-004``REV-005` 能拼成更大的闭环
因为它们不是随机 feature而是在系统业务链上前后衔接。
可以这样理解:
- `REV-004` 负责账务处理域中的调整、退款、冲正、坏账等控制逻辑
- `REV-005` 负责收费后发票结果的业务闭环
两者共同服务的是“营收处理全过程”的可控性。
举个直白例子:
如果系统里发生退款或冲正,账务状态一定会影响后续发票状态、发票可用性、结果展示和客户消费体验。
所以:
- 没有 `REV-004``REV-005` 可能会缺失上游账务一致性约束
- 没有 `REV-005``REV-004` 的结果又无法完整延伸到发票交付链
它们拼起来,才更接近真实的营收业务闭环。
### 14.5 从小闭环拼成大闭环的真实过程
下面用更接近执行的方式来描述。
#### 第一步:先闭合 `REV-004` 内部的小闭环
例如先闭这些:
1. 范围闭环
2. `IF-REV-007` 统一合同闭环
3. 留痕与结果状态统一闭环
4. 退款/冲正原交易校验闭环
5. 正式文档与执行手册回写闭环
这些闭完后,`REV-004` 才算从“讨论账务”变成“系统已有一套稳定账务处理骨架”。
#### 第二步:再闭合 `REV-005` 内部的小闭环
例如继续闭这些:
1. 发票申请闭环
2. `SYS-008` 查询兜底闭环
3. 发票结果回写闭环
4. 账单-发票关联闭环
5. 客户侧查询/下载/推送闭环
6. backend 编译与文档回写闭环
这些闭完后,`REV-005` 才算从“发票规划”变成“系统已有一条稳定发票结果链”。
#### 第三步:再把 feature 闭环在系统层面接起来
这一步不是再写一份大而全的总方案,而是靠以下机制自然接起来:
- 主文档持续回写
- 接口设计持续统一
- 数据库承接口径持续统一
- 项目进度与任务台账持续记录
- 新 feature 继续站在前一轮闭环结论上推进
这就形成了系统级大闭环:
```text
账务闭环稳定
-> 发票闭环接入
-> 通知闭环接入
-> 统计闭环接入
-> 各能力通过主文档、接口、数据、台账不断统一
-> 营收系统形成整体受控结构
```
### 14.6 为什么主文档和台账这么重要
很多团队的大闭环之所以拼不起来,不是因为他们没有做 feature而是因为
- 每个 feature 的结论只留在聊天记录里
- 代码改了但主文档没回写
- 台账不更新,没人知道当前真实状态
- 下一个 feature 启动时,又重新猜一遍现状
这样 feature 虽然一个个做了,但系统级大闭环拼不起来。
而你这个仓库里一直强调:
- 主文档单一真源
- 重要结果回写 `01_Project_Progress.md`
- 任务闭环更新 `03_Task_Checklist.md`
它的真正作用就是:
**让上一个 feature 的输出,成为下一个 feature 的稳定输入。**
这正是大闭环成立的关键条件。
---
## 15. 一个更贴近实现目标的系统级示意图
如果你的最终目标是“把营收系统真实实现出来”,可以把当前过程理解成下面这张图:
```text
系统目标:形成可交付、可实现、可验证的营收系统
Feature AREV-004 账务处理一期
- 闭合范围
- 闭合接口
- 闭合留痕与状态
- 闭合账务场景控制骨架
Feature BREV-005 发票业务流
- 闭合申请
- 闭合查询兜底
- 闭合结果回写
- 闭合客户消费链路
Feature CREV-006 催缴与通知
- 闭合任务生成
- 闭合消息协同
- 闭合结果回写
Feature DREV-007 统计分析
- 闭合统计口径
- 闭合指标来源
- 闭合输出与追溯
主文档 / 接口设计 / 数据库设计 / 台账 / evidence 持续统一
系统级大闭环:各业务域能力逐步收敛成一个整体系统
```
这张图里最重要的是:
不是先有“大闭环设计”,再去硬塞小闭环。
而是每个 feature 先形成稳定能力块,再通过统一主文档、统一接口、统一数据库口径、统一台账回写,把这些能力块拼成系统级闭环。
---
## 16. 给当前实施目标的直接建议
如果你现在的目标是“以系统实现为目的继续推进”,那最实用的策略不是问“什么时候整个系统才算闭环”,而是每一轮都问下面三个问题:
1. 当前这个 feature补的是系统里的哪一块能力
2. 这块能力内部还有哪些小闭环没闭?
3. 这轮闭合后的结果,会通过哪些主文档、接口、数据和台账,变成下一个 feature 的输入?
只要这三个问题一直答得清楚,系统级大闭环就会自然长出来。
反过来说,如果这三个问题答不清楚,就算团队一直在干活,也很容易变成“局部热闹,整体失控”。

View File

@ -0,0 +1,335 @@
# 营收系统能力地图与闭环进度图
## 1. 文档目的
本文用于回答三个实际问题:
1. 当前 `REV-004``REV-005``REV-006``REV-007` 分别在补系统的哪一块能力?
2. 这些 feature 当前各自走到了 `spec / plan / tasks / implement / verify / 回写` 的哪一步?
3. 如果目标是“继续把系统实现出来”,下一轮最应该优先闭哪一块?
这份图不是替代 `specs/`,而是把多个 feature 的局部闭环放到同一张系统视角里,帮助统一判断整体推进顺序。
---
## 2. 一张总图
```text
福建水务营收系统
收费与账单主链
- REV-002 开账计费与账单生成
- REV-003 收费核销处理
账后控制与结果链
- REV-004 账务处理一期
- REV-005 发票业务流
- REV-006 催缴与通知
- REV-007 营收统计查询
主文档 / 接口设计 / 数据库设计 / 台账 / evidence 持续统一
系统级大闭环
```
这条链的意思不是“后面的 feature 必须等前面的全做完才能动”,而是:
- `REV-004``REV-005``REV-006``REV-007` 都属于收费后处理与经营结果链条
- 它们既可以分阶段推进,也必须最终回到统一的主文档、接口、数据库和治理台账上
---
## 3. 当前能力地图
### 3.1 feature 与系统能力的对应关系
| Feature | 系统能力类型 | 核心目标 | 当前角色 |
| --- | --- | --- | --- |
| `REV-004` | 账后控制能力 | 统一账务调整、退款、冲正、坏账申请的一期边界、接口、留痕和状态口径 | 底座型闭环 |
| `REV-005` | 发票结果链能力 | 打通发票申请、外部协同、查询兜底、结果回写与客户侧消费 | 流程型闭环 |
| `REV-006` | 催缴协同能力 | 打通催缴对象生成、通知协同、结果回写、停复水边界和历史查询口径 | 协同型闭环 |
| `REV-007` | 经营观察能力 | 明确统计主题、指标口径、查询维度、接口边界和数据承接口径 | 观察型闭环 |
### 3.2 这四块能力在业务链上的位置
```text
账单 / 收费结果
├─ REV-004账后调整与异常处理
├─ REV-005发票申请、开具、回写、客户消费
├─ REV-006欠费提醒、通知协同、结果承接
└─ REV-007经营统计、收费统计、欠费统计、渠道分析
```
因此这四个 feature 不是平级替代关系,而是共同围绕“收费后的业务控制、结果消费、消息协同、经营观察”形成系统后半段。
---
## 4. 闭环进度图
## 4.1 统一阶段定义
为了避免“已经做了很多”但说不清做到哪,下面统一用六段来标识每个 feature 的阶段:
1. `spec`:范围和验收口径已锁定
2. `plan`:结构、依赖、接口/数据设计已组织
3. `tasks`:任务拆解已能直接指导执行
4. `implement`:文档或代码已进入实际落地
5. `verify`:已有验证证据或待补证据边界已明确
6. `ledger sync`:主文档、进度台账、任务台账已统一回写
## 4.2 当前 feature 闭环进度
| Feature | spec | plan | tasks | implement | verify | ledger sync | 当前判断 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `REV-004` | 已完成 | 已完成 | 已完成 | 文档 implement 已完成 | 文档校验已完成 | 已完成 | 文档闭环完成,代码闭环未启动 |
| `REV-005` | 已完成 | 已完成 | 已完成 | 已进入 backend implement 并完成 US1~US4 | Verification Pending | 已完成 | 实现态闭环基本完成,运行态证据待补 |
| `REV-006` | 已完成 | 已完成 | 已完成 | implement 阶段文档收口已完成 | 文档校验已完成 | 已完成 | 治理闭环完成backend 仍未见明确实现 |
| `REV-007` | 已完成 | 已完成 | 已完成 | implement 阶段正式文档收口推进中 | 文档校验与治理校验为主 | 已完成 | 设计闭环已建立,代码入口未见明确实现 |
### 4.3 用符号再看一遍
```text
REV-004: spec ✓ -> plan ✓ -> tasks ✓ -> implement(文档) ✓ -> verify(文档) ✓ -> ledger ✓
REV-005: spec ✓ -> plan ✓ -> tasks ✓ -> implement(代码+文档) ✓ -> verify(运行态待补) △ -> ledger ✓
REV-006: spec ✓ -> plan ✓ -> tasks ✓ -> implement(文档) ✓ -> verify(文档) ✓ -> ledger ✓
REV-007: spec ✓ -> plan ✓ -> tasks ✓ -> implement(文档) ✓ -> verify(设计/治理) ✓ -> ledger ✓
```
最关键的区别是:
- `REV-004 / REV-006 / REV-007` 当前主要是“文档与治理闭环”
- `REV-005` 已经进入“代码实现闭环”
这决定了后续系统推进的优先级不能一刀切。
---
## 5. 依赖关系图
## 5.1 业务依赖
```text
REV-002/REV-003 产出账单与收费结果
REV-004 处理账后调整、退款、冲正、坏账
REV-005 消费收费/账单结果形成发票闭环
REV-006 基于欠费/催缴对象形成通知协同闭环
REV-007 对账单、收费、欠费、渠道、客户结果做统计观察
```
## 5.2 方法依赖
```text
REV-004 先统一账务状态、留痕、原交易校验口径
有助于 REV-005 / REV-006 / REV-007 在状态、日志、追溯上保持一致
```
因此:
- `REV-004` 更像后续 feature 的控制骨架
- `REV-005` 更像当前最接近真实交付的结果链
- `REV-006` 更像待转实现的协同链
- `REV-007` 更像待转实现的观察链
---
## 6. 从“小闭环”到“系统大闭环”的拼接方式
## 6.1 feature 内部怎么闭
每个 feature 先把自己内部的小闭环闭起来。
### `REV-004`
- 范围闭环:五类场景收敛
- 合同闭环:`IF-REV-007`
- 共性能力闭环:留痕、原始依据、结果状态、审批边界
- 治理闭环:执行手册、项目进度、任务清单回写
### `REV-005`
- 申请闭环:后台发票申请与校验
- 协同闭环:`SYS-008` 异步申请与查询兜底
- 回写闭环:发票状态、账单关联、客户侧消费
- 二期入口闭环:作废与红冲最小入口
- 验证闭环:实现态证据已补,运行态样本待补
### `REV-006`
- 设计闭环:催缴对象、通知事件、结果状态四态、停复水边界
- 接口闭环:`IF-REV-013``IF-EXT-008`
- 治理闭环implement 阶段文档收口与台账回写
### `REV-007`
- 设计闭环:统计主题、维度、指标与排除项
- 接口闭环:`IF-REV-010`
- 数据闭环:聚合来源与承接口径
- 治理闭环:实现评估与正式台账回写
## 6.2 feature 之间怎么拼
系统大闭环不是再额外建一个“超级文档”,而是依靠四类统一机制把它们接起来:
1. 主文档统一
所有结论最终回到 `12_REV_Detailed.md``03_Interface_Design.md``01_Database_Design.md`
2. 台账统一
重要动作回写 `01_Project_Progress.md``03_Task_Checklist.md`
3. 接口统一
各 feature 都落到同一套 `IF-*` 体系中
4. 数据承接口径统一
不让每个 feature 各自发明自己的状态、日志、承接模型
这四类统一机制的作用就是:
**让上一个闭环的输出,自动成为下一个闭环的稳定输入。**
---
## 7. 当前系统视角下的真实判断
### 7.1 哪些 feature 已经是“稳定能力块”
- `REV-004`:是,文档与治理层面已稳定
- `REV-005`:是,已形成实现态稳定能力块
- `REV-006`:是,设计与治理层面已稳定
- `REV-007`:是,设计与治理层面已稳定
### 7.2 哪些 feature 还不是“完整业务闭环”
- `REV-004`:还不是完整实现闭环,因为代码侧尚未启动
- `REV-006`:还不是完整实现闭环,因为 backend 仍未见明确实现
- `REV-007`:还不是完整实现闭环,因为代码入口仍未见明确实现
- `REV-005`:最接近完整业务闭环,但仍缺运行态样本、联调统计与物理 DDL 风险闭合
### 7.3 当前系统最大的断点在哪里
当前最大的断点不是设计断点,而是:
**设计闭环与实现闭环之间的转换断点。**
换句话说:
- `REV-006``REV-007` 都已经具备进入实现的前置条件
- 但还没有真正转成 backend 可验证的实现链
这说明现在系统推进的主矛盾,已经不是“要不要继续补文档”,而是:
**哪一块设计基线应当最先转成实现闭环。**
---
## 8. 面向“系统实现”为目标的优先级建议
## 8.1 第一优先级:完成 `REV-005` 的 verify 收口
原因:
- 它是四个 feature 里唯一已经深入到 backend implement 的
- 再补少量运行态证据,就能形成最完整的一条真实业务闭环
- 这会给后续 feature 提供一个“设计如何转实现、实现如何转验证”的标准样板
建议优先补:
- 运行态联调样本
- `T055``T060 ~ T063` 对应的验证记录
- `biz_invoice` 物理 DDL / migration 风险的最终判断
## 8.2 第二优先级:启动 `REV-006` 的 backend 最小实现闭环
原因:
- `REV-006` 已完成 `spec -> plan -> tasks -> implement(文档)` 全链条
- 它适合作为“从设计闭环转实现闭环”的下一块
- 相比 `REV-007`,它更直接落在业务控制与消息协同链上,更贴近真实业务运行
最小切入建议:
- 先做催缴对象生成
- 再做通知事件触发
- 再做结果回写
- 最后做停复水边界联动
## 8.3 第三优先级:评估 `REV-004` 二期还是 `REV-007` 实现入口
这一步要看你的系统目标偏哪边:
- 如果更偏“业务控制完善”,优先 `REV-004`
- 如果更偏“经营观察与管理看板”,优先 `REV-007`
当前从系统运行价值看,我更建议:
**`REV-004` 优先于 `REV-007`。**
原因是:
- `REV-004` 会影响退款、冲正、坏账、账务状态与留痕一致性
- 这是多个后续流程共享的控制骨架
- `REV-007` 的价值也高,但它更偏“看清系统”,不是“先让系统可控”
---
## 9. 一个可以直接执行的推进顺序
如果以“尽快把系统大闭环往前推”为目标,建议按下面顺序推进:
```text
第一步:补完 REV-005 verify 收口
目标:形成第一条最完整的实现态业务闭环
第二步:启动 REV-006 backend 最小实现
目标:把设计闭环成功转成协同实现闭环
第三步:启动 REV-004 二期实现
目标:把账务控制骨架从文档闭环转成实现闭环
第四步:启动 REV-007 最小查询实现
目标:让经营观察能力从“设计已定义”进入“查询可验证”
```
这条顺序的逻辑是:
- 先拿 `REV-005` 固化“实现闭环样板”
- 再拿 `REV-006` 证明“设计闭环可转实现闭环”
- 再拿 `REV-004` 固化系统控制骨架
- 最后拿 `REV-007` 打开经营观察能力
---
## 10. 结论
当前仓库已经不是“还在摸索 feature 是什么”,而是已经形成了四块明确的系统能力板块。
真实状态可以概括为:
- `REV-004`:账务控制骨架已闭合,待转实现
- `REV-005`:实现链最完整,待补运行态证据
- `REV-006`:协同设计已闭合,待转实现
- `REV-007`:统计设计已闭合,待转实现
因此,从系统大闭环视角看,下一阶段最重要的事情不是继续横向铺更多 spec而是
**把已经完成设计闭环的能力块,逐步转成实现闭环。**
这就是当前从“文档治理阶段”走向“系统实现阶段”的真正分水岭。
---
## 11. 参考依据
本文基于以下仓内工件整理:
- `specs/001-rev004-accounting/`
- `specs/002-rev005-invoice-flow/`
- `specs/003-rev006-reminder-event-design/`
- `specs/004-rev007-revenue-statistics-design/`
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/03_Task_Checklist.md`
- `docs/design/00_Management/15_SYS002_Requirement_Breakdown.md`

View File

@ -0,0 +1,419 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.6.6 Chrome/144.0.7559.236 Electron/40.8.4 Safari/537.36" version="29.6.6" pages="3">
<diagram id="zones" name="网络分区图">
<mxGraphModel grid="1" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="1800" pageHeight="1000" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="9ridG9Hz4lSlouddoKVb-23" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="核心区" vertex="1">
<mxGeometry height="600" width="380" x="1060" y="-64" as="geometry" />
</mxCell>
<mxCell id="9ridG9Hz4lSlouddoKVb-22" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="办公区" vertex="1">
<mxGeometry height="178" width="200" x="140" y="-68" as="geometry" />
</mxCell>
<mxCell id="z1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Untrust&#xa;外网区域" vertex="1">
<mxGeometry height="424" width="200" x="140" y="120" as="geometry" />
</mxCell>
<mxCell id="m1" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.people.standing_man_2;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="移动端用户" vertex="1">
<mxGeometry height="70" width="40" x="230" y="135" as="geometry" />
</mxCell>
<mxCell id="m2" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="210" y="264" as="geometry" />
</mxCell>
<mxCell id="m3" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.buildings.government_building;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="80" x="210" y="367" as="geometry" />
</mxCell>
<mxCell id="z2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="DMZ&#xa;对外服务区" vertex="1">
<mxGeometry height="608" width="280" x="370" y="-68" as="geometry" />
</mxCell>
<mxCell id="nginx" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.www_server;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="Nginx 入口" vertex="1">
<mxGeometry height="82" width="85" x="480" y="123" as="geometry" />
</mxCell>
<mxCell id="ftp" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="70" x="565" y="367" as="geometry" />
</mxCell>
<mxCell id="z3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="服务器区域" vertex="1">
<mxGeometry height="610" width="330" x="670" y="-70" as="geometry" />
</mxCell>
<mxCell id="pc" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="190" y="-20" as="geometry" />
</mxCell>
<mxCell id="svc" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="综合节点&lt;br&gt;中间件&lt;div&gt;数据库存储控制&lt;/div&gt;" vertex="1">
<mxGeometry height="73.38" width="80" x="780" y="345.31" as="geometry" />
</mxCell>
<mxCell id="db1" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1100" y="330" as="geometry" />
</mxCell>
<mxCell id="db2" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1100" y="180" as="geometry" />
</mxCell>
<mxCell id="bak" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="60" x="1360" y="180" as="geometry" />
</mxCell>
<mxCell id="e1" edge="1" parent="1" source="m1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="nginx" value="HTTPS 443">
<mxGeometry relative="1" as="geometry">
<mxPoint x="380" y="145" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e2" edge="1" parent="1" source="m2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" target="nginx" value="HTTPS/API">
<mxGeometry relative="1" as="geometry">
<mxPoint x="415" y="171.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e2b" edge="1" parent="1" source="m3" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ftp" value="公网隧道&lt;span style=&quot;background-color: light-dark(#ffffff, var(--ge-dark-color, #121212)); color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;/专线&lt;/span&gt;21/22 银行文件交换">
<mxGeometry relative="1" x="0.1096" y="-3" as="geometry">
<mxPoint as="offset" />
<mxPoint x="380" y="407" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e3" edge="1" parent="1" source="pc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="nginx" value="办公网访问,内网 IP">
<mxGeometry relative="1" as="geometry">
<mxPoint x="510" y="104" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e4" edge="1" parent="1" source="nginx" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="9ridG9Hz4lSlouddoKVb-13" value="API转发">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="560" y="124" />
<mxPoint x="560" y="130" />
<mxPoint x="694" y="130" />
</Array>
<mxPoint x="565" y="165" as="sourcePoint" />
<mxPoint x="817.3199999999999" y="256.38000000000045" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e8" edge="1" parent="1" source="9ridG9Hz4lSlouddoKVb-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="svc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e10" edge="1" parent="1" source="9ridG9Hz4lSlouddoKVb-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.035;exitY=0.76;exitDx=0;exitDy=0;exitPerimeter=0;" target="ftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="703" y="210" />
<mxPoint x="600" y="210" />
</Array>
<mxPoint x="1010" y="286" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="e11" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="db1" value="5432 数据库访问">
<mxGeometry relative="1" x="-0.0013" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="e12" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;dashed=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="db2" value="5432 状态探测">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="980" y="350" />
<mxPoint x="980" y="222" />
</Array>
<mxPoint x="1160" y="446" as="sourcePoint" />
<mxPoint x="1339" y="104.00000000000023" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="e13" edge="1" parent="1" source="db1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="db2" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e14" edge="1" parent="1" source="db1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="bak" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e15" edge="1" parent="1" source="db2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="bak" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e16" edge="1" parent="1" source="svc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.9;exitY=0.9;exitDx=0;exitDy=0;exitPerimeter=0;" target="bak" value="文件归档">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="852" y="440" />
<mxPoint x="1420" y="440" />
</Array>
<mxPoint x="1130" y="468.04761904761926" as="sourcePoint" />
<mxPoint x="1487.0476190476188" y="371.99999999999955" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="HN1-GFPAo3kp1Sdrea_3-7" edge="1" parent="1" source="z1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" target="z1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="app1" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="73" width="90" x="775" y="144" as="geometry" />
</mxCell>
<mxCell id="app2" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="72" width="90" x="777.5" y="-5" as="geometry" />
</mxCell>
<mxCell id="9ridG9Hz4lSlouddoKVb-13" parent="1" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;sketch=0;pointerEvents=1;strokeColor=#99CCFF;strokeWidth=2;align=center;verticalAlign=top;fontFamily=Helvetica;fontSize=12;fontColor=default;fillColor=none;perimeterSpacing=6;" value="" vertex="1">
<mxGeometry height="305" width="240" x="701.5" y="-22" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="links" name="网络连接图">
<mxGraphModel grid="1" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="1800" pageHeight="1000" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="lz1" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Untrust&#xa;外网区域" vertex="1">
<mxGeometry height="220" width="260" x="30" y="90" as="geometry" />
</mxCell>
<mxCell id="lm1" parent="1" style="shape=mxgraph.cisco.people.man;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="移动端用户" vertex="1">
<mxGeometry height="80" width="80" x="60" y="145" as="geometry" />
</mxCell>
<mxCell id="lm2" parent="1" style="shape=mxgraph.cisco.misc.generic_building;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="160" y="145" as="geometry" />
</mxCell>
<mxCell id="lm3" parent="1" style="shape=mxgraph.cisco.misc.generic_building;html=1;fillColor=#ffffff;strokeColor=#ea580c;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="110" x="95" y="230" as="geometry" />
</mxCell>
<mxCell id="ldmz" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="DMZ&#xa;对外服务区" vertex="1">
<mxGeometry height="250" width="300" x="340" y="70" as="geometry" />
</mxCell>
<mxCell id="lnginx" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#d97706;" value="内网 Nginx 入口节点" vertex="1">
<mxGeometry height="82" width="105" x="435" y="118" as="geometry" />
</mxCell>
<mxCell id="lftp" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#fff1f2;strokeColor=#dc2626;strokeWidth=2;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="145" x="415" y="220" as="geometry" />
</mxCell>
<mxCell id="llan" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="LAN&#xa;办公与应用区" vertex="1">
<mxGeometry height="360" width="560" x="690" y="50" as="geometry" />
</mxCell>
<mxCell id="lpc" parent="1" style="shape=mxgraph.cisco.people.man;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="720" y="108" as="geometry" />
</mxCell>
<mxCell id="lsw" parent="1" style="shape=mxgraph.cisco.switches.workgroup_switch;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="应用区交换" vertex="1">
<mxGeometry height="74" width="104" x="870" y="130" as="geometry" />
</mxCell>
<mxCell id="lapp1" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="96" width="120" x="1010" y="102" as="geometry" />
</mxCell>
<mxCell id="lapp2" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#ffffff;strokeColor=#2563eb;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="96" width="120" x="1010" y="228" as="geometry" />
</mxCell>
<mxCell id="lsvc" parent="1" style="shape=mxgraph.cisco.servers.server;html=1;fillColor=#f8f4ff;strokeColor=#7c3aed;strokeWidth=2;" value="综合节点&#xa;缓存: Redis&#xa;配置: Nacos&#xa;对象存储: MinIO&#xa;数据库控制: HAProxy / PgBouncer / Patroni" vertex="1">
<mxGeometry height="134" width="200" x="800" y="245" as="geometry" />
</mxCell>
<mxCell id="lcore" parent="1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ecfdf5;strokeColor=#059669;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="Core&#xa;核心数据区" vertex="1">
<mxGeometry height="280" width="470" x="1300" y="90" as="geometry" />
</mxCell>
<mxCell id="ldb1" parent="1" style="shape=mxgraph.cisco.servers.database;html=1;fillColor=#ffffff;strokeColor=#059669;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1355" y="145" as="geometry" />
</mxCell>
<mxCell id="ldb2" parent="1" style="shape=mxgraph.cisco.servers.database;html=1;fillColor=#ffffff;strokeColor=#059669;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1510" y="145" as="geometry" />
</mxCell>
<mxCell id="lbak" parent="1" style="shape=mxgraph.cisco.storage.storage_array;html=1;fillColor=#ffffff;strokeColor=#d97706;strokeWidth=2;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="170" x="1432" y="260" as="geometry" />
</mxCell>
<mxCell id="le1" edge="1" parent="1" source="lm1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="HTTPS 443">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le2" edge="1" parent="1" source="lm2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="HTTPS/API">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le2b" edge="1" parent="1" source="lm3" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 银行文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le3" edge="1" parent="1" source="lpc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lnginx" value="办公网访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le4" edge="1" parent="1" source="lnginx" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsw" value="API转发">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le5" edge="1" parent="1" source="lsw" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lapp1" value="节点1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le6" edge="1" parent="1" source="lsw" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lapp2" value="节点2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le7" edge="1" parent="1" source="lapp1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsvc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le8" edge="1" parent="1" source="lapp2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lsvc" value="业务访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le9" edge="1" parent="1" source="lapp1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le10" edge="1" parent="1" source="lapp2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lftp" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le11" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ldb1" value="5432 数据库访问">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le12" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;dashed=1;" target="ldb2" value="5432 状态探测">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le13" edge="1" parent="1" source="ldb1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="ldb2" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le14" edge="1" parent="1" source="ldb1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le15" edge="1" parent="1" source="ldb2" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="le16" edge="1" parent="1" source="lsvc" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="lbak" value="文件归档">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="1P_7QRTpX64gtdREb-dQ" name="讨论版本">
<mxGraphModel dx="1298" dy="853" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="cTkcAQCcS-jnWanXpuNl-1" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="内网区" vertex="1">
<mxGeometry height="610" width="470" x="920" y="480" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#eff6ff;strokeColor=#2563eb;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="办公区" vertex="1">
<mxGeometry height="178" width="200" x="140" y="482" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-3" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff7ed;strokeColor=#ea580c;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="公网区域" vertex="1">
<mxGeometry height="424" width="200" x="140" y="670" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-4" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.people.standing_man_2;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="移动端用户" vertex="1">
<mxGeometry height="70" width="40" x="230" y="699" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-5" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="第三方系统" vertex="1">
<mxGeometry height="80" width="80" x="210" y="826" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-6" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" target="cTkcAQCcS-jnWanXpuNl-31">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-7" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.buildings.government_building;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="银行系统&#xa;公网/专线" vertex="1">
<mxGeometry height="80" width="80" x="210" y="956" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-8" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fffbeb;strokeColor=#d97706;strokeWidth=2;fontStyle=1;align=left;verticalAlign=top;spacingLeft=10;spacingTop=8;" value="互联网区/DMZ" vertex="1">
<mxGeometry height="608" width="520" x="370" y="482" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-9" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="SFTP/FTP&#xa;文件交换服务器" vertex="1">
<mxGeometry height="88" width="70" x="703.75" y="948" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-10" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.computers_and_peripherals.pc;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PC 端用户&#xa;办公网段" vertex="1">
<mxGeometry height="92" width="95" x="190" y="530" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-11" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="综合节点&lt;br&gt;&lt;br&gt;&lt;div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;/div&gt;" vertex="1">
<mxGeometry height="73.38" width="80" x="970" y="641.81" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-12" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 主库" vertex="1">
<mxGeometry height="84" width="118" x="1164" y="730" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-13" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.storage.relational_database;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="PostgreSQL 热备" vertex="1">
<mxGeometry height="84" width="118" x="1164" y="547" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-14" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.fileserver;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="备份归档存储" vertex="1">
<mxGeometry height="84" width="60" x="1294" y="940" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-15" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-4" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-31" value="HTTPS 443">
<mxGeometry relative="1" as="geometry">
<mxPoint x="380" y="695" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-16" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-5" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.12;entryY=0.9;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-31" value="HTTPS/API">
<mxGeometry relative="1" as="geometry">
<mxPoint x="415" y="721.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-17" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-9" value="公网隧道&lt;span style=&quot;background-color: light-dark(#ffffff, var(--ge-dark-color, #121212)); color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));&quot;&gt;/专线&lt;/span&gt;21/22 银行文件交换">
<mxGeometry relative="1" x="0.1096" y="-3" as="geometry">
<mxPoint as="offset" />
<mxPoint x="380" y="957" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-18" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-10" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-33" value="办公网访问,内网 IP">
<mxGeometry relative="1" as="geometry">
<mxPoint x="510" y="654" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-19" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-33" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;entryX=0.038;entryY=0.681;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-29" value="API转发">
<mxGeometry relative="1" as="geometry">
<mxPoint x="694" y="656" as="sourcePoint" />
<mxPoint x="763" y="676.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-20" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-29" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-11" value="业务访问">
<mxGeometry relative="1" as="geometry">
<mxPoint x="750.75" y="903" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-21" edge="1" parent="1" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-9" value="21/22 文件交换">
<mxGeometry relative="1" as="geometry">
<mxPoint as="offset" />
<Array as="points" />
<mxPoint x="740" y="806" as="sourcePoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-22" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-39" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=1;exitY=0.75;exitDx=0;exitDy=0;" target="cTkcAQCcS-jnWanXpuNl-12" value="5432 数据库访问">
<mxGeometry relative="1" x="-0.0013" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-23" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-39" style="html=1;endArrow=block;dashed=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0;exitDx=0;exitDy=0;edgeStyle=orthogonalEdgeStyle;" target="cTkcAQCcS-jnWanXpuNl-13" value="5432 状态探测">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1110" y="730" />
<mxPoint x="1110" y="589" />
</Array>
<mxPoint x="1011" y="863" as="sourcePoint" />
<mxPoint x="1150" y="755" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-24" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-12" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-13" value="主备同步">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-25" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-12" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-14" value="备份/WAL">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-26" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-13" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;" target="cTkcAQCcS-jnWanXpuNl-14" value="备份副本">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-27" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-38" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;exitX=0.37;exitY=0.994;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-14" value="文件归档">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1130" y="1018.0476190476193" as="sourcePoint" />
<mxPoint x="1487.0476190476188" y="921.9999999999995" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-28" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" target="cTkcAQCcS-jnWanXpuNl-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-29" parent="1" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;sketch=0;pointerEvents=1;strokeColor=#99CCFF;strokeWidth=2;align=center;verticalAlign=top;fontFamily=Helvetica;fontSize=12;fontColor=default;fillColor=none;perimeterSpacing=6;" value="" vertex="1">
<mxGeometry height="305" width="171.5" x="640" y="526" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-30" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 2&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="72" width="90" x="675.75" y="553" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-31" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.routers.router;" value="边界路由 NAT 转发" vertex="1">
<mxGeometry height="53" width="78" x="400" y="709" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-32" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.13;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-33">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-33" parent="1" style="strokeColor=#ffffff;sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.servers.www_server;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="Nginx 入口" vertex="1">
<mxGeometry height="82" width="85" x="500" y="693" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-34" parent="1" style="sketch=0;html=1;pointerEvents=1;dashed=0;fillColor=#036897;strokeColor=#ffffff;strokeWidth=2;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.cisco.misc.hp_mini;fontFamily=Helvetica;fontSize=12;fontColor=default;" value="业务应用节点 1&#xa;Spring Boot Gateway&#xa;业务服务" vertex="1">
<mxGeometry height="73" width="90" x="680.75" y="698.5" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-35" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-36" connectable="0" parent="1" style="group" value="" vertex="1">
<mxGeometry height="90" width="84" x="966" y="750" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-37" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="中间件" vertex="1">
<mxGeometry height="30" width="84" y="30" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-38" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="对象存储" vertex="1">
<mxGeometry height="30" width="84" y="60" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-39" parent="cTkcAQCcS-jnWanXpuNl-36" style="text;html=1;whiteSpace=wrap;strokeColor=default;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;数据库管理控件&lt;/span&gt;" vertex="1">
<mxGeometry height="30" width="84" as="geometry" />
</mxCell>
<mxCell id="cTkcAQCcS-jnWanXpuNl-40" edge="1" parent="1" source="cTkcAQCcS-jnWanXpuNl-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" target="cTkcAQCcS-jnWanXpuNl-31" value="HTTPS/API">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="960" />
<mxPoint x="439" y="960" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -0,0 +1,74 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PlanB Network Security Zones</title>
<style>
body { margin: 0; padding: 24px; background: #f5f7fb; color: #0f172a; font: 14px/1.5 "PingFang SC","Microsoft YaHei",sans-serif; }
h1 { margin: 0 0 8px; font-size: 22px; }
p { margin: 0 0 16px; color: #475569; }
.card { background: #fff; border: 1px solid #dbe3ef; border-radius: 16px; padding: 16px; box-shadow: 0 10px 24px rgba(15,23,42,.06); }
canvas { width: 100%; height: auto; display: block; background: #fff; border-radius: 12px; }
</style>
</head>
<body>
<h1>福建水务营收系统网络安全域图</h1>
<p>按“互联网区DMZ/ 非互联网区”两大安全域重绘,仅保留应用系统相关对象。防火墙与路由器仅做抽象表达。</p>
<div class="card">
<canvas id="c" width="1800" height="1100"></canvas>
</div>
<script>
const C = {
ink:"#0f172a", line:"#5b9bd5", zone:"#f8fafc", zoneBorder:"#94a3b8",
dmz:"#fff7ed", dmzBorder:"#ea580c", inner:"#eff6ff", innerBorder:"#2563eb",
svc:"#f5f3ff", svcBorder:"#7c3aed", data:"#ecfdf5", dataBorder:"#059669",
bank:"#fef2f2", bankBorder:"#dc2626", backup:"#fffbeb", backupBorder:"#d97706"
};
function rr(ctx,x,y,w,h,r,fill,stroke,lw=2){ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();ctx.fillStyle=fill;ctx.fill();ctx.lineWidth=lw;ctx.strokeStyle=stroke;ctx.stroke();}
function txt(ctx,lines,x,y,opt={}){const a=Array.isArray(lines)?lines:[lines],s=opt.size||16,w=opt.weight||"600";ctx.fillStyle=opt.color||C.ink;ctx.font=`${w} ${s}px "PingFang SC","Microsoft YaHei",sans-serif`;ctx.textAlign=opt.align||"center";ctx.textBaseline="middle";a.forEach((l,i)=>ctx.fillText(l,x,y+i*(s+5)));}
function box(ctx,x,y,w,h,lines,fill,stroke,size=15){rr(ctx,x,y,w,h,10,fill||"#fff",stroke||C.line,2);txt(ctx,lines,x+w/2,y+h/2-(Array.isArray(lines)?8:0),{size});}
function arrow(ctx,x1,y1,x2,y2,label=""){ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.lineWidth=2;ctx.strokeStyle=C.line;ctx.stroke();const a=Math.atan2(y2-y1,x2-x1),l=10;ctx.beginPath();ctx.moveTo(x2,y2);ctx.lineTo(x2-l*Math.cos(a-Math.PI/6),y2-l*Math.sin(a-Math.PI/6));ctx.lineTo(x2-l*Math.cos(a+Math.PI/6),y2-l*Math.sin(a+Math.PI/6));ctx.closePath();ctx.fillStyle=C.line;ctx.fill();if(label){rr(ctx,(x1+x2)/2-90,(y1+y2)/2-28,180,28,8,"#fff","#cbd5e1",1);txt(ctx,label,(x1+x2)/2,(y1+y2)/2-14,{size:12,weight:"500",color:"#475569"});}}
const c=document.getElementById("c"),ctx=c.getContext("2d");
// DMZ
rr(ctx,40,40,520,980,28,C.dmz,C.dmzBorder);
txt(ctx,"互联网区DMZ",300,72,{size:24});
box(ctx,90,120,140,56,"移动端用户","#fff",C.dmzBorder,15);
box(ctx,260,120,140,56,"第三方系统","#fff",C.dmzBorder,15);
box(ctx,155,220,180,64,["公网接入代理","外部设施"],"#fff",C.dmzBorder,15);
box(ctx,155,350,180,64,["SFTP / FTP","文件交换服务器"],"#fff",C.bankBorder,15);
box(ctx,155,500,180,64,["边界防火墙","/ 路由设备"],"#fff",C.dmzBorder,15);
txt(ctx,["说明DMZ 中仅保留", "公网接入代理与银行文件交换服务"],300,650,{size:15,weight:"500",color:"#475569"});
// non-internet
rr(ctx,620,40,1140,980,28,C.inner,C.innerBorder);
txt(ctx,"非互联网区",1190,72,{size:24});
box(ctx,690,120,200,64,["办公网段", "PC 端用户"],"#fff",C.innerBorder,15);
box(ctx,1010,120,220,64,["内网 Nginx", "机房接入入口"],"#fff",C.innerBorder,15);
box(ctx,1360,120,220,64,["业务应用集群", "Gateway + 业务服务"],"#fff",C.innerBorder,15);
box(ctx,980,320,280,74,["综合节点", "Redis / Nacos / MinIO", "HAProxy / PgBouncer / Patroni"],"#fff",C.svcBorder,14);
box(ctx,1360,330,180,64,["PostgreSQL", "主库"],"#fff",C.dataBorder,15);
box(ctx,1360,430,180,64,["PostgreSQL", "热备"],"#fff",C.dataBorder,15);
box(ctx,1360,560,220,74,["备份归档存储"],"#fff",C.backupBorder,16);
txt(ctx,["说明:非互联网区内承载", "内网接入、应用、综合服务、数据库与备份"],1170,760,{size:15,weight:"500",color:"#475569"});
// links
arrow(ctx,230,148,245,220,"HTTPS");
arrow(ctx,400,148,245,220,"接口");
arrow(ctx,245,284,245,350,"文件交换");
arrow(ctx,335,252,1120,152,"跨边界转发");
arrow(ctx,335,532,1120,152,"受控访问");
arrow(ctx,790,152,1010,152,"办公网访问");
arrow(ctx,1230,152,1360,152,"网关/API");
arrow(ctx,1450,184,1120,320,"业务访问");
arrow(ctx,1450,184,245,350,"21/22");
arrow(ctx,1260,357,1360,362,"5432");
arrow(ctx,1260,357,1360,462,"状态探测");
arrow(ctx,1450,394,1450,430,"主备同步");
arrow(ctx,1540,394,1540,560,"备份/WAL");
arrow(ctx,1540,494,1540,560,"备份副本");
arrow(ctx,1260,394,1360,597,"对象/文件归档");
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@ -0,0 +1,285 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const YAML = require('js-yaml');
const { glob } = require('glob');
class OpenAPIValidator {
constructor() {
this.errors = [];
this.warnings = [];
this.stats = {
totalFiles: 0,
validFiles: 0,
invalidFiles: 0
};
}
// 验证YAML文件格式
validateYAMLSyntax(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
YAML.load(content);
return { valid: true };
} catch (error) {
return {
valid: false,
error: `YAML语法错误: ${error.message}`
};
}
}
// 验证OpenAPI规范
validateOpenAPISpec(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8');
const doc = YAML.load(content);
// 基本结构检查
if (filePath.endsWith('openapi.yaml')) {
return this.validateMainSpec(doc);
} else if (filePath.includes('/schemas/')) {
return this.validateSchemas(doc);
} else if (filePath.includes('/paths/')) {
return this.validatePaths(doc);
} else if (filePath.includes('/components/')) {
return this.validateComponents(doc);
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: `规范验证错误: ${error.message}`
};
}
}
// 验证主文档
validateMainSpec(doc) {
const errors = [];
if (!doc.openapi) {
errors.push('缺少 openapi 版本号');
} else if (!doc.openapi.startsWith('3.0')) {
errors.push('openapi 版本应为 3.0.x');
}
if (!doc.info) {
errors.push('缺少 info 部分');
} else {
if (!doc.info.title) errors.push('缺少 info.title');
if (!doc.info.version) errors.push('缺少 info.version');
}
if (!doc.paths) {
errors.push('缺少 paths 部分');
}
return {
valid: errors.length === 0,
errors: errors
};
}
// 验证数据模型
validateSchemas(doc) {
const errors = [];
for (const [name, schema] of Object.entries(doc)) {
if (typeof schema !== 'object') continue;
// 检查必要字段
if (!schema.type && !schema.$ref && !schema.allOf && !schema.oneOf && !schema.anyOf) {
errors.push(`模型 ${name} 缺少 type 或引用定义`);
}
// 检查描述
if (schema.type === 'object' && schema.properties) {
for (const [propName, prop] of Object.entries(schema.properties)) {
if (!prop.description) {
errors.push(`模型 ${name}.${propName} 缺少描述`);
}
}
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
// 验证路径定义
validatePaths(doc) {
const errors = [];
for (const [pathName, pathItem] of Object.entries(doc)) {
if (typeof pathItem !== 'object') continue;
for (const [method, operation] of Object.entries(pathItem)) {
if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method)) {
continue;
}
// 检查必要字段
if (!operation.tags) {
errors.push(`路径 ${pathName}.${method} 缺少 tags`);
}
if (!operation.summary) {
errors.push(`路径 ${pathName}.${method} 缺少 summary`);
}
if (!operation.description) {
errors.push(`路径 ${pathName}.${method} 缺少 description`);
}
if (!operation.operationId) {
errors.push(`路径 ${pathName}.${method} 缺少 operationId`);
}
if (!operation.responses) {
errors.push(`路径 ${pathName}.${method} 缺少 responses`);
}
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
// 验证组件定义
validateComponents(doc) {
const errors = [];
// 基本组件结构检查
for (const [name, component] of Object.entries(doc)) {
if (typeof component !== 'object') continue;
// 检查是否有必要的属性
if (!component.description && !component.$ref) {
errors.push(`组件 ${name} 建议添加描述`);
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
// 验证单个文件
async validateFile(filePath) {
console.log(`验证文件: ${filePath}`);
this.stats.totalFiles++;
// YAML语法验证
const syntaxResult = this.validateYAMLSyntax(filePath);
if (!syntaxResult.valid) {
this.errors.push(`${filePath}: ${syntaxResult.error}`);
this.stats.invalidFiles++;
return false;
}
// OpenAPI规范验证
const specResult = this.validateOpenAPISpec(filePath);
if (!specResult.valid) {
this.stats.invalidFiles++;
if (specResult.errors) {
specResult.errors.forEach(error => {
this.errors.push(`${filePath}: ${error}`);
});
} else {
this.errors.push(`${filePath}: ${specResult.error}`);
}
return false;
}
this.stats.validFiles++;
return true;
}
// 验证所有文档文件
async validateAll() {
console.log('🔍 开始验证 OpenAPI 文档...\n');
try {
// 查找所有YAML文件
const yamlFiles = await glob('docs/**/*.yaml', {
cwd: process.cwd(),
absolute: true
});
if (yamlFiles.length === 0) {
console.log('⚠️ 未找到任何 YAML 文件');
return false;
}
console.log(`找到 ${yamlFiles.length} 个 YAML 文件\n`);
// 验证每个文件
for (const file of yamlFiles) {
await this.validateFile(file);
}
return this.generateReport();
} catch (error) {
console.error('验证过程中出现错误:', error);
return false;
}
}
// 生成验证报告
generateReport() {
console.log('\n📊 验证报告');
console.log('='.repeat(50));
console.log(`总文件数: ${this.stats.totalFiles}`);
console.log(`有效文件: ${this.stats.validFiles}`);
console.log(`无效文件: ${this.stats.invalidFiles}`);
if (this.errors.length > 0) {
console.log('\n❌ 发现的错误:');
this.errors.forEach((error, index) => {
console.log(`${index + 1}. ${error}`);
});
}
if (this.warnings.length > 0) {
console.log('\n⚠ 警告信息:');
this.warnings.forEach((warning, index) => {
console.log(`${index + 1}. ${warning}`);
});
}
const isValid = this.errors.length === 0;
if (isValid) {
console.log('\n✅ 所有文档验证通过!');
} else {
console.log('\n❌ 文档验证失败,请修复上述错误。');
}
return isValid;
}
}
// 主执行函数
async function main() {
const validator = new OpenAPIValidator();
const isValid = await validator.validateAll();
// 设置退出码
process.exit(isValid ? 0 : 1);
}
// 如果直接运行此脚本
if (require.main === module) {
main().catch(error => {
console.error('验证脚本执行失败:', error);
process.exit(1);
});
}
module.exports = OpenAPIValidator;

View File

@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""
正式网络图生成脚本
用途
- 生成 PlanB 当前正式口径下的网络分区图与网络连接图
- 作为 output/diagrams/ 下正式产物的唯一来源
说明
- 如需试验不同布局请使用 scripts/generate_planb_diagrams_variants.py
"""
from pathlib import Path
from diagrams import Cluster, Diagram, Edge
from diagrams.generic.network import Router, Switch
from diagrams.onprem.compute import Server
from diagrams.onprem.network import HAProxy, Internet, Nginx
from diagrams.onprem.storage import Ceph
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "output" / "diagrams"
OUT.mkdir(parents=True, exist_ok=True)
GRAPH_ATTR = {
"fontsize": "20",
"bgcolor": "white",
"pad": "0.2",
"ranksep": "0.55",
"nodesep": "0.35",
"splines": "ortho",
"labelloc": "t",
"fontname": "PingFang SC",
"margin": "0.15",
"rankdir": "LR",
"newrank": "true",
}
NODE_ATTR = {
"fontname": "PingFang SC",
"fontsize": "13",
}
EDGE_ATTR = {
"fontname": "PingFang SC",
"fontsize": "11",
}
def zone_diagram():
with Diagram(
"PlanB Network Zones",
filename=str(OUT / "planb_network_zones"),
outformat="png",
show=False,
graph_attr=GRAPH_ATTR,
node_attr=NODE_ATTR,
edge_attr=EDGE_ATTR,
):
with Cluster("Untrust\n外网区域", graph_attr={"style": "rounded,filled", "color": "#ea580c", "fillcolor": "#fff7ed"}):
mobile = Internet("移动端用户")
third = Internet("第三方系统")
with Cluster("DMZ\n对外服务区", graph_attr={"style": "rounded,filled", "color": "#d97706", "fillcolor": "#fffbeb"}):
nginx_entry = Nginx("Nginx 入口")
ftp_host = Server("SFTP/FTP 文件交换服务器")
with Cluster("LAN\n办公与应用区", graph_attr={"style": "rounded,filled", "color": "#2563eb", "fillcolor": "#eff6ff"}):
office_pc = Server("PC 端用户\n办公网段")
app_sw = Switch("应用区交换")
app_cluster = Server("业务应用集群\n2 节点")
svc_host = Server("综合节点")
with Cluster("Core\n核心数据区", graph_attr={"style": "rounded,filled", "color": "#059669", "fillcolor": "#ecfdf5"}):
db_primary = Server("PostgreSQL 主库")
db_standby = Server("PostgreSQL 热备")
backup = Ceph("备份归档存储")
# enforce left-to-right zone order and keep the zone centers aligned
mobile - Edge(style="invis", weight="10") - nginx_entry
nginx_entry - Edge(style="invis", weight="10") - office_pc
office_pc - Edge(style="invis", weight="10") - db_primary
mobile >> Edge(label="HTTPS 443") >> nginx_entry
third >> Edge(label="HTTPS / API") >> nginx_entry
office_pc >> Edge(label="办公网访问") >> nginx_entry
nginx_entry >> app_sw >> app_cluster
app_cluster >> svc_host
app_cluster >> Edge(label="文件交换 21/22") >> ftp_host
svc_host >> Edge(label="数据库访问") >> db_primary
svc_host >> Edge(label="探测/控制") >> db_standby
db_primary >> Edge(label="主备同步") >> db_standby
db_primary >> Edge(label="备份/WAL") >> backup
db_standby >> Edge(label="备份副本") >> backup
svc_host >> Edge(label="文件归档") >> backup
def link_diagram():
graph_attr = dict(GRAPH_ATTR)
graph_attr["ranksep"] = "0.75"
graph_attr["nodesep"] = "0.3"
with Diagram(
"PlanB Network Links",
filename=str(OUT / "planb_network_links"),
outformat="png",
show=False,
graph_attr=graph_attr,
node_attr=NODE_ATTR,
edge_attr=EDGE_ATTR,
):
with Cluster("Untrust\n外网区域", graph_attr={"style": "rounded,filled", "color": "#ea580c", "fillcolor": "#fff7ed"}):
mobile = Internet("移动端用户")
third = Internet("第三方系统")
bank_net = Internet("银行系统\n公网/专线")
with Cluster("DMZ\n对外服务区", graph_attr={"style": "rounded,filled", "color": "#d97706", "fillcolor": "#fffbeb"}):
nginx_entry = Nginx("Nginx 入口")
bank = Server("SFTP/FTP 文件交换服务器")
with Cluster("LAN\n办公与应用区", graph_attr={"style": "rounded,filled", "color": "#2563eb", "fillcolor": "#eff6ff"}):
pc = Server("PC 端用户\n办公网段")
app_switch = Switch("应用区交换")
app1 = Server("业务应用节点 1\nGateway + Biz")
app2 = Server("业务应用节点 2\nGateway + Biz")
svc = Server("综合节点")
haproxy = HAProxy("HAProxy / PgBouncer / Patroni")
with Cluster("Core\n核心数据区", graph_attr={"style": "rounded,filled", "color": "#059669", "fillcolor": "#ecfdf5"}):
dbm = Server("PostgreSQL 主库")
dbs = Server("PostgreSQL 热备")
backup = Ceph("备份归档存储")
# enforce left-to-right zone order: Untrust -> DMZ -> LAN -> Core
mobile - Edge(style="invis", weight="10") - nginx_entry
nginx_entry - Edge(style="invis", weight="10") - pc
pc - Edge(style="invis", weight="10") - dbm
mobile >> Edge(label="HTTPS 443") >> nginx_entry
third >> Edge(label="HTTPS / 接口") >> nginx_entry
bank_net >> Edge(label="21/22") >> bank
pc >> Edge(label="办公网访问") >> nginx_entry
nginx_entry >> app_switch
app_switch >> Edge(label="Gateway / API") >> app1
app_switch >> Edge(label="Gateway / API") >> app2
app_switch >> Edge(label="业务访问") >> svc
app_switch >> Edge(label="文件交换 21/22") >> bank
svc >> Edge(label="缓存 / 配置 / 文件") >> haproxy
haproxy >> Edge(label="5432 写流量") >> dbm
haproxy >> Edge(label="5432 状态探测", style="dashed") >> dbs
dbm >> Edge(label="主备同步") >> dbs
svc >> Edge(label="文件归档") >> backup
dbm >> Edge(label="WAL / 备份") >> backup
dbs >> Edge(label="备份副本") >> backup
if __name__ == "__main__":
zone_diagram()
link_diagram()
print(OUT / "planb_network_zones.png")
print(OUT / "planb_network_links.png")

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""
试验版布局脚本
用途
- 仅用于对比不同 diagrams / graphviz 布局参数的效果
- 不作为正式交付图的唯一来源
正式图来源
- scripts/generate_planb_diagrams.py
"""
from pathlib import Path
from diagrams import Cluster, Diagram, Edge
from diagrams.generic.network import Switch
from diagrams.onprem.compute import Server
from diagrams.onprem.network import HAProxy, Internet, Nginx
from diagrams.onprem.storage import Ceph
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "output" / "diagrams_variants"
OUT.mkdir(parents=True, exist_ok=True)
BASE_GRAPH_ATTR = {
"fontsize": "20",
"bgcolor": "white",
"pad": "0.18",
"ranksep": "0.65",
"nodesep": "0.35",
"splines": "ortho",
"labelloc": "t",
"fontname": "PingFang SC",
"margin": "0.12",
"newrank": "true",
}
NODE_ATTR = {
"fontname": "PingFang SC",
"fontsize": "13",
}
EDGE_ATTR = {
"fontname": "PingFang SC",
"fontsize": "11",
}
ZONE_STYLE = {
"Untrust": {"style": "rounded,filled", "color": "#ea580c", "fillcolor": "#fff7ed"},
"DMZ": {"style": "rounded,filled", "color": "#d97706", "fillcolor": "#fffbeb"},
"LAN": {"style": "rounded,filled", "color": "#2563eb", "fillcolor": "#eff6ff"},
"Core": {"style": "rounded,filled", "color": "#059669", "fillcolor": "#ecfdf5"},
}
def build_nodes():
with Cluster("Untrust\n外网区域", graph_attr=ZONE_STYLE["Untrust"]):
mobile = Internet("移动端用户")
third = Internet("第三方系统")
bank_net = Internet("银行系统\n公网/专线")
with Cluster("DMZ\n对外服务区", graph_attr=ZONE_STYLE["DMZ"]):
nginx_entry = Nginx("Nginx 入口")
bank = Server("SFTP/FTP 文件交换服务器")
with Cluster("LAN\n办公与应用区", graph_attr=ZONE_STYLE["LAN"]):
pc = Server("PC 端用户\n办公网段")
app_switch = Switch("应用区交换")
app1 = Server("业务应用节点 1\nGateway + Biz")
app2 = Server("业务应用节点 2\nGateway + Biz")
svc = Server("综合节点")
ctrl = HAProxy("HAProxy / PgBouncer / Patroni")
with Cluster("Core\n核心数据区", graph_attr=ZONE_STYLE["Core"]):
dbm = Server("PostgreSQL 主库")
dbs = Server("PostgreSQL 热备")
backup = Ceph("备份归档存储")
return mobile, third, bank_net, nginx_entry, bank, pc, app_switch, app1, app2, svc, ctrl, dbm, dbs, backup
def wire(nodes):
mobile, third, bank_net, nginx_entry, bank, pc, app_switch, app1, app2, svc, ctrl, dbm, dbs, backup = nodes
mobile >> Edge(label="HTTPS 443") >> nginx_entry
third >> Edge(label="HTTPS / 接口") >> nginx_entry
bank_net >> Edge(label="21/22") >> bank
pc >> Edge(label="办公网访问") >> nginx_entry
nginx_entry >> app_switch
app_switch >> Edge(label="Gateway / API") >> app1
app_switch >> Edge(label="Gateway / API") >> app2
app_switch >> Edge(label="文件交换 21/22") >> bank
app_switch >> Edge(label="业务访问") >> svc
svc >> Edge(label="缓存 / 配置 / 文件") >> ctrl
ctrl >> Edge(label="5432 写流量") >> dbm
ctrl >> Edge(label="5432 状态探测", style="dashed") >> dbs
dbm >> Edge(label="主备同步") >> dbs
svc >> Edge(label="文件归档") >> backup
dbm >> Edge(label="WAL / 备份") >> backup
dbs >> Edge(label="备份副本") >> backup
def make_variant(name: str, rankdir: str, ranksep: str, nodesep: str):
graph_attr = dict(BASE_GRAPH_ATTR)
graph_attr["rankdir"] = rankdir
graph_attr["ranksep"] = ranksep
graph_attr["nodesep"] = nodesep
with Diagram(
f"PlanB {name}",
filename=str(OUT / name),
outformat="png",
show=False,
graph_attr=graph_attr,
node_attr=NODE_ATTR,
edge_attr=EDGE_ATTR,
):
nodes = build_nodes()
wire(nodes)
if __name__ == "__main__":
variants = [
("planb_links_lr_compact", "LR", "0.65", "0.30"),
("planb_links_lr_balanced", "LR", "0.9", "0.45"),
("planb_links_tb_compact", "TB", "0.75", "0.35"),
("planb_links_tb_balanced", "TB", "1.0", "0.45"),
]
for name, rankdir, ranksep, nodesep in variants:
make_variant(name, rankdir, ranksep, nodesep)
print(OUT / f"{name}.png")

View File

@ -0,0 +1,31 @@
# Baseline: SYS-009设计整合与实现对齐
## Repository Baselines
| Repo | Branch | Commit | Role in This Feature |
|------|--------|--------|----------------------|
| `water-docs` | `007-sys009-design-align` | working tree | 正式规格、计划与后续文档修订入口 |
| `water-backend` | `develop` | `68ab72ae6330e33afa0fec3817c0605829973ec2` | `SYS-009` 当前实现证据基线 |
| `water-frontend` | `develop` | `ae65939045449894c0fccab53fee08521e538ddd` | 范围排除依据,仅说明本轮不承接银行运营界面 |
## Baseline Rules
1. 所有实现成熟度判断均以 `water-backend` 当前 commit 为准。
2. 若后续代码基线变化,必须同步更新本文件和 `final-verdict.md`
3. frontend 不作为本轮正式承接范围,只保留“无完整银行批次/回盘/对账运营界面”的边界说明。
## Key Evidence Anchors
- 实时收费:
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/payceb/PayCebController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/payceb/PayCebServiceImpl.java`
- 代扣:
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/bankwithholding/BankWithholdingController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/bankwithholding/BankWithholdingServiceImpl.java`
- 托收:
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/bankcollection/BankCollectionController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/bankcollection/BankCollectionServiceImpl.java`
- 表结构与后台运营:
- `../water-backend/sw-business-bank/docs/建表sql.sql`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/admin/`

View File

@ -0,0 +1,36 @@
# Specification Quality Checklist: SYS-009设计整合与实现对齐
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-20
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Validation iteration 1 passed.
- Mandatory sections are complete, no clarification markers remain, and the scope is bounded to formal document integration plus backend evidence alignment.
- Implementation file paths are used only as brownfield evidence anchors, not as prescribed implementation work for this round.

View File

@ -0,0 +1,30 @@
# Contract: SYS-009 Capability Alignment
## Purpose
定义外部 `SYS-009` 能力簇与本仓库正式文档落点之间的最小对齐契约,供后续文档修订、任务拆解和验收复用。
## Alignment Matrix
| Capability | Formal Domain | Primary Formal Target | Secondary Target | Expected Verdict Types |
|-----------|---------------|-----------------------|------------------|------------------------|
| 实时收费查询/缴费 | `REV-003` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | `docs/design/03_Technical_Design/03_Interface_Design.md` `IF-EXT-003` | 已实现 / 部分实现 |
| 当日未对账红冲 | `REV-003` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 / 文档先行 |
| 代扣签约 | `REV-008` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | `docs/design/03_Technical_Design/03_Interface_Design.md` | 已实现 / 部分实现 |
| 代扣解约 | `REV-008` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | `docs/design/03_Technical_Design/03_Interface_Design.md` | 已实现 / 部分实现 |
| 代扣客户状态查询 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 / 文档先行 |
| 送盘 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` `IF-EXT-001` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 |
| 送盘状态查询 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 / 文档先行 |
| 取消送盘 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 / 文档先行 |
| 回盘 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` `IF-EXT-002` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 |
| 回盘状态查询 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | 部分实现 / 文档先行 |
| 对账文件与对账请求 | `REV-008` | `docs/design/03_Technical_Design/03_Interface_Design.md` | `docs/design/03_Technical_Design/01_Database_Design.md` | 部分实现 |
| 结算状态协同 | `REV-008` | `docs/design/02_Detailed_Design/12_REV_Detailed.md` | `docs/design/03_Technical_Design/01_Database_Design.md` | 文档先行 / 部分实现 |
## Contract Rules
1. 任何能力簇都必须先映射到既有正式主文档,再允许写入计划、任务和验收结论。
2. 若外部设计存在而 backend 缺少闭环实现,正式文档必须保留目标设计边界,同时显式标注当前成熟度。
3. 不允许为单个能力簇创建新的平行正式文档。
4. 若能力簇涉及 `bk_*` 表族,数据库设计只补承接口径,不下沉到实现脚本级细节。

View File

@ -0,0 +1,37 @@
# Contract: SYS-009 Status Verdicts
## Purpose
定义能力簇状态判定的统一准则,避免在主文档、验证工件和后续任务中出现不同口径。
## Verdict Definitions
| Verdict | Required Evidence | Disallowed Shortcut |
|--------|-------------------|---------------------|
| 已实现 | 具备明确路由/入口、核心业务校验、数据写入或状态更新、结果回写或留痕 | 仅有表结构、仅有后台管理页面、仅有 DTO |
| 部分实现 | 具备接口骨架、DTO、服务接口、部分业务逻辑或局部闭环但存在 TODO、空实现、未打通的外部文件/状态处理 | 仅凭“将来准备实现”的设计说明 |
| 文档先行 | 有正式设计目标和对象口径,但当前未定位到足够代码证据支撑 | 以外部参考文档直接推断已实现 |
## Capability Verdicts For This Feature
| Capability | Initial Verdict | Evidence Anchor |
|-----------|-----------------|-----------------|
| 实时收费查询 | 已实现 | `PayCebController#getChargeSearch` / `PayCebServiceImpl#getChargeSearch` |
| 实时收费缴费 | 已实现 | `PayCebController#getChargeOffs` / `PayCebServiceImpl#getChargeOffs` |
| 代理收费对账 | 部分实现 | `PayCebController#paymentCheck` TODO占位未闭环 |
| 代扣签约 | 已实现 | `BankWithholdingController#signing` / `BankWithholdingServiceImpl#signing` |
| 代扣解约 | 已实现 | `BankWithholdingController#termination` / `BankWithholdingServiceImpl#termination` |
| 代扣客户状态查询 | 已实现 | `BankWithholdingController#customerCheck` / `BankWithholdingServiceImpl#customerCheck` |
| 送盘 | 已实现 | `BankWithholdingController#sendDisc` / `BankWithholdingServiceImpl#sendDisc` |
| 送盘状态查询 | 已实现 | `BankWithholdingController#sendDiscCheck` / `BankWithholdingServiceImpl#sendDiscCheck` |
| 取消送盘 | 已实现 | `BankWithholdingController#cancelDisc` / `BankWithholdingServiceImpl#cancelDisc` |
| 回盘 | 已实现 | `BankWithholdingController#backDisc` / `BankWithholdingServiceImpl#backDisc` |
| 回盘状态查询 | 已实现 | `BankWithholdingController#backDiscCheck` / `BankWithholdingServiceImpl#backDiscCheck` |
| 托收平行链路 | 部分实现 | `BankCollection*` 路径已存在,成熟度未全闭环 |
| 运营侧批次/对账/结算管理 | 已具备入口 | `WithholdingBatchController` / `ReconcileBatchController` / `SettlementBatchController` |
## Contract Rules
1. 主文档中同一能力簇的 verdict 必须与 `final-verdict.md` 保持一致。
2. 若后续代码变化导致 verdict 升降级,必须同时更新基线和依据。
3. `已具备入口` 不是独立 verdict只能作为 supporting note最终仍需归入三类正式 verdict 之一。

View File

@ -0,0 +1,113 @@
# Data Model: SYS-009设计整合与实现对齐
## 1. Source Capability
### Purpose
表示外部 `water-bank-api-doc` 中一个可被正式文档吸收的银行协同能力簇。
### Fields
| Field | Description | Validation |
|------|-------------|------------|
| `capability_id` | 能力标识,如 `REALTIME_PAY``WITHHOLD_SIGNING` | 必填,唯一 |
| `capability_name` | 能力名称 | 必填 |
| `business_domain` | 业务归属,如 `REV-003``REV-008` | 必填,必须落在当前正式模块范围内 |
| `source_docs` | 来源文档列表 | 至少 1 个 |
| `formal_targets` | 正式承接文档与章节 | 至少 1 个 |
| `current_status` | 当前实现状态 | 必填,取值仅限 `已实现` / `部分实现` / `文档先行` |
| `notes` | 边界说明 | 可选 |
### Relationships
- 一个 `Source Capability` 必须映射到至少一个 `Formal Document Section`
- 一个 `Source Capability` 可以关联多个 `Backend Evidence Item`
## 2. Formal Document Section
### Purpose
表示本仓库中承接 `SYS-009` 设计整合结论的正式章节或表格位置。
### Fields
| Field | Description | Validation |
|------|-------------|------------|
| `document_path` | 正式文档路径 | 必填,必须是既有主文档或治理文档 |
| `section_anchor` | 章节锚点或节标题 | 必填 |
| `section_type` | 章节类型,如范围摘要、业务流程、接口契约、数据对象 | 必填 |
| `update_intent` | 本轮更新意图 | 必填 |
| `consistency_impacts` | 受影响的一致性项 | 至少 1 项 |
### Relationships
- 一个 `Formal Document Section` 可承接多个 `Source Capability`
- 一个 `Formal Document Section` 可引用多个 `Backend Evidence Item`
## 3. Backend Evidence Item
### Purpose
表示用于判定当前实现状态的代码或表结构证据。
### Fields
| Field | Description | Validation |
|------|-------------|------------|
| `evidence_path` | 证据文件路径 | 必填 |
| `evidence_type` | 类型,如 `controller``service``table_sql``admin_entry` | 必填 |
| `capability_scope` | 覆盖的能力簇 | 必填 |
| `maturity_signal` | 成熟度信号如完整实现、TODO、骨架接口、表结构已具备 | 必填 |
| `baseline_sha` | 对应 backend 基线 | 必填 |
| `remarks` | 备注 | 可选 |
### State Interpretation
| Maturity Signal | Verdict |
|----------------|---------|
| 完整请求处理 + 业务校验 + 数据写入/状态更新 + 日志留痕 | 已实现 |
| 有路由 / DTO / service 接口 / 部分逻辑,但存在 TODO 或空实现 | 部分实现 |
| 只有正式设计目标,没有明确代码证据 | 文档先行 |
## 4. Alignment Verdict
### Purpose
表示对单个能力簇的正式对齐结论。
### Fields
| Field | Description | Validation |
|------|-------------|------------|
| `capability_id` | 对应能力标识 | 必填 |
| `formal_target` | 正式落点 | 必填 |
| `verdict` | 对齐结论 | 必填,取值仅限 `已实现` / `部分实现` / `文档先行` |
| `basis_summary` | 判定依据摘要 | 必填 |
| `follow_up_action` | 后续动作 | 必填 |
### State Transitions
```text
文档先行 -> 部分实现 -> 已实现
```
触发条件:
- `文档先行 -> 部分实现`:出现 controller/service/DTO/表结构等明确工程骨架
- `部分实现 -> 已实现`:业务链路、状态更新和留痕闭环完成,且有稳定代码证据
## 5. Verification Artifact
### Purpose
表示本轮用于追踪计划、研究、对齐和结论的工件。
### Fields
| Field | Description | Validation |
|------|-------------|------------|
| `artifact_name` | 工件名称 | 必填 |
| `artifact_path` | 工件路径 | 必填 |
| `artifact_role` | 作用,如基线、研究、契约、最终结论 | 必填 |
| `depends_on` | 依赖工件 | 可选 |
| `completion_rule` | 何时可判定完成 | 必填 |

View File

@ -0,0 +1,56 @@
# Final Verdict: SYS-009设计整合与实现对齐
## Current Implementation Verdict
本轮已完成正式主文档修订、实现边界回写、`BankWithholding` 六条银行入口补齐及最小编译验证,当前结论如下:
- `PayCeb`:基础实时查询/缴费闭环已实现,对账回传未实现。
- `BankWithholding`:签约、解约、客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询均已具备 controller/service 路由、核心校验、批次/明细状态更新或留痕能力,已形成最小实现态闭环。
- `BankCollection`:与代扣平行,整体为部分实现。
- `bk_*` 表族与后台管理入口:对象层与资源管理层基本齐备,可支撑代扣批处理状态管理,但仍不能替代真实银行文件联调与运行态样本证据。
- 正式文档回写仍采用“目标设计边界 + 当前成熟度注记”的双层表达,并将实现态闭环与运行态风险分层记录。
## Completed Changes
1. 已修订 `docs/design/02_Detailed_Design/12_REV_Detailed.md`,补充 `REV-003``REV-008` 的实现边界与当前成熟度说明。
2. 已修订 `docs/design/03_Technical_Design/03_Interface_Design.md`,补充签约、解约、状态查询、取消送盘、回盘状态和当前状态说明。
3. 已修订 `docs/design/03_Technical_Design/01_Database_Design.md`,明确 `bk_*` 表族对象齐备但业务编排未闭环的边界。
4. 已修订 `docs/design/01_Overview/03_Summary_Design.md`,将 `SYS-009` 从强实现表述收敛为“已落地能力 + 后续完善项”口径。
5. 已修订 `specs/007-sys009-design-align/plan.md``research.md``quickstart.md`,同步范围、工件和执行结果。
6. 已复核 backend `BankWithholdingController``BankWithholdingServiceImpl``TransactionMapper` 与相关 DTO/DO/Mapper实现 `customerCheck``sendDisc``sendDiscCheck``cancelDisc``backDisc``backDiscCheck` 六条银行入口从 TODO/null 到实现态闭环的升级。
## Validation Results
- `make validate-file FILE=docs/design/02_Detailed_Design/12_REV_Detailed.md`通过0 个问题
- `make validate-file FILE=docs/design/03_Technical_Design/03_Interface_Design.md`通过0 个问题
- `make validate-file FILE=docs/design/03_Technical_Design/01_Database_Design.md`通过0 个问题
- `make validate-file FILE=docs/design/01_Overview/03_Summary_Design.md`通过0 个问题
- `make check-links`:通过,仓库级链接检查无问题
- `make validate-mermaid`通过Mermaid 语法验证无问题
- `mvn -f /Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/pom.xml -pl sw-business-bank-server -am -DskipTests compile`通过backend 最小编译验证成功
- `mvn -f /Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/pom.xml -pl sw-business-bank-server -am test`:通过,当前模块无失败测试(本轮输出为 `No tests to run`
## Governance Sync
1. 已回写 `docs/design/00_Management/01_Project_Progress.md`,记录本轮 `SYS-009` 设计整合、银行代扣六条入口实现态闭环与剩余运行态风险。
2. 已回写 `docs/design/00_Management/03_Task_Checklist.md`,登记本轮任务闭环状态、六条银行入口补齐结果与最小验证结论。
3. 已回写 `specs/007-sys009-design-align/contracts/sys009-status-verdicts.md`,将 `BankWithholding` 六条银行入口 verdict 与当前实现态证据同步。
## Remaining Risks
1. `PayCeb` 当前仅可确认欠费查询与缴费处理闭环,对账回传仍未落地。
2. `BankWithholding` 六条银行入口虽已形成最小实现态闭环,但 `backDisc` 仍为最小化结果分发实现尚未接入真实银行回盘文件解析、SFTP/文件通道联调和运行态样本补证。
3. `BankCollection` 仍处于部分实现状态;与 `BankWithholding` 平行的托收闭环尚未同步补齐。
4. `bk_*` 表族和后台资源管理入口已具备对象层基础,但不能替代银行 app 协同闭环的运行态验证证据。
## Feature Artifacts Produced
- `spec.md`
- `plan.md`
- `baseline.md`
- `research.md`
- `data-model.md`
- `quickstart.md`
- `contracts/sys009-capability-alignment.md`
- `contracts/sys009-status-verdicts.md`
- `tasks.md`

View File

@ -0,0 +1,126 @@
# Implementation Plan: [FEATURE]
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. For this project, planning is document-first and multi-repo aware.
## Summary
[Extract from feature spec: primary requirement + target documents + intended update approach]
## Repository Scope
- **Formal workflow home**: `water-docs`
- **Target repos in scope**:
- `water-docs`: [Yes / No]
- `water-backend`: [Yes / No]
- `water-frontend`: [Yes / No]
- **Primary delivery mode**: [Document closure / Code evidence alignment / Backend implementation / Frontend implementation / Mixed]
## Code Baseline
- **Backend baseline**: [commit SHA / branch / N/A]
- **Frontend baseline**: [commit SHA / branch / N/A]
- **Baseline capture plan**: [How code evidence will be tied to a stable commit]
## Technical Context
**Primary Work Product**: [Markdown design docs, management docs, evidence files, backend code, frontend code]
**Source of Truth Documents**: [List the authoritative docs that this change must align with]
**Reference Sources**: [Archive/guides/other references allowed for verification]
**Validation Commands**: [e.g., make validate-file FILE=..., make check-links, make validate-mermaid, compile/build/test commands]
**Target Scope**: [specific chapters, main docs, code modules, or verification slices]
**Project Type**: 文档治理仓库 + 多仓实现协作
**Constraints**: [no new parallel formal docs, no invented business rules, relative links only, branch rules, baseline rules]
**Scale/Scope**: [single document / cross-document / multi-repo / verification-heavy]
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- [ ] **主文档归属已确认**:正式结论优先落在 `water-docs` 既有主文档或治理文档,不新增平行正式稿。
- [ ] **多仓范围已确认**:已明确本轮是否涉及 `water-backend``water-frontend`,以及各自只是取证还是需要改代码。
- [ ] **代码基线已确认**backend/frontend 的 commit 或 branch 基线已记录,避免文档结论失去实现版本锚点。
- [ ] **Archive 使用方式合规**Archive 仅作为来源/核对输入,不直接替代正式口径。
- [ ] **一致性影响已列出**:已识别系统名称、数据库口径、编号、图表、链接、术语、接口契约等受影响项。
- [ ] **校验与台账动作已规划**已明确需要执行的文档校验、代码验证、evidence 更新,以及是否需要更新 `01_Project_Progress.md` / `03_Task_Checklist.md`
## Project Structure
### Feature Artifacts
```text
specs/[###-feature]/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
├── tasks.md
└── verification.md / final-verdict.md
```
### Repository Touchpoints
```text
water-docs/
├── docs/design/
├── specs/
└── .specify/
water-backend/
└── [backend modules in scope]
water-frontend/
└── [frontend modules in scope]
```
**Structure Decision**: [Document the exact files or modules to be updated and the reason each is in scope]
## Phase 0: Research & Alignment
### Research Inputs
- [List unresolved questions about docs, backend, frontend, baseline, or validation]
### Deliverables
- `research.md`
- [Optional evidence note if brownfield code audit is required]
## Phase 1: Design & Contracts
### Planned Artifacts
- `data-model.md`
- `contracts/*`
- `quickstart.md`
- `verification.md` or `final-verdict.md` if the feature is verification-heavy
### Design Decisions
- [List the intended docs-first and multi-repo decisions]
## Validation Plan
- **Document validation**: [make validate-file / check-links / mermaid checks]
- **Backend validation**: [compile / unit test / smoke / N/A]
- **Frontend validation**: [build / lint / typecheck / smoke / N/A]
- **Evidence output**: [baseline.md / docs-validation.md / backend-validation.md / frontend-validation.md / final-verdict.md]
## Ledger Sync Plan
- **Project progress update required**: [Yes / No]
- **Task checklist update required**: [Yes / No]
- **Evidence or verification summary update required**: [Yes / No]
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| [e.g., temporary appendix doc] | [current need] | [why existing main doc could not safely absorb it] |
| [e.g., Archive citation exception] | [specific traceability reason] | [why normal main-doc source was insufficient] |

View File

@ -0,0 +1,83 @@
# Quickstart: SYS-009设计整合与实现对齐
## 1. 准备上下文
1. 在 `water-docs` 根目录确认当前分支为 `007-sys009-design-align`
2. 阅读以下文件:
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/02_Delivery_Standards.md`
- `docs/design/00_Management/03_Task_Checklist.md`
- `docs/design/01_Overview/03_Summary_Design.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
## 2. 固定代码基线
1. 记录 backend 分支和 commit
- `git -C ../water-backend rev-parse --abbrev-ref HEAD`
- `git -C ../water-backend rev-parse HEAD`
2. 如仅做范围说明,可记录 frontend 分支和 commit
- `git -C ../water-frontend rev-parse --abbrev-ref HEAD`
- `git -C ../water-frontend rev-parse HEAD`
3. 将结果写入 `baseline.md``final-verdict.md`
## 3. 收集对齐证据
1. 核对外部设计来源:
- `/Users/tangweijie/github/water-bank-api-doc/营收系统缴费接口.md`
- `/Users/tangweijie/github/water-bank-api-doc/营收系统接口规范设计文档.md`
2. 核对 backend 关键证据:
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/.../controller/app/payceb/PayCebController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/.../service/payceb/PayCebServiceImpl.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/.../controller/app/bankwithholding/BankWithholdingController.java`
- `../water-backend/sw-business-bank/sw-business-bank-server/src/main/java/.../service/bankwithholding/BankWithholdingServiceImpl.java`
- `../water-backend/sw-business-bank/docs/建表sql.sql`
3. 按“已实现 / 部分实现 / 文档先行”归档能力簇结论。
## 4. 更新正式文档
1. 在 `12_REV_Detailed.md``REV-003``REV-008` 的能力边界与成熟度说明。
2. 在 `03_Interface_Design.md` 补签约、解约、状态查询、取消送盘、对账文件规则等接口契约。
3. 在 `01_Database_Design.md``bk_*` 表族与外部能力簇的承接口径。
4. 在 `03_Summary_Design.md` 同步 `SYS-009` 范围摘要和外部接口表。
## 5. 生成与更新证据工件
1. 更新或新增:
- `baseline.md`
- `research.md`
- `data-model.md`
- `contracts/*`
- `final-verdict.md`
2. 若本轮已完成正式文档修订,同步更新:
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/03_Task_Checklist.md`
## 6. 执行校验
1. `make validate-file FILE=docs/design/02_Detailed_Design/12_REV_Detailed.md`
2. `make validate-file FILE=docs/design/03_Technical_Design/03_Interface_Design.md`
3. `make validate-file FILE=docs/design/03_Technical_Design/01_Database_Design.md`
4. `make validate-file FILE=docs/design/01_Overview/03_Summary_Design.md`
5. `make check-links`
6. `make validate-mermaid`
## 7. 形成最终结论
1. 在 `final-verdict.md` 汇总:
- 本轮基线
- 已整合的能力簇
- 每个能力簇的成熟度结论
- 已更新的正式文档
- 已执行的校验
- 剩余未闭环项
## 8. 当前已执行结果
- 已完成 `12_REV_Detailed.md``03_Interface_Design.md``01_Database_Design.md``03_Summary_Design.md` 的 SYS-009 正式口径修订。
- 已完成以下单文件校验并通过:
- `make validate-file FILE=docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `make validate-file FILE=docs/design/03_Technical_Design/03_Interface_Design.md`
- `make validate-file FILE=docs/design/03_Technical_Design/01_Database_Design.md`
- `make validate-file FILE=docs/design/01_Overview/03_Summary_Design.md`

View File

@ -0,0 +1,77 @@
# Phase 0 Research: SYS-009设计整合与实现对齐
## 1. 外部设计归并路径
Decision实时收费能力归并到 `docs/design/02_Detailed_Design/12_REV_Detailed.md``REV-003 营业收费`,并在 `docs/design/03_Technical_Design/03_Interface_Design.md``IF-EXT-003 银行实时收费接口` 承接查询、缴费和当日未对账红冲约束。
Rationale实时收费首先影响账单查询、缴费处理和收费结果回写业务归属在收费主链路外部资料新增的是银行协议细节不需要为其新建独立 `SYS-009` 正式章节。
Alternatives considered全部并入 `REV-008`;被放弃,因为这样会弱化实时收费与营业收费核销主链的关联。
Decision代扣签约/解约归并到 `docs/design/02_Detailed_Design/12_REV_Detailed.md``REV-008 代收与银行业务`,并在 `docs/design/03_Technical_Design/03_Interface_Design.md` 增补签约、解约接口小节。
Rationale`bk_withholding_agreement` 已是正式数据库对象,但当前接口专项缺少签约/解约契约,外部资料正好补足该缺口。
Alternatives considered归入客户资料模块被放弃因为签解约属于银行协同交易动作不是客户主数据维护。
Decision送盘/回盘继续以 `IF-EXT-001``IF-EXT-002` 为主承接,在接口专项中补充批次号、文件名、时间窗口和文件交互规则,在 `REV-008` 中保留业务主流程。
Rationale正式文档已有批次下发和回盘时序外部资料主要补的是操作细节和文件规则。
Alternatives considered在概要设计扩写送盘/回盘细节;被放弃,因为概要设计不适合承载文件交互细节。
Decision送盘状态查询、回盘状态查询、代扣客户状态查询、取消送盘统一归并到 `docs/design/03_Technical_Design/03_Interface_Design.md`,并在 `REV-008` 中仅保留业务边界和状态流转说明。
Rationale这些能力本质是外部协同补偿和批次生命周期控制最适合作为正式接口契约补充。
Alternatives considered只在详细设计文字说明中顺带提及被放弃因为无法形成可验收的接口边界。
Decision对账能力在接口专项中补对账文件规则、文件命名和应答约束结算能力保持正式文档现有抽象不凭空制造外部结算报文。
Rationale外部资料对对账文件和对账请求定义较清晰但未提供独立结算报文正式文档当前以 `bk_settlement_batch` 和结算状态协同承接更稳妥。
Alternatives considered把对账与结算都写成独立外部接口被放弃因为当前证据不足会制造新的正式口径风险。
## 2. 当前 backend 实现成熟度
Decision`PayCeb` 应判定为“已实现(基础闭环)”,但代理收费对账仅为“部分实现”。
Rationale`PayCebController` + `PayCebServiceImpl` 已完成欠费查询、缴费的解密、反序列化、业务校验、调用 `ChargeApi`/`CustApi`/`DeptApi`、流水唯一性控制和交易日志留痕;`paymentCheck` 仍为 TODO占位返回。正式文档应写成“实时查询与缴费已实现对账回传未实现”而不是笼统写“PayCeb 全部已完成”。
Alternatives considered将整个实时收费能力统一标记为“部分实现”被放弃因为查询/缴费确有真实闭环,不应被 TODO 对账能力整体拉低。
Decision代扣签约与解约可判定为“已实现”代扣客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询均判定为“部分实现”。
Rationale`BankWithholdingController``BankWithholdingServiceImpl` 中签约/解约已完成解密、报文解析、机构/客户/协议校验、协议写入或状态更新、交易日志留痕;其余能力保留 controller TODO 和 service 空实现。
Alternatives considered将除签约外的全部能力标记为“文档先行”被放弃因为 app controller 路由、DTO 和服务接口已存在,说明已进入骨架实现阶段。
Decision银行托收路径与代扣路径结构平行成熟度整体为“部分实现”。
Rationale`BankCollectionController` / `BankCollectionServiceImpl` 具备签约、解约、客户状态查询、送盘、回盘、取消等接口与服务定义,但完整实现闭环尚不统一成熟。
Alternatives considered本轮不写托收被放弃因为外部设计和正式文档都把托收作为 `REV-008` 范围一部分。
Decision`bk_*` 表族应判定为“表结构与对象层基本齐备,但业务编排仅部分实现”。
Rationale`bk_payment_channel``bk_channel_api_config``bk_channel_route_rule``bk_channel_statistics``bk_transaction*``bk_withholding_*``bk_reconcile_*``bk_settlement_batch` 均有 DO/Mapper`sw-business-bank/docs/建表sql.sql` 给出完整建表脚本但仓库内未见正式迁移脚本统一落位Mapper XML 也未体现送盘/回盘/对账/结算完整编排。
Alternatives considered直接判为“已实现”被放弃因为对象齐备不等于业务闭环跑通。也不适合降为“文档先行”因为交易与协议对象已被真实使用。
Decision后台运营管理对象可判定为“已具备运营侧入口”但不能据此推断银行 app 协同闭环已完成。
Rationale`WithholdingAgreementController``WithholdingBatchController``ReconcileBatchController``SettlementBatchController` 等管理入口已存在,同时后台资源管理层已覆盖交易、协议、批次、明细、对账差异和结算台账。
Alternatives considered把后台运营入口视为所有能力已完成的证据被放弃因为 app 协议处理、文件交互和状态补偿仍有明显未实现部分。
## 3. 正式文档回写策略
Decision正式文档采用“目标设计边界 + 当前成熟度注记”的双层表达。
Rationale如果只写设计会掩盖实现缺口如果只写现状会丢失外部设计应承接的正式边界。
Alternatives considered只在 `specs/` 中记录成熟度,不回写主文档;被放弃,因为主文档才是正式评审入口。
Decision`01_Database_Design.md` 中仅补对象承接与成熟度说明,不新增 DDL 级细节。
Rationale当前数据库主文档已明确 `bk_*` 表族口径,本轮重点是把外部设计能力映射到既有对象。
Alternatives considered复写外部设计中的示例表结构被放弃因为会破坏文档抽象层次。
Decision最终结论以 `final-verdict.md` 汇总,并以 `baseline.md` 锚定 backend 版本。
Rationale后续 `/speckit.tasks` 和验收时需要直接复用统一结论。
Alternatives considered把所有证据都散落在 plan 或 spec 中;被放弃,因为不利于后续追踪。
## 4. 回写重点
Decision`docs/design/01_Overview/03_Summary_Design.md``SYS-009` 只能保留保守表述。
Rationale当前可安全声明的是聚合支付基础能力、银行欠费查询/缴费、代扣/托收签解约、后台资源管理已具备;夜间批量代扣、送盘/回盘、对账、结算不能继续使用“完整支持”的强表述。
Alternatives considered延续现有强描述被放弃因为会高估实现成熟度。
Decision`docs/design/03_Technical_Design/03_Interface_Design.md` 需显式拆分 `PayCeb Query/Pay``PayCheck``BankWithholding/BankCollection Signing/Termination` 与其余预留接口。
Rationale只有把接口分层定级后续评审和联调才不会把签解约已实现误读成送盘/回盘/对账也已完成。
Alternatives considered仅在 `final-verdict.md` 说明;被放弃,因为正式接口文档本身需要承担契约边界。
Decision`docs/design/03_Technical_Design/01_Database_Design.md` 保留 `bk_*` 为真实承接口径,但要补“业务编排与正式迁移证据仍不完整”的边界。
Rationale数据库对象确实存在但不能让读者从表族完整推断业务闭环完成。
Alternatives considered删弱 `bk_*` 口径;被放弃,因为会低估当前真实承接基础。
Decision正式文档修订已按上述研究结论完成首轮回写并通过 4 份主文档单文件校验。
Rationale当前 `12_REV_Detailed.md``03_Interface_Design.md``01_Database_Design.md``03_Summary_Design.md` 已分别补入成熟度边界、接口扩展说明和数据库承接口径说明,验证了研究结论可直接落入正式主文档。
Alternatives considered先只保留 research 结论、延后正式文档回写;被放弃,因为本轮目标是形成可交付、可评审的正式文档闭环。

View File

@ -0,0 +1,168 @@
# Feature Specification: SYS-009设计整合与实现对齐
**Feature Branch**: `007-sys009-design-align`
**Created**: 2026-03-20
**Status**: Draft
**Input**: User description: "我需要将 /Users/tangweijie/github/water-bank-api-doc 这里的SYS-009的设计整合到我们文档里面来,并且和现在实现的代码进行对其"
## Document Scope & Sources *(mandatory)*
- **Target documents**:
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/01_Overview/03_Summary_Design.md`
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/03_Task_Checklist.md`
- **Primary source of truth**:
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/design/00_Management/01_Project_Progress.md`
- `docs/design/00_Management/03_Task_Checklist.md`
- **Reference sources**:
- `/Users/tangweijie/github/water-bank-api-doc/营收系统缴费接口.md`
- `/Users/tangweijie/github/water-bank-api-doc/营收系统接口规范设计文档.md`
- `/Users/tangweijie/github/water-bank-api-doc/docs/README.md`
- `docs/guides/BACKEND_CURRENT_STATUS.md`
- `docs/guides/BACKEND_TABLE_MAPPING.md`
- `docs/design/04_Appendix/Archive/03_Design_Docs/营业收费管理系统-概要设计说明书20250912.md`
- `docs/design/04_Appendix/Archive/04_Original_Attachments/营收系统_需求规格说明书.md`
- **Scope decision**: 本次需求在范围内,范围判断以 `docs/design/01_Overview/03_Summary_Design.md``docs/design/04_Appendix/Archive/03_Design_Docs/营业收费管理系统-概要设计说明书20250912.md` 的交集为准;`SYS-009` 实时收费、代扣、托收、对账相关能力在交集范围内,可进入现有正式主文档整合与当前 backend 实现对齐,不新建平行正式文档。
## Repository Scope *(mandatory)*
- **Target repos**:
- `water-docs`: Required
- `water-backend`: Required
- `water-frontend`: Not Required
- **Expected delivery type**: Document closure / Code evidence alignment
- **Out of scope for this round**:
- 直接修改 `../water-backend``../water-frontend` 业务代码
- 新建独立的“SYS-009 正式主文档”替代现有主文档
- 在无实现证据的前提下把高级银行接口写成“已完成”
- 为银行批次、回盘、对账、结算补做前端运营页面设计
## Code Baseline *(mandatory for brownfield work)*
- **Backend baseline**: `water-backend` `develop` @ `68ab72ae6330e33afa0fec3817c0605829973ec2`
- **Frontend baseline**: `water-frontend` `develop` @ `ae65939045449894c0fccab53fee08521e538ddd`(仅用于范围判断,不作为本轮正式承接基线)
- **Baseline capture rule**: 所有“已实现 / 部分实现 / 文档先行”判断都必须绑定到本次记录的 backend commit SHA并在后续验证工件中写明对应代码路径与判定理由。
## Evidence Scope *(mandatory)*
- **Document evidence required**:
- `specs/007-sys009-design-align/spec.md`
- 正式文档中与 `SYS-009``REV-008`、外部银行接口相关的修订章节
- 管理台账中的变更记录与任务状态更新
- **Backend evidence required**:
- 实时收费接口证据:`PayCebController``PayCebServiceImpl`
- 银行代扣接口证据:`BankWithholdingController``BankWithholdingServiceImpl`
- 银行托收接口证据:`BankCollectionController``BankCollectionServiceImpl`
- 数据对象证据:`docs/建表sql.sql``bk_transaction*``bk_withholding_*``bk_reconcile_*``bk_settlement_batch`
- 运营侧入口证据:`WithholdingAgreementController``WithholdingBatchController``ReconcileBatchController``SettlementBatchController`
- **Frontend evidence required**:
- 非强制
- 如需说明边界,仅记录当前客户代扣资料维护入口与缺失的银行批次运营界面,不形成 frontend 改造范围
- **Verification artifacts required**:
- `specs/007-sys009-design-align/baseline.md`
- `specs/007-sys009-design-align/final-verdict.md`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - 整合正式设计口径 (Priority: P1)
作为文档评审人,我需要在现有正式主文档中看到 `SYS-009` 银行侧能力的统一设计口径,这样我可以用一套正式材料评审实时收费、代扣、送盘、回盘、对账与结算能力,而不是同时维护外部原始资料和仓内多份描述。
**Why this priority**: 这是本次需求的核心目标;如果正式文档不先吸收 `SYS-009` 设计,后续代码评审、验收和任务拆解都会继续依赖仓外资料,无法形成单一交付入口。
**Independent Test**: 仅阅读更新后的正式主文档,即可定位 `SYS-009` 相关能力范围、接口边界、关键数据对象和与 `REV-003` / `REV-008` 的关系,无需再依赖仓外原始接口文档。
**Acceptance Scenarios**:
1. **Given** 正式文档目前仅概括了 `SYS-009` 的实时收费、代扣、对账、结算能力,**When** 本次整合完成,**Then** 正式文档必须明确承接外部来源中的实时收费、代扣签约/解约、送盘、回盘、对账、状态查询与取消等关键能力簇。
2. **Given** 外部 `water-bank-api-doc` 存在银行协议与接口细节,**When** 设计被回写到正式主文档,**Then** 必须保持当前文档编号、术语和主文档结构稳定,而不是新增平行主稿。
---
### User Story 2 - 对齐当前实现边界 (Priority: P2)
作为技术评审人,我需要知道 `SYS-009` 哪些设计已经被当前 backend 实现承接、哪些只是接口骨架或数据模型已具备、哪些仍然只是文档先行,这样我可以据此安排后续开发和验收范围。
**Why this priority**: 仅有设计整合但没有实现对齐,会把“接口存在”“代码完成”“数据表已建”混为一谈,直接影响后续任务排序和风险判断。
**Independent Test**: 对照文档中的对齐结论和 backend 验证工件,评审人应能独立判断实时收费、代扣签约/解约、客户状态查询、送盘、回盘、对账等能力分别处于哪种状态。
**Acceptance Scenarios**:
1. **Given** backend 已存在 `PayCeb*``BankWithholding*` 代码入口,**When** 文档完成对齐,**Then** 文档必须区分“已实现”“部分实现”“文档先行”三类状态,而不能统一写成“已支持”。
2. **Given** backend 中部分接口仍为 TODO 或占位返回,**When** 文档回写正式口径,**Then** 必须把这些能力标记为待补实现或仅保留设计边界,不能误写为已落地闭环。
---
### User Story 3 - 形成可审计证据链 (Priority: P3)
作为项目治理人员,我需要这次整合留下可追溯的基线、验证和结论工件,这样后续 `/speckit.plan`、任务拆解和验收结论都能直接复用,不再重复做同一轮对齐判断。
**Why this priority**: 没有基线和验证工件,正式文档即使更新,也难以证明它对应的是哪一版实现和哪些证据。
**Independent Test**: 只检查 `specs/007-sys009-design-align/` 下的工件与管理台账,即可确认本轮使用的代码基线、核对过的证据路径以及最终结论。
**Acceptance Scenarios**:
1. **Given** 本轮需要引用外部设计与当前代码,**When** 验证结束,**Then** 必须能从工件中看到代码基线、证据路径、结论和未闭环项。
2. **Given** 正式主文档发生重要更新,**When** 本轮收尾,**Then** `01_Project_Progress.md``03_Task_Checklist.md` 必须同步回写本次变更摘要和收尾状态。
---
### Edge Cases
- 当 `water-bank-api-doc` 的接口设计比当前正式文档更细,但 backend 仅实现了其中一部分时,正式文档必须同时保留目标设计边界和当前实现状态,不能只保留其一。
- 当外部设计文档的 OpenAPI 版本遗漏了对账、客户状态查询、取消送盘、状态查询等高级接口时,应回退到原始 Markdown 接口说明核对,而不是误判这些能力“不在设计范围”。
- 当 backend 已有 `bk_*` 表族和后台管理控制器,但银行 app 侧接口仍是 TODO 时,文档必须避免把“后台对象存在”直接等同为“银行协同闭环完成”。
- 当 frontend 只存在客户代扣资料维护入口而不存在银行批次、回盘、对账运营页面时,正式文档必须明确这是当前范围边界,而不是默认这些页面已落地。
## Assumptions
- 本轮“整合 `SYS-009` 设计”是指回写到现有正式主文档体系,而不是创建新的正式交付主稿。
- 当前实现对齐以 `water-backend` 为主要证据来源,因为银行协议接口、交易流水、批次、回盘、对账和结算对象都集中在 backend。
- 对于 controller/service 中仍为 TODO 的能力,本轮会按“部分实现”或“文档先行”处理,而不会要求在本轮直接补代码。
- `water-bank-api-doc` 是本轮银行接口语义的重要参考,但正式口径仍以本仓库主文档编号、术语和边界约束为准。
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: 规格必须明确本轮将修改的正式主文档,并以现有主文档为唯一正式承接入口。
- **FR-002**: 规格必须覆盖外部 `SYS-009` 来源中的关键银行能力簇,至少包括实时收费、缴费结果处理、代扣签约/解约、客户状态查询、送盘、送盘状态查询、取消送盘、回盘、回盘状态查询、对账与结算。
- **FR-003**: 规格必须定义每个关键能力簇在当前实现中的状态标签,且状态标签至少区分“已实现”“部分实现”“文档先行”三类。
- **FR-004**: 规格必须要求正式文档中的接口、时序、数据对象和模块描述与当前 backend 的 `bk_transaction*``bk_withholding_*``bk_reconcile_*``bk_settlement_batch` 等对象口径一致。
- **FR-005**: 规格必须要求正式文档保持当前编号和引用体系稳定,不得为本轮整合发明新的并行编号规则或新的平行正式文档。
- **FR-006**: 规格必须记录 backend 基线版本,并要求所有实现判断可追溯到具体代码路径和该基线版本。
- **FR-007**: 规格必须限定本轮以文档修订和代码证据核对为主,不直接包含 backend 或 frontend 业务代码改造。
- **FR-008**: 规格必须要求在正式文档中明确实时收费与银行代扣是不同协同路径,避免把查询/缴费、送盘/回盘、对账/结算混写为同一处理流程。
- **FR-009**: 规格必须要求当外部设计与当前实现不一致时,正式文档优先表达当前可落地边界,并对未落地能力保留清晰的待实现说明。
- **FR-010**: 规格必须要求正式文档同步说明 `REV-003``REV-008``SYS-009` 之间的职责边界,避免把营收业务规则、银行渠道协议和聚合支付职责混淆。
- **FR-011**: 规格必须要求在本轮输出验证工件至少包括代码基线、正式文档校验、backend 证据核对和最终结论。
- **FR-012**: 规格必须要求重要正式文档修改完成后,更新 `docs/design/00_Management/01_Project_Progress.md` 的变更记录。
- **FR-013**: 规格必须要求当本轮任务项达到可关闭状态时,更新 `docs/design/00_Management/03_Task_Checklist.md` 的任务说明。
- **FR-014**: 规格必须要求对变更后的正式文档执行适用的最小校验动作,包括单文件校验、链接检查或 Mermaid 检查。
### Key Entities *(include if feature involves data)*
- **Source Capability**: 外部 `water-bank-api-doc` 中定义的一个银行协同能力簇,例如实时收费、代扣签约、送盘、回盘、对账。
- **Formal Document Section**: 本仓库正式主文档中承接 `SYS-009` 设计的章节或表格单元,是本轮最终交付的正式落点。
- **Backend Evidence Item**: 用于证明当前实现状态的 controller、service、数据对象、建表 SQL 或管理入口。
- **Alignment Verdict**: 对单个能力簇给出的实现结论,取值为“已实现”“部分实现”“文档先行”之一。
- **Code Baseline**: 用于锚定本轮对齐判断的相邻代码仓提交版本。
- **Verification Artifact**: 本轮在 `specs/007-sys009-design-align/` 下生成的基线、验证和最终结论文档。
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: 正式文档中至少明确 5 组 `SYS-009` 能力簇的承接口径,且每组能力簇都能映射到目标文档中的具体章节或表格位置。
- **SC-002**: 每个被纳入正式文档的 `SYS-009` 能力簇都至少关联 1 个外部来源和 1 个当前代码证据,或明确标注其仅为文档先行能力。
- **SC-003**: 评审人能够基于本轮工件独立判断实时收费、代扣签约/解约、送盘、回盘、对账、结算各自属于“已实现”“部分实现”还是“文档先行”。
- **SC-004**: 本轮基线、文档验证、backend 证据核对和最终结论工件全部具备明确文件名和用途,后续无需重新定义验证结构即可进入 `/speckit.plan`
- **SC-005**: 本轮规格中不保留 `[NEEDS CLARIFICATION]` 标记,且范围边界、依赖来源、验证动作和台账更新责任均可直接执行。

View File

@ -0,0 +1,195 @@
# Tasks: SYS-009设计整合与实现对齐
**Input**: Design documents from `/specs/007-sys009-design-align/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/, quickstart.md
**Validation**: Validation and evidence tasks are NOT optional. Every feature task set MUST include the applicable document validation, code validation, ledger-sync, and final-verdict tasks.
**Organization**: Tasks are grouped by user story so each slice can be completed, reviewed, and validated independently.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Path Conventions
- Formal workflow: `water-docs/`
- Main documents: `docs/design/01_Overview/`, `docs/design/02_Detailed_Design/`, `docs/design/03_Technical_Design/`
- Governance documents: `docs/design/00_Management/`
- Feature artifacts: `specs/007-sys009-design-align/`
- Backend modules: `water-backend/...`
- Frontend modules: `water-frontend/...`
## Phase 1: Scope, Baseline & Source Confirmation
**Purpose**: Confirm the source-of-truth set, repo boundary, required reading set, and code baselines before editing anything.
- [x] T001 阅读 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/01_Project_Progress.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/02_Delivery_Standards.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/03_Task_Checklist.md`
- [x] T002 阅读 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/01_Overview/03_Summary_Design.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/02_Detailed_Design/12_REV_Detailed.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/01_Database_Design.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/03_Interface_Design.md`
- [x] T003 [P] 阅读 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/04_Writing_Guide.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/08_AI_Agent_Maintenance_SOP.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/10_AI_Retrieval_Whitelist.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/11_Main_Doc_Chapter_Index.md`
- [x] T004 Confirm target documents, target repos, user story priorities, and exact chapter targets from `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/spec.md`
- [x] T005 Confirm governing source-of-truth documents, range intersection basis, external references, and validation commands from `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/plan.md`
- [x] T006 [P] Record the stable repo baselines and key evidence anchors in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/baseline.md`
- [x] T007 [P] Confirm the formal alignment contract and verdict rules in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/contracts/sys009-capability-alignment.md` and `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/contracts/sys009-status-verdicts.md`
---
## Phase 2: Shared Foundation
**Purpose**: Establish the shared alignment baseline for docs, code evidence, and verification outputs.
- [x] T008 Normalize capability names, verdict vocabulary, and `REV-003` / `REV-008` / `SYS-009` responsibility terms in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/research.md`
- [x] T009 [P] Prepare the implementation/evidence checklist and execution order in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/quickstart.md`
- [x] T010 [P] Prepare the final evidence output skeleton in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
---
## Phase 3: User Story 1 - 整合正式设计口径 (Priority: P1) 🎯 MVP
**Goal**: 将外部 `SYS-009` 设计吸收进现有正式主文档,使正式评审不再依赖仓外资料。
**Independent Test**: 仅阅读 `12_REV_Detailed.md``03_Interface_Design.md``03_Summary_Design.md`,即可定位实时收费、签解约、送盘/回盘、状态查询、取消送盘、对账/结算的正式口径和章节入口。
### Implementation for User Story 1
- [x] T011 [US1] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/02_Detailed_Design/12_REV_Detailed.md`,补齐 `REV-003` 实时收费与 `REV-008` 代扣/托收/送盘/回盘/对账/结算的正式业务边界
- [x] T012 [US1] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/03_Interface_Design.md`,补齐签约、解约、客户状态查询、送盘状态查询、取消送盘、回盘状态查询和对账文件规则
- [x] T013 [US1] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/01_Overview/03_Summary_Design.md`,同步 `SYS-009` 范围摘要和外部接口表的正式描述
- [x] T014 [US1] 同步 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/contracts/sys009-capability-alignment.md` 中的正式落点结论,确保与主文档实际章节一致
- [x] T015 [US1] 运行 `make validate-file FILE=docs/design/02_Detailed_Design/12_REV_Detailed.md` 并将结果记录到 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
- [x] T016 [US1] 运行 `make validate-file FILE=docs/design/03_Technical_Design/03_Interface_Design.md``make validate-file FILE=docs/design/01_Overview/03_Summary_Design.md` 并将结果记录到 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
**Checkpoint**: User Story 1 is independently reviewable with formal `SYS-009` design integrated into the main documents.
---
## Phase 4: User Story 2 - 对齐当前实现边界 (Priority: P2)
**Goal**: 将当前 backend 的真实成熟度结论回写到正式文档和证据工件,避免高估 `SYS-009` 完成度。
**Independent Test**: 对照正式文档与 `final-verdict.md`,评审人能够独立判断 `PayCeb``BankWithholding``BankCollection``bk_*` 表族和后台管理入口分别处于什么成熟度。
### Implementation for User Story 2
- [x] T017 [US2] 核对 `/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/payceb/PayCebController.java``/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/payceb/PayCebServiceImpl.java`,确认实时收费查询/缴费/对账的成熟度结论
- [x] T018 [P] [US2] 核对 `/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/bankwithholding/BankWithholdingController.java``/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/bankwithholding/BankWithholdingServiceImpl.java`,确认代扣签解约与其余接口的成熟度结论
- [x] T019 [P] [US2] 核对 `/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/app/bankcollection/BankCollectionController.java``/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/service/bankcollection/BankCollectionServiceImpl.java`,确认托收平行链路的成熟度结论
- [x] T020 [P] [US2] 核对 `/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/docs/建表sql.sql``/Volumes/Dpan/github/water-workspace/water-backend/sw-business-bank/sw-business-bank-server/src/main/java/cn/com/emsoft/sw/bankbusiness/controller/admin/` 下后台管理入口,确认 `bk_*` 表族和资源管理层边界
- [x] T021 [US2] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/01_Database_Design.md`,补充 `bk_*` 表族“对象层齐备但业务编排仅部分实现”的边界说明
- [x] T022 [US2] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/03_Interface_Design.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/01_Overview/03_Summary_Design.md`,将 `PayCeb``BankWithholding``BankCollection` 的真实成熟度回写为保守口径
- [x] T023 [US2] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md``/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/contracts/sys009-status-verdicts.md`,固化本轮实现边界结论
- [x] T024 [US2] 运行 `make validate-file FILE=docs/design/03_Technical_Design/01_Database_Design.md` 并将 backend 证据核对结果写入 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
- [x] T025 [US2] 运行 `make validate-file FILE=docs/design/03_Technical_Design/03_Interface_Design.md``make validate-file FILE=docs/design/01_Overview/03_Summary_Design.md` 并将结果记录到 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
**Checkpoint**: User Story 2 is independently reviewable and all maturity statements are tied to concrete backend evidence.
---
## Phase 5: User Story 3 - 形成可审计证据链 (Priority: P3)
**Goal**: 形成可复用的基线、验证、台账和交付摘要,使后续任务和验收可以直接沿用。
**Independent Test**: 仅检查 `specs/007-sys009-design-align/` 和治理台账,即可确认本轮基线、修订范围、验证动作和剩余未闭环项。
### Implementation for User Story 3
- [x] T026 [US3] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/baseline.md`,确认最终使用的 backend/frontend 基线和关键证据路径
- [x] T027 [P] [US3] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/quickstart.md`,使执行顺序、验证命令和回填位置与实际修订结果一致
- [x] T028 [P] [US3] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/research.md``/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/data-model.md`,确保研究结论和实体模型与最终正式口径一致
- [x] T029 [US3] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/01_Project_Progress.md`,记录本轮 `SYS-009` 设计整合与实现对齐变更摘要
- [x] T030 [US3] 更新 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/03_Task_Checklist.md`,回写本轮任务闭环状态和后续待办边界
- [x] T031 [US3] 复核 `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/01_Project_Progress.md``/Volumes/Dpan/github/water-workspace/water-docs/docs/design/00_Management/03_Task_Checklist.md` 的更新内容,并将治理同步结果记录到 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
- [x] T032 [US3] 汇总已修改文件、校验结果、成熟度结论和剩余风险到 `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
**Checkpoint**: All planning evidence, governance records, and final conclusions are independently reviewable.
---
## Final Phase: Verification & Closure
**Purpose**: Ensure repository-wide consistency, baseline traceability, and final verdict output.
- [x] T033 [P] Re-check source-of-truth alignment across `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/01_Overview/03_Summary_Design.md`, `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/02_Detailed_Design/12_REV_Detailed.md`, `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/01_Database_Design.md`, and `/Volumes/Dpan/github/water-workspace/water-docs/docs/design/03_Technical_Design/03_Interface_Design.md`
- [x] T034 [P] Run `make check-links` and `make validate-mermaid` and record the repository-wide results in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
- [x] T035 Re-check backend baseline SHAs, evidence citations, and governance sync results in `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/baseline.md` and `/Volumes/Dpan/github/water-workspace/water-docs/specs/007-sys009-design-align/final-verdict.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 1: Scope, Baseline & Source Confirmation**: No dependencies; MUST finish before edits
- **Phase 2: Shared Foundation**: Depends on Phase 1 and MUST finish before user stories
- **Phase 3: User Story 1**: Depends on Phase 2; MVP slice
- **Phase 4: User Story 2**: Depends on Phase 3 because mature capability wording must align to the integrated formal design
- **Phase 5: User Story 3**: Depends on Phase 3 and Phase 4 because evidence and governance must reflect final doc/evidence state
- **Final Phase**: Depends on all selected user stories being complete
### User Story Completion Order
`US1 -> US2 -> US3`
### Within Each User Story
- Complete formal doc edits before validation
- Complete backend evidence inspection before maturity wording is finalized
- Complete governance updates after document and evidence conclusions stabilize
- Complete final verdict updates after validation and ledger sync are done
### Parallel Opportunities
- `T003`, `T006`, and `T007` can run in parallel after baseline reading
- `T009` and `T010` can run in parallel
- In US2, `T018`, `T019`, and `T020` can run in parallel because they inspect different capability slices
- In US3, `T027` and `T028` can run in parallel because they touch different feature artifacts
- In the final phase, `T033` and `T034` can run in parallel
## Parallel Execution Examples
### User Story 1
```text
Run T015 after T011 completes
Run T016 after T012 and T013 complete
```
### User Story 2
```text
Run T018, T019, and T020 together
Run T021 and T022 after T017-T020 complete
Run T023-T025 after T021 and T022 complete
```
### User Story 3
```text
Run T027 and T028 together
Run T029 and T030 after T026-T028 complete
Run T031 after T029 and T030 complete
Run T032 after T031 complete
```
## Implementation Strategy
### MVP First
1. Finish Phase 1 and Phase 2
2. Deliver US1 only: 先完成正式设计整合,让主文档成为单一评审入口
3. Validate US1 before moving to US2
### Incremental Delivery
1. US1: 正式设计口径收拢
2. US2: 当前实现成熟度对齐
3. US3: 证据链和治理台账闭环
### Notes
- Every task preserves the single-source-of-truth model in `water-docs`
- Archive and external docs are used for verification and traceability, not direct formal replacement
- `water-backend` only provides implementation evidence in this round
- Validation, evidence, and ledger tasks are mandatory and already included

View File

@ -0,0 +1,185 @@
# Dictionary: REV-004 第一批次编码转换字典 v1
## 1. 目标
本字典用于补齐第一批次试迁中最容易导致字段映射失真的编码转换规则。
当前仅覆盖三类核心编码:
- `PriceListId`
- `PriceCode`
- `PriceItemId`
它们分别影响:
- 账单主表中的调价号 / 调价快照承接
- 账单主表与明细中的用水性质 / 价格模板承接
- 明细中的费用组成承接
## 2. 使用原则
- 本字典当前是 v1 规划字典,不假定所有旧编码与新编码已经一一建立物理映射表。
- 若当前 backend 已存在稳定编码主数据,应优先落到现有主数据对象。
- 若当前尚未确认稳定编码表,则先保留“旧值 + 映射占位 + 追溯字段”,不要在脚本里硬编码不可验证的转换结果。
## 3. 编码转换规则总览
| 旧字段 | 旧语义 | 新承接字段 | 当前转换方式 | 当前状态 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `PriceListId` | 调价号 / 水价调整编号 | `adjustmentSnapCode` | `legacy int -> new string code` | `needs-mapping` | 需要形成调价号到快照编码的转换字典 |
| `PriceCode` | 用水性质 / 价格类别编码 | `priceTemplateCode` | `legacy int -> new string template code` | `needs-mapping` | 需要形成旧用水性质到新价格模板编码的转换字典 |
| `PriceItemId` | 费用组成 ID | `costComponentCode` | `legacy int -> new string component code` | `needs-mapping` | 需要形成旧费用项到新费用组成编码的转换字典 |
## 4. `PriceListId` 转换字典
### 4.1 承接目标
| 旧字段 | 新字段 | 说明 |
| --- | --- | --- |
| `AT_CHARGES.PriceListId` | `biz_charge.adjustmentSnapCode` | 账单主表调价快照编码 |
| `AT_CHARGE_DETAILS.PriceListId` | `biz_charge_detail.adjustmentSnapCode` | 账单明细调价快照编码 |
### 4.2 当前转换策略
在未建立完整字典前,建议采用两段式处理:
1. 迁移脚本先保留原值到映射层
2. 主表字段使用可验证的新编码规则转换,若无法确定则标记待补
### 4.3 推荐映射结构
| 字段 | 说明 |
| --- | --- |
| `legacyPriceListId` | 旧调价号 |
| `targetAdjustmentSnapCode` | 新调价快照编码 |
| `mappingSource` | 来源:主数据 / 规则推导 / 手工补录 |
| `mappingStatus` | `planned / verified / unresolved` |
| `remark` | 备注 |
### 4.4 当前脚本建议
- 若存在稳定调价快照表和唯一编码,可直接转换。
- 若不存在稳定映射来源:
- `adjustmentSnapCode` 可先按约定规则生成占位值
- 同时必须写入 `legacyPriceListId`
- `mappingStatus` 标记为 `unresolved`
## 5. `PriceCode` 转换字典
### 5.1 承接目标
| 旧字段 | 新字段 | 说明 |
| --- | --- | --- |
| `AT_CHARGES.PriceCode` | `biz_charge.priceTemplateCode` | 账单主表价格模板编码 |
| `AT_CHARGE_DETAILS.PriceCode` | `biz_charge_detail.priceTemplateCode` | 账单明细价格模板编码 |
### 5.2 当前转换策略
`PriceCode` 在旧模型里是整数型“用水性质”,在新模型里更接近字符串型模板编码。
因此本轮不建议简单字符串化后直接当正式编码使用,而应采用:
- `legacyPriceCode` 保留旧值
- `targetPriceTemplateCode` 记录新值
- 明确映射来源
### 5.3 推荐映射结构
| 字段 | 说明 |
| --- | --- |
| `legacyPriceCode` | 旧用水性质编码 |
| `targetPriceTemplateCode` | 新价格模板编码 |
| `priceCategoryName` | 可选,用于人工核对 |
| `mappingSource` | 来源:价格模板表 / 规则推导 / 手工补录 |
| `mappingStatus` | `planned / verified / unresolved` |
### 5.4 当前脚本建议
- 若已有价格模板主数据且存在旧编码字段,优先按主数据表映射。
- 若无稳定映射来源,暂不把脚本写成“旧值转字符串”这种伪映射。
- 所有无法确认的值必须落差异清单。
## 6. `PriceItemId` 转换字典
### 6.1 承接目标
| 旧字段 | 新字段 | 说明 |
| --- | --- | --- |
| `AT_CHARGE_DETAILS.PriceItemId` | `biz_charge_detail.costComponentCode` | 明细费用组成编码 |
### 6.2 当前转换策略
`PriceItemId` 旧模型是费用组成主键,当前新模型更偏编码型字段 `costComponentCode`
这类转换比 `PriceListId``PriceCode` 更敏感,因为它直接影响费用构成、统计汇总和开票明细。
### 6.3 推荐映射结构
| 字段 | 说明 |
| --- | --- |
| `legacyPriceItemId` | 旧费用组成 ID |
| `targetCostComponentCode` | 新费用组成编码 |
| `targetCostComponentName` | 新费用组成名称 |
| `mappingSource` | 来源:费用组成主数据 / 手工字典 |
| `mappingStatus` | `planned / verified / unresolved` |
### 6.4 当前脚本建议
- 没有稳定费用组成字典前,不要直接迁移到正式 `costComponentCode`
- 可先写入映射表,待字典确认后再回填主表。
- 对无法映射的费用项必须记录差异,不能静默丢弃。
## 7. 第一批次最小字典表建议
后续如果进入脚本实施,建议至少准备三张字典或等价数据集:
| 字典名称 | 用途 |
| --- | --- |
| `legacy_price_list_mapping` | `PriceListId -> adjustmentSnapCode` |
| `legacy_price_code_mapping` | `PriceCode -> priceTemplateCode` |
| `legacy_price_item_mapping` | `PriceItemId -> costComponentCode` |
每张字典至少包含:
- `legacyCode`
- `targetCode`
- `targetName`
- `mappingSource`
- `mappingStatus`
- `remark`
## 8. 当前 v1 的直接结论
### 8.1 可以先不阻塞试迁的项
- `PriceListId`
- `PriceCode`
前提是:
- 原值必须保留
- 差异必须可追踪
- 未确认映射不得伪造正式编码
### 8.2 不能随便糊过去的项
- `PriceItemId`
因为它直接影响:
- 费用组成
- 开票明细
- 金额汇总
- 后续统计与审计
所以 `PriceItemId` 的字典确认优先级高于前两者。
## 9. 后续动作
本字典之后,建议继续补:
1. 第一批次枚举值对照表
重点是 `PayState``FeeState``AccountState`
2. 第一批次差异清单模板
专门记录编码映射缺口、无法确认项和人工补录项

View File

@ -0,0 +1,240 @@
# Matrix: REV-004 第一批次真实迁移映射矩阵细化版 v1
## 1. 目标
本矩阵用于把第一批次迁移对象细化到“可以直接指导脚本设计”的程度。
第一批次范围限定为:
- `AT_CHARGES`
- `AT_CHARGE_DETAILS`
- `CT_ACCOUNTS`
本版重点不是覆盖旧表所有字段,而是先锁定:
- 主键与核心业务标识
- 核心业务字段
- 关键状态字段
- 必要关系字段
- 映射落库要求
## 2. 第一批次总原则
- 账单主明细和账户对象属于在线主模型优先承接范围。
- 旧主键和旧业务标识必须进入映射层,不允许只保留新主键。
- 当前不能稳定落入主表的旧字段,先进入映射层或历史只读层,不强行污染主对象。
- 第一批次试迁后必须能回答:原账单是谁、原账户是谁、新对象是谁、金额和状态是否一致。
## 3. 表级落位总览
| 旧表 | 目标主对象 | 目标映射对象 | 当前 backend 证据 | 当前结论 |
| --- | --- | --- | --- | --- |
| `AT_CHARGES` | `biz_charge` | `legacy_charge_mapping` | `ChargeDO.java` | 可直接作为第一批次主对象 |
| `AT_CHARGE_DETAILS` | `biz_charge_detail` | `legacy_charge_detail_mapping` | `ChargeDetailDO.java` | 可直接作为第一批次明细对象 |
| `CT_ACCOUNTS` | `biz_account` | `legacy_account_mapping` | `AccountDO.java` | 可直接作为第一批次账户对象 |
## 4. `AT_CHARGES -> biz_charge` 细化映射
### 4.1 主键与标识
| 旧字段 | 新字段/位置 | 策略 | 备注 |
| --- | --- | --- | --- |
| `FeeId` | `biz_charge.id` | 直接承接或保留旧值映射 | 是否复用旧主键需由迁移脚本策略决定,但映射表必须保留 |
| `FeeId` | `legacy_charge_mapping.legacyId` | 必填 | 新旧账单主线标识 |
| 原账单号(如存在) | `legacy_charge_mapping.legacyBizNo` | 必填 | 若旧表无独立账单号,可用 `FeeId` 兼任 |
| 新账单号(如生成) | `legacy_charge_mapping.targetBizNo` | 选填 | 若新系统仅用主键,可暂为空 |
### 4.2 核心业务字段
| 旧字段 | 新字段 | 策略 | 备注 |
| --- | --- | --- | --- |
| `RecordId` | `recordId` | 直接映射 | 抄表记录引用 |
| `BillMonth` | `billMonth` | 直接映射 | 核心账期字段 |
| `OrgId` | `deptId` | 转换映射 | 旧站点字段名与新部门字段名不同 |
| `BookId` | `bookId` | 直接映射 | 册本引用 |
| `BookSortIndex` | `bookSortIndex` | 直接映射 | 册内顺序 |
| `CustId` | `custId` | 直接映射 | 客户主键 |
| `CustCode` | `custCode` | 直接映射 | 客户编号 |
| `CustName` | `custName` | 直接映射 | 客户名称 |
| `CustAddress` | `custAddress` | 直接映射 | 客户地址 |
| `Populine` | `population` | 字段改名映射 | 旧字段拼写与新字段不同 |
| `PayMethod` | `payMethod` | 直接/枚举转换 | 需核对枚举值是否一致 |
| `LastReading` | `lastReading` | 类型转换映射 | 旧 `int` 到新 `BigDecimal` |
| `LastChildReading` | `lastChildReading` | 类型转换映射 | 同上 |
| `LastReadWater` | `lastReadWater` | 类型转换映射 | 同上 |
| `LastReadDate` | `lastReadDate` | 直接映射 | 时间字段 |
| `LastReadStateId` | `lastReadStateId` | 直接映射 | 状态引用 |
| `Reading` | `reading` | 类型转换映射 | 旧 `int` 到新 `BigDecimal` |
| `ChildReading` | `childReading` | 类型转换映射 | 同上 |
| `ReadWater` | `readWater` | 类型转换映射 | 同上 |
| `ReadDate` | `readDate` | 直接映射 | 时间字段 |
| `ReadStateId` | `readStateId` | 直接映射 | 状态引用 |
| `ReadTimes` | `readTimes` | 直接映射 | 抄次 |
| `MeterReaderId` | `meterReaderId` | 直接映射 | 抄表员 |
| `PriceListId` | `adjustmentSnapCode` 或扩展映射 | 暂按转换映射 | 旧调价号是 `int`,新对象用 `String adjustmentSnapCode` |
| `PriceCode` | `priceTemplateCode` | 转换映射 | 旧用水性质编码映射到新价格模板编码 |
| `TotalWater` | `totalWater` | 类型转换映射 | 旧 `int` 到新 `BigDecimal` |
| `ReplaceWater` | `replaceWater` | 类型转换映射 | 同上 |
| `BillWater` | `billWater` | 类型转换映射 | 同上 |
| `BillAmount` | `billAmount` | 直接映射 | 金额核心字段 |
| `ExtendedAmount` | `extendedAmount` | 直接映射 | 应收金额核心字段 |
| `LastChange` | `lastChange` | 直接映射 | 零头字段 |
| `Change` | `change` | 直接映射 | 零头字段 |
| `BillerId` | `billerId` | 直接映射 | 开账人 |
| `BillDate` | `billDate` | 直接映射 | 开账时间 |
| `LateFeeBeginDate` | `lateFeeBeginDate` | 直接映射 | 滞纳金起算日 |
| `LateFee` | `lateFee` | 直接映射 | 核心金额字段 |
| `CashierId` | `cashierId` | 直接映射 | 收费员 |
| `PayDate` | `payDate` | 直接映射 | 收费时间 |
| `CheckoutDate` | `checkoutDate` | 直接映射 | 结账时间 |
| `ChargeMethod` | `chargeMethod` | 直接/枚举转换 | 需核对枚举 |
| `ChargeWay` | `chargeWay` | 直接/枚举转换 | 需核对枚举 |
### 4.3 状态与结果字段
| 旧字段 | 新字段/位置 | 策略 | 备注 |
| --- | --- | --- | --- |
| `PayState` | `biz_charge.payState` | 状态转换 | 必须按状态矩阵归并 |
| `FeeState` | `biz_charge.feeState``legacyState` | 状态转换 + 保留旧值 | 若新字段无法完整表达,保留旧值 |
| `InvoiceState` | `biz_charge.invoiceState` + 发票映射层 | 状态转换 | 需与发票主对象一致 |
| `InvoiceError` | `biz_charge.invoiceError` | 直接映射 | 开票失败原因 |
| `PrintState` | `biz_charge.printState` | 直接映射 | 打印状态 |
| `PrintTimes` | `biz_charge.printTimes` | 直接映射 | 打印次数 |
| `LockNum` | `biz_charge.lockNum` | 直接映射 | 锁定编号 |
| `LockoutEndTime` | `biz_charge.lockoutEndTime` | 直接映射 | 锁定时间 |
### 4.4 关系和追溯字段
| 旧字段 | 新字段/位置 | 策略 | 备注 |
| --- | --- | --- | --- |
| `AdjustType` | `biz_charge.adjustType` + `tracePayload.adjustType` | 直接映射 + 追溯保留 | 当前新对象已有该字段 |
| `ContrastFeeId` | `biz_charge.contrastFeeId` + `legacy_charge_mapping.relatedFeeId` | 直接映射 + 映射保留 | 调整前后关系 |
| `ParentFeeId` | `legacy_charge_mapping.sourceFeeId` | 追溯保留 | 新 `ChargeDO` 未见同名字段,必须进映射层 |
| `InvoiceCode` | `biz_charge.invoiceCode` | 直接映射 | 发票代码 |
| `InvoiceDate` | `biz_charge.invoiceDate` | 直接映射 | 开票日期 |
| `InvoiceNumber` | `biz_charge.invoiceNumber` | 直接映射 | 发票号码 |
| `RefundWater` | `biz_charge.refundWater` + `tracePayload.refundWater` | 直接映射 + 追溯保留 | 当前新对象已有该字段 |
### 4.5 第一批次对 `AT_CHARGES` 的直接建议
- `AT_CHARGES` 可直接作为第一批次最优先迁移主表。
- 需要额外设计:
- `PriceListId -> adjustmentSnapCode`
- `PriceCode -> priceTemplateCode`
- `ParentFeeId` 的映射层保留
- `PayState / FeeState / InvoiceState` 的状态归并规则
## 5. `AT_CHARGE_DETAILS -> biz_charge_detail` 细化映射
### 5.1 主键与关系
| 旧字段 | 新字段/位置 | 策略 | 备注 |
| --- | --- | --- | --- |
| 明细主键 `Id` | `biz_charge_detail.id` | 直接承接或映射保留 | 视脚本主键策略决定 |
| `FeeId` | `biz_charge_detail.feeId` | 直接映射 | 必须保持主明细关系 |
| 明细主键 | `legacy_charge_detail_mapping.legacyId` | 必填 | 保留旧明细主键 |
### 5.2 核心业务字段
| 旧字段 | 新字段 | 策略 | 备注 |
| --- | --- | --- | --- |
| `PriceListId` | `adjustmentSnapCode` | 转换映射 | 与主账一致,需要调价号转换 |
| `PriceCode` | `priceTemplateCode` | 转换映射 | 与主账一致 |
| `PriceDetailId` | `costAdjustmentId` 或扩展字段 | `tbd` | 需进一步确认新字段语义是否等价 |
| `PriceItemId` | `costComponentCode` | 转换映射 | 旧费用组成 ID 到新费用组成编码 |
| `CalcMode` | `calcMode` | 直接映射 | 计费方式 |
| `LevelType` | `levelType` | 直接映射 | 阶梯模式 |
| `LevelIndex` | `levelIndex` | 直接映射 | 阶梯级别 |
| `StartMonth` | `startMonth` | 直接映射 | 开始月份 |
| `EndMonth` | `endMonth` | 直接映射 | 结束月份 |
| `StartWater` | `startWater` | 类型转换映射 | 旧 `int` 到新 `BigDecimal` |
| `EndWater` | `endWater` | 类型转换映射 | 同上 |
| `SettleMethod` | `settleMethod` | 直接映射 | 分摊方式 |
| `SettleValues` | `settleValues` | 类型转换映射 | 旧 `float` 到新 `BigDecimal` |
| `Price` | `price` | 直接映射 | 单价 |
| `Water` | `water` | 类型转换映射 | 应收水量 |
| `DiscountWater` | `discountWater` | 类型转换映射 | 优惠水量 |
| `WaterNum` | `waterNum` | 直接映射 | 水量系数 |
| `Money` | `money` | 直接映射 | 明细金额 |
| `State` | `state` | 状态转换 | 明细状态需核对枚举 |
| `IsPreferential` | `isPreferential` | 直接映射 | 是否优惠 |
| `BasicNumber` | `basicNumber` | 直接映射 | 基数 |
| `InvoicedState` | `invoicedState` | 状态转换 | 开票状态 |
| `DiscountMoney` | `discountMoney` | 直接映射 | 优惠金额 |
| `OriginalMoney` | `originalMoney` | 直接映射 | 优惠前金额 |
| `IsWaterOver` | `isWaterOver` | 直接映射 | 是否超计划 |
| `ItemInvoiceError` | `itemInvoiceError` | 直接映射 | 明细开票错误 |
| `ItemInvoiceState` | `itemInvoiceState` | 直接映射 | 明细开票状态 |
| `ItemInvoiceType` | `itemInvoiceType` | 直接映射 | 明细开票类型 |
### 5.3 第一批次对 `AT_CHARGE_DETAILS` 的直接建议
- 明细对象整体与 `ChargeDetailDO` 对应度较高,可以直接进入第一批次。
- 主要需要额外确认的点:
- `PriceDetailId -> costAdjustmentId` 是否语义等价
- `PriceItemId -> costComponentCode` 的编码转换规则
## 6. `CT_ACCOUNTS -> biz_account` 细化映射
### 6.1 主键与关系
| 旧字段 | 新字段/位置 | 策略 | 备注 |
| --- | --- | --- | --- |
| `AccountId` | `biz_account.id` | 直接承接或映射保留 | 是否复用旧主键由脚本策略决定 |
| `AccountId` | `legacy_account_mapping.legacyId` | 必填 | 账户迁移主线 |
| 原账户编号 | `legacy_account_mapping.legacyBizNo` | 选填 | 若旧系统无独立业务账户号,可用主键兼任 |
| `CustId` | `biz_account.custId` | 直接映射 | 账户与客户关系核心字段 |
### 6.2 核心业务字段
| 旧字段 | 新字段 | 策略 | 备注 |
| --- | --- | --- | --- |
| `Deposit` | `deposit` | 直接映射 | 预存余额 |
| `UnCheckMoney` | `uncheckMoney` | 直接映射 | 未到账金额 |
| `Overdraft` | `overdraft` | 直接映射 | 透支额度 |
| `AccountState` | `status` | 状态转换 | 旧账户状态需与新 `status` 枚举核对 |
### 6.3 第一批次对 `CT_ACCOUNTS` 的直接建议
- `CT_ACCOUNTS``AccountDO` 的字段匹配度较高,可以直接进入第一批次。
- 关键点不在字段缺失,而在于:
- `AccountState -> status` 的枚举值核对
- 账户与客户主数据的一致性校验
## 7. 第一批次试迁时必须同步落库的映射对象
建议至少同时生成以下映射记录:
| 映射对象 | 作用 |
| --- | --- |
| `legacy_charge_mapping` | 保留旧账单到新账单关系 |
| `legacy_charge_detail_mapping` | 保留旧账单明细到新明细关系 |
| `legacy_account_mapping` | 保留旧账户到新账户关系 |
每条映射记录最少包括:
- `legacyTable`
- `legacyId`
- `legacyBizNo`
- `targetDomain`
- `targetId`
- `targetBizNo`
- `migrationBatchNo`
- `mappingStatus`
## 8. 第一批次最小脚本出口条件
在进入实际试迁脚本开发前,第一批次至少应满足:
- `AT_CHARGES` 字段映射规则已稳定
- `AT_CHARGE_DETAILS` 字段映射规则已稳定
- `CT_ACCOUNTS` 字段映射规则已稳定
- 三类主对象的状态映射规则已确认
- 三类主对象的标识映射结构已确认
## 9. 当前仍待补的小项
本版之后,第一批次还建议继续补两项:
1. `PriceListId / PriceCode / PriceItemId` 的编码转换字典
2. `PayState / FeeState / AccountState` 的枚举值对照表

View File

@ -0,0 +1,140 @@
# Matrix: REV-004 旧字段到新字段映射矩阵 v1
## 1. 说明
本矩阵用于在“旧对象 -> 新对象”映射基础上,进一步明确关键字段如何承接、转换和保留。
本版只覆盖迁移中最关键的字段:
- 主键与业务标识
- 账期与客户标识
- 金额 / 水量 / 滞纳金
- 退款 / 冲正 / 坏账相关核心字段
- 账户余额与流水字段
- 发票状态与票据标识字段
- 历史追溯必须保留的流程痕迹字段
## 2. 字段级承接策略说明
- `direct`:可直接映射到现有新字段
- `transform`:需要转换、重算或状态归并
- `mapping-only`:不进入在线主对象,仅写入映射层
- `readonly-retain`:只在历史只读层保留
- `tbd`:当前需要进一步核对 backend 真实字段或实现
## 3. 字段映射矩阵
| 旧表 | 旧字段 | 字段语义 | 新对象 | 新字段 / 承接位置 | 承接策略 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
| `AT_CHARGES` | `FeeId` | 账单主键 | `ChargeAggregate` | `biz_charge.id` + `legacy_charge_mapping.legacyId` | `direct` + `mapping-only` | 新旧账单主键必须双保留 |
| `AT_CHARGES` | `BillMonth` | 账务年月 | `ChargeAggregate` | `biz_charge.billMonth` | `direct` | 核心账期字段直接承接 |
| `AT_CHARGES` | `CustId` | 客户 ID | `ChargeAggregate` | `biz_charge.custId` | `direct` | 与客户主数据联动 |
| `AT_CHARGES` | `CustCode` | 客户编号 | `ChargeAggregate` | `biz_charge.custCode` | `direct` | 用于查询和迁移验收 |
| `AT_CHARGES` | `BillWater` | 开账水量 | `ChargeAggregate` | `biz_charge.billWater` | `direct` | 核心业务量字段 |
| `AT_CHARGES` | `BillAmount` | 开账金额 | `ChargeAggregate` | `biz_charge.billAmount` | `direct` | 与应收金额共同校验 |
| `AT_CHARGES` | `ExtendedAmount` | 应收金额 | `ChargeAggregate` | `biz_charge.extendedAmount` | `direct` | 迁移金额核对核心字段 |
| `AT_CHARGES` | `LateFee` | 滞纳金 | `ChargeAggregate` | `biz_charge.lateFee` | `direct` | 需与减免对象联动核查 |
| `AT_CHARGES` | `PayState` | 收费状态 | `ChargeAggregate` | `biz_charge.payState` + 状态映射表 | `transform` | 需与新收费状态语义核对 |
| `AT_CHARGES` | `InvoiceState` | 开票状态 | `ChargeAggregate` / `InvoiceRelation` | `biz_charge.invoiceState` + `legacy_invoice_mapping` | `transform` | 账单侧状态与发票主对象需联动校验 |
| `AT_CHARGES` | `ParentFeeId` | 调整原始 FeeId | `CompatibilityMappingRecord` | `legacy_charge_mapping.sourceFeeId` | `mapping-only` | 是迁移后追溯原账单的关键字段 |
| `AT_CHARGES` | `ContrastFeeId` | 对应费用编号 | `CompatibilityMappingRecord` | `legacy_charge_mapping.relatedFeeId` | `mapping-only` | 用于比对调整前后关系 |
| `AT_CHARGES` | `AdjustType` | 调整类型 | `AccountingEvidence` / 映射层 | `tracePayload.adjustType` | `transform` | 当前新模型统一挂场景,不建议原样主表承接 |
| `AT_CHARGES` | `RefundWater` | 退补水量 | `AccountingEvidence` | `tracePayload.refundWater` | `mapping-only` | 与退款/调整场景相关,保留追溯值 |
| `AT_CHARGE_DETAILS` | `FeeId` | 账单外键 | `ChargeAggregate` | `biz_charge_detail.chargeId` | `direct` | 必须保持主明细关系 |
| `AT_CHARGE_DETAILS` | `PriceItemId` | 费用组成 | `ChargeAggregate` | `biz_charge_detail.priceItemId` | `direct` | 核心费用项映射 |
| `AT_CHARGE_DETAILS` | `Water` | 应收水量 | `ChargeAggregate` | `biz_charge_detail.water` | `direct` | 明细层水量 |
| `AT_CHARGE_DETAILS` | `Money` | 明细金额 | `ChargeAggregate` | `biz_charge_detail.money` | `direct` | 明细层金额 |
| `AT_CHARGE_DETAILS` | `InvoicedState` | 明细开票状态 | `InvoiceRelation` / 映射层 | `legacy_invoice_mapping.detailInvoiceState` | `mapping-only` | 明细开票状态优先保留关系层 |
| `CT_ACCOUNTS` | `AccountId` | 账户主键 | `AccountBalance` | `biz_account.id` + `legacy_account_log_mapping.accountLegacyId` | `direct` + `mapping-only` | 账户主键双保留 |
| `CT_ACCOUNTS` | `CustId` | 客户 ID | `AccountBalance` | `biz_account.custId` | `direct` | 账户与客户绑定核心字段 |
| `CT_ACCOUNTS` | `Deposit` | 预存余额 | `AccountBalance` | `biz_account.deposit` | `direct` / `tbd` | 需最终确认 backend 账户表字段命名 |
| `CT_ACCOUNTS` | `UnCheckMoney` | 未到账金额 | `AccountBalance` | `biz_account.unCheckMoney` 或扩展字段 | `tbd` | 需确认新系统是否有对应字段 |
| `CT_ACCOUNTS` | `Overdraft` | 透支额度 | `AccountBalance` | `biz_account.overdraft` 或扩展字段 | `tbd` | 需核实现有表结构 |
| `CT_ACCOUNT_LOGS` | `AccLogId` | 账户流水主键 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.legacyId` | `mapping-only` | 流水主键必须可追溯 |
| `CT_ACCOUNT_LOGS` | `PayDetailId` | 来源编号 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.sourceDetailId` | `mapping-only` | 用于关联收费或退款来源 |
| `CT_ACCOUNT_LOGS` | `AccLogType` | 暂收类型 | `HistoricalReadonlyRecord` | `legacy_account_log_mapping.legacyType` | `readonly-retain` | 新模型当前未显式在线承接该分类 |
| `CT_ACCOUNT_LOGS` | `AccInOut` | 进出标志 | `HistoricalReadonlyRecord` | `legacy_account_log_mapping.inOutFlag` | `readonly-retain` | 作为账户流水查询语义保留 |
| `CT_ACCOUNT_LOGS` | `LastDeposit` | 上次余额 | `HistoricalReadonlyRecord` | `summarySnapshot.lastDeposit` | `readonly-retain` | 用于迁移验收对账 |
| `CT_ACCOUNT_LOGS` | `InOutMoney` | 收支金额 | `HistoricalReadonlyRecord` / `Transaction` | `summarySnapshot.inOutMoney` | `readonly-retain` / `tbd` | 需结合新交易对象最终判定 |
| `CT_ACCOUNT_LOGS` | `Deposit` | 本次余额 | `HistoricalReadonlyRecord` | `summarySnapshot.deposit` | `readonly-retain` | 用于余额核对 |
| `PM_ACCOUNT_RECORD_DETAILS` | `CustId` | 客户 ID | `AccountingEvidence` | `tracePayload.custId` | `mapping-only` | 退款详情应保留客户维度 |
| `PM_ACCOUNT_RECORD_DETAILS` | `RefundDeposit` | 退款金额 | `AccountingEvidence` / `Transaction` | `tracePayload.refundAmount` + 交易对象金额 | `transform` | 核心退款金额,需和新交易金额对齐 |
| `PM_ACCOUNT_RECORD_DETAILS` | `Deposit` | 原预存金额 | `AccountingEvidence` | `tracePayload.originalDeposit` | `mapping-only` | 用于退款前后余额审计 |
| `PM_ACCOUNT_RECORD_DETAILS` | `AccountLogId` | 被调整流水记录 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.sourceAccountLogId` | `mapping-only` | 退款场景断链风险最高字段之一 |
| `PM_ACCOUNT_RECORD_DETAILS` | `TargetAccountLogId` | 目标流水记录 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.targetAccountLogId` | `mapping-only` | 用于转退款 / 转销账链路追溯 |
| `PM_ACCOUNT_RECORD_DETAILS` | `ProcType` | 处理方式 | `AccountingResult` / 映射层 | `tracePayload.procType` | `transform` | 当前新模型只保留统一结果位,原处理方式需保留映射 |
| `PM_AMOUNT_RECORD_DETAILS` | `FeeId` | 原账单 ID | `CompatibilityMappingRecord` | `legacy_charge_mapping.sourceFeeId` | `mapping-only` | 调整前账单引用 |
| `PM_AMOUNT_RECORD_DETAILS` | `NewFeeId` | 新账单 ID | `CompatibilityMappingRecord` | `legacy_charge_mapping.targetFeeId` | `mapping-only` | 调整后账单引用 |
| `PM_AMOUNT_RECORD_DETAILS` | `ExtendedAmount` | 调整前账单金额 | `AccountingEvidence` | `tracePayload.beforeExtendedAmount` | `mapping-only` | 核心差异字段 |
| `PM_AMOUNT_RECORD_DETAILS` | `NewExtendedAmount` | 调整后账单金额 | `AccountingEvidence` | `tracePayload.afterExtendedAmount` | `mapping-only` | 核心差异字段 |
| `PM_AMOUNT_RECORD_DETAILS` | `BillWater` | 调整前开账水量 | `AccountingEvidence` | `tracePayload.beforeBillWater` | `mapping-only` | 水量调整场景核心字段 |
| `PM_AMOUNT_RECORD_DETAILS` | `NewBillWater` | 调整后开账水量 | `AccountingEvidence` | `tracePayload.afterBillWater` | `mapping-only` | 水量调整场景核心字段 |
| `PM_AMOUNT_RECORD_DETAILS` | `Reading` | 原底码 | `AccountingEvidence` | `tracePayload.beforeReading` | `mapping-only` | 旧抄表依据必须保留 |
| `PM_AMOUNT_RECORD_DETAILS` | `NewReading` | 新底码 | `AccountingEvidence` | `tracePayload.afterReading` | `mapping-only` | 调整后依据必须保留 |
| `PM_PAYMENT_RECORD_DETAILS` | `FeeId` | 原账单 ID | `CompatibilityMappingRecord` | `legacy_charge_mapping.sourceFeeId` | `mapping-only` | 已销调整/冲正场景核心引用 |
| `PM_PAYMENT_RECORD_DETAILS` | `NewFeeId` | 新账单 ID | `CompatibilityMappingRecord` | `legacy_charge_mapping.targetFeeId` | `mapping-only` | 如有重构账单必须保留 |
| `PM_PAYMENT_RECORD_DETAILS` | `ActualMoney` | 实收金额 | `Transaction` | `bk_transaction.amount` 或扩展金额字段 | `transform` | 需与原交易金额比对 |
| `PM_PAYMENT_RECORD_DETAILS` | `DeductionAmount` | 抵扣金额 | `AccountingEvidence` | `tracePayload.deductionAmount` | `mapping-only` | 当前新模型未见统一在线字段 |
| `PM_PAYMENT_RECORD_DETAILS` | `AccountLogId` | 被调整流水记录 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.sourceAccountLogId` | `mapping-only` | 冲正场景关键追溯字段 |
| `PM_PAYMENT_RECORD_DETAILS` | `TargetAccountLogId` | 目标流水记录 | `CompatibilityMappingRecord` | `legacy_account_log_mapping.targetAccountLogId` | `mapping-only` | 冲正后目标记录追溯 |
| `PM_KNOTTY_RECORD_DETAILS` | `FeeId` | 账单 ID | `CompatibilityMappingRecord` | `legacy_charge_mapping.sourceFeeId` | `mapping-only` | 坏账与账单关系必须保留 |
| `PM_KNOTTY_RECORD_DETAILS` | `BillMonth` | 账务年月 | `AccountingEvidence` | `tracePayload.billMonth` | `mapping-only` | 用于坏账账龄与核对 |
| `PM_KNOTTY_RECORD_DETAILS` | `ExtendedAmount` | 账单应收金额 | `AccountingEvidence` | `tracePayload.extendedAmount` | `mapping-only` | 与坏账金额核对 |
| `PM_KNOTTY_RECORD_DETAILS` | `ProcType` | 处理方式 | `AccountingResult` / 映射层 | `tracePayload.procType` | `transform` | 坏账类型和结果需映射 |
| `IV_INVOICE_INFOS` | `Id` | 发票主键 | `InvoiceRecord` | `biz_invoice.id` + `legacy_invoice_mapping.legacyInvoiceId` | `direct` + `mapping-only` | 发票主键双保留 |
| `IV_INVOICE_INFOS` | `FeeId` | 费用 ID | `InvoiceRelation` | `legacy_invoice_mapping.sourceFeeId` | `mapping-only` | 账单与发票主关系核心字段 |
| `IV_INVOICE_INFOS` | `SerialNo` | 交易流水号 | `InvoiceRecord` / `Transaction` | `biz_invoice.sysRequestNo` 或关系映射 | `transform` | 需与当前发票协同请求号核对 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 发票状态 | `InvoiceRecord` | `biz_invoice.invoiceState` + 状态映射表 | `transform` | 必做旧状态到新状态映射 |
| `IV_INVOICE_INFOS` | `InvoiceCode` | 发票代码 | `InvoiceRecord` | `biz_invoice.invoiceCode` | `direct` | 核心票据标识 |
| `IV_INVOICE_INFOS` | `InvoiceNumber` | 发票号码 | `InvoiceRecord` | `biz_invoice.invoiceNumber` | `direct` | 核心票据标识 |
| `IV_INVOICE_INFOS` | `InvoiceDate` | 发票日期 | `InvoiceRecord` | `biz_invoice.invoiceDate` | `direct` | 核心票据时间 |
| `IV_INVOICE_INFOS` | `InvoicePath` | 电子票地址 | `InvoiceRecord` | `biz_invoice.fileUrl` 或等价字段 | `transform` | 需核对当前 DO/VO 字段名 |
| `IV_INVOICE_INFOS` | `CndnCode` | 原发票代码 | `InvoiceRecord` | `biz_invoice.originalInvoiceCode` 或扩展字段 | `tbd` | 当前新系统存在类似字段,但需最终核实命名 |
| `IV_INVOICE_INFOS` | `CndnNumber` | 原发票号码 | `InvoiceRecord` | `biz_invoice.originalInvoiceNumber` 或扩展字段 | `tbd` | 与红冲、作废场景强关联 |
| `IV_INVOICE_INFOS` | `LastTryTime` | 最后查询时间 | `InvoiceRecord` | `biz_invoice.lastTryTime` | `direct` | 当前 REV-005 已引入类似重试字段 |
| `IV_INVOICE_INFOS` | `NextTryTime` | 下一次查询时间 | `InvoiceRecord` | `biz_invoice.nextTryTime` | `direct` | 当前 REV-005 已引入类似重试字段 |
| `IV_INVOICE_INFOS` | `TryCount` | 查询次数 | `InvoiceRecord` | `biz_invoice.tryCount` | `direct` | 当前 REV-005 已引入类似字段 |
## 4. 当前 v1 的直接落地建议
### 4.1 可直接进入字段级迁移设计的对象
- `AT_CHARGES`
- `AT_CHARGE_DETAILS`
- `IV_INVOICE_INFOS`
这些对象的核心字段与当前新模型最接近,可以优先进入试迁字段设计。
### 4.2 必须补“映射层字段”的对象
- `CT_ACCOUNT_LOGS`
- `PM_ACCOUNT_RECORD_DETAILS`
- `PM_AMOUNT_RECORD_DETAILS`
- `PM_PAYMENT_RECORD_DETAILS`
- `PM_KNOTTY_RECORD_DETAILS`
这些对象的关键价值不在于在线主字段一一映射,而在于:
- 原账单引用
- 原流水引用
- 前后差异值
- 处理方式
- 原始处理痕迹
### 4.3 当前仍需进一步核实的字段
以下字段建议进入下一轮字段核实清单:
- `CT_ACCOUNTS.Deposit / UnCheckMoney / Overdraft` 在当前 backend 的精确落点
- `PM_PAY_DETAILS.ActualMoney``bk_transaction*` 的最终字段承接方式
- `IV_INVOICE_INFOS.SerialNo` 与当前发票协同请求号 / 受理号的对应关系
- `IV_INVOICE_INFOS.CndnCode / CndnNumber` 在当前新模型中的正式字段名
## 5. 后续动作
后续建议继续补两张矩阵:
1. `旧状态 -> 新状态` 映射矩阵
2. `新旧标识` 映射矩阵
字段级矩阵完成后,才适合正式设计迁移脚本的字段转换逻辑。

View File

@ -0,0 +1,42 @@
# Contract: REV-004 功能缺失判定矩阵
## 1. 目标
本合同用于统一判定旧账务对象相对当前 backend 的承接成熟度,避免将“旧表缺失”直接误判为“功能缺失”。
## 2. 判定字段
| Field | Required | Description |
| --- | --- | --- |
| `legacyObjectName` | Yes | 旧对象名称 |
| `legacyTable` | Yes | 旧表名 |
| `expectedCapability` | Yes | 旧对象期望承载的业务能力 |
| `currentVerdict` | Yes | `implemented` / `partial` / `readonly` / `missing` |
| `evidenceType` | Yes | `controller` / `service` / `do` / `mapping-doc` / `none` |
| `evidencePath` | Yes | 证据路径或文档路径 |
| `whyNotFullyImplemented` | No | 为什么不能判定为完全实现 |
| `recommendedHandling` | Yes | `reuse` / `extend` / `readonly-retain` / `implement-later` |
| `migrationImpact` | Yes | 对迁移的影响:低 / 中 / 高 |
| `notes` | No | 其他补充说明 |
## 3. 判定规则
- `implemented`:当前 backend 已有稳定在线能力,且核心业务语义可被当前对象直接承接。
- `partial`:已有部分能力、控制器、服务或数据对象,但无法完整表达旧模型全部语义。
- `readonly`:不建议在线重建,只要求查询、比对和追溯能力。
- `missing`:当前未看到稳定承接能力或映射出口,后续需补设计或开发。
## 4. 使用约束
- 不允许仅凭“未看到同名表”判定为 `missing`
- 必须优先核对统一场景入口、服务逻辑、日志承接和历史只读口径。
- 对于旧审批流相关对象,若当前只保留审批能力位,可优先判为 `readonly``partial`,而不是默认 `missing`
## 5. 示例
| legacyObjectName | currentVerdict | evidencePath | recommendedHandling | 说明 |
| --- | --- | --- | --- | --- |
| 营业账 | implemented | `ChargeDO.java` / `biz_charge` | reuse | 已有主对象承接 |
| 预存退款详情 | partial | `ChargeServiceImpl.adjustAccounting` + `bk_transaction*` | extend | 退款语义已有,旧细明台账未一对一落地 |
| 账单-呆坏账详情 | partial | `ChargeServiceImpl.adjustAccounting` | extend | 坏账申请语义已有,但旧细表未独立承接 |
| 特账明细 | missing | `BACKEND_TABLE_MAPPING.md` | implement-later or readonly-retain | 当前未见稳定承接 |

View File

@ -0,0 +1,104 @@
# Matrix: REV-004 新旧标识映射矩阵 v1
## 1. 说明
本矩阵用于定义迁移后必须保留的新旧标识关系,确保以下能力不丢失:
- 迁移验收对账
- 历史明细追溯
- 审计与问题定位
- 旧单据到新业务对象的关系恢复
如果没有这张矩阵,即使数据迁过去,后续也很容易出现“查得到结果,但找不回原单据链”的问题。
## 2. 标识映射原则
- 每一类核心业务对象都必须保留“旧主键 + 旧业务单号 + 新主键 + 新业务单号”的最小映射能力。
- 对于账单、流水、申请单、发票号等强业务标识,不允许只保留新值不保留旧值。
- 若新系统不存在完全等价的新业务单号,至少保留新主键和映射批次号。
- 新旧标识映射对象必须可按批次、对象类型和映射状态查询。
## 3. 映射矩阵
| 旧对象 | 旧主键 | 旧业务标识 | 新对象 | 新主键 | 新业务标识 | 映射承接层 | 最低保留要求 | 说明 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 营业账 | `FeeId` | 原账单号 / 历史账单编号 | `biz_charge` | `chargeId` | 新账单号或业务编号 | `mapping-layer` | `legacyId``targetId``legacyBizNo``targetBizNo` | 账单是迁移核查主线之一 |
| 营业账明细 | `Id` 或明细主键 | 无统一业务单号时可为空 | `biz_charge_detail` | `chargeDetailId` | 可选 | `mapping-layer` | `legacyId``targetId``sourceFeeId` | 明细至少能挂回主账单 |
| 账户 | `AccountId` | 原账户编号 | 账户承接对象 | `accountId` | 新账户编号 | `mapping-layer` | `legacyId``targetId``legacyBizNo``targetBizNo` | 预存余额迁移核查主线 |
| 账户流水 | `AccLogId` | 原流水号 / 原暂收流水号 | 账户流水承接对象或只读映射 | `accountLogId` 或映射主键 | 新流水号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetId/targetBizNo` | 退款、冲正、转预存必须依赖该链路 |
| 收费汇总 | `CollectId` | 结账批次号 / 汇总编号 | 收费汇总承接对象 | `collectId` | 新汇总编号 | `mapping-layer` + `history-readonly` | `legacyId``targetId`、批次号 | 汇总核对主线 |
| 收费小计 | `SubtotalId` | 原小计编号 | 小计承接对象或只读映射 | `subtotalId` | 新小计编号 | `history-readonly` | `legacyId``targetId` | 一般不作为主业务单号,但需追溯 |
| 收费明细 | `DetailId` | `TradeCode` / `ThirdPartyNum` / 第三方流水号 | 交易对象 / 收费明细承接对象 | `transactionId` 或映射主键 | 新交易流水号 | `mapping-layer` | `legacyId``legacyBizNo``targetId``targetBizNo` | 收费、退款、冲正核查关键链路 |
| 退款账 | `RefundId` | 原退款单号 | 退款场景映射对象 | 映射主键 | 新调整单号 / 新退款单号 | `mapping-layer` | `legacyId``targetBizNo` | 若新系统无独立退款主键,至少保留新业务单号 |
| 预存退款汇总 | `Id` | 原申请单号 | 退款申请映射对象 | 映射主键 | 新调整单号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetBizNo` | 旧申请单是历史审批追溯主线 |
| 预存退款详情 | `Id` | 原详情单号 | 退款明细映射对象 | 映射主键 | 新明细引用 | `mapping-layer` + `history-readonly` | `legacyId``sourceAccountLogId``targetAccountLogId` | 重点保留原流水到目标流水关系 |
| 调整减免汇总 | `Id` | 原调整申请单号 | 调整场景映射对象 | 映射主键 | 新调整单号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetBizNo` | 金额/水量调整的历史入口 |
| 调整减免明细 | `Id` | 无统一业务单号 | 调整明细映射对象 | 映射主键 | 新账单引用 / 新明细引用 | `mapping-layer` | `legacyId``sourceFeeId``targetFeeId` | 前后账单链必须保留 |
| 价差调整汇总 | `Id` | 原价差调整申请单号 | 价差调整映射对象 | 映射主键 | 新调整单号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetBizNo` | 若不单独在线化,也必须保留申请号映射 |
| 已销调整汇总 | `Id` | 原已销调整申请单号 | 冲正/调整映射对象 | 映射主键 | 新调整单号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetBizNo` | 与收费结果链强关联 |
| 坏账汇总 | `Id` | 原坏账申请单号 | 坏账申请映射对象 | 映射主键 | 新调整单号 / 新申请单号 | `mapping-layer` + `history-readonly` | `legacyId``legacyBizNo``targetBizNo` | 坏账审批与生效查询主线 |
| 坏账明细 | `Id` | 无统一业务单号 | 坏账明细映射对象 | 映射主键 | 新账单引用 | `mapping-layer` | `legacyId``sourceFeeId` | 保证坏账记录能追到原账单 |
| 发票主表 | `Id` / `InvoiceInfoId` | `InvoiceCode + InvoiceNumber` / `OrderNo` / `SerialNo` | `biz_invoice` | `invoiceId` | 新申请单号 / 新受理号 / 新发票号 | `mapping-layer` | `legacyId``legacyBizNo``targetId``targetBizNo` | 发票查询、补打和对账主线 |
| 发票明细 | `Id` | 无统一业务单号 | 发票明细承接对象或只读映射 | `invoiceDetailId` | 可选 | `mapping-layer` + `history-readonly` | `legacyId``targetId``invoiceId` | 明细至少挂回发票主对象 |
| 营业账开票映射 | `Id` | 账单号 + 发票号组合 | 发票关系映射对象 | 映射主键 | 新账单号 + 新发票号组合 | `mapping-layer` | `sourceFeeId``targetChargeId``legacyInvoiceNo``targetInvoiceNo` | 发票关系迁移验收的核心对象 |
## 4. 映射记录建议字段
建议统一的标识映射记录至少包含:
| Field | Description |
| --- | --- |
| `mappingId` | 映射记录主键 |
| `legacySystem` | 原系统标识 |
| `legacyTable` | 旧表名 |
| `legacyId` | 旧主键 |
| `legacyBizNo` | 旧业务单号 / 流水号 / 发票号 |
| `targetDomain` | 新领域名称 |
| `targetId` | 新主键 |
| `targetBizNo` | 新业务单号 / 受理号 / 发票号 |
| `sourceLegacyId` | 源旧主键(用于前后关系对象) |
| `sourceTargetId` | 源新主键 |
| `mappingStatus` | `planned / migrated / verified / failed` |
| `migrationBatchNo` | 迁移批次号 |
| `verifiedAt` | 校验时间 |
| `remark` | 备注 |
## 5. 当前 v1 的直接结论
### 5.1 必须优先落映射的主线标识
以下标识是迁移最容易断链、也最必须优先保留的:
- `FeeId`
- `AccLogId`
- 收费流水号 / 第三方流水号
- 旧调整申请单号
- 旧坏账申请单号
- 发票代码 + 发票号码
- 发票申请单号 / 订单号 / 受理号
### 5.2 最容易被忽略但必须保留的关系标识
- `ParentFeeId`
- `ContrastFeeId`
- `AccountLogId`
- `TargetAccountLogId`
- 账单与发票映射关系中的组合标识
这些字段如果不在迁移时显式保留,后续几乎无法恢复“调整前后”“退款前后”“原交易与后续交易”“原账单与新账单”的链路。
### 5.3 对后续脚本设计的直接约束
迁移脚本设计时,不允许只写“插入新表”逻辑,还必须同步写:
1. 标识映射入库逻辑
2. 关系标识补链逻辑
3. 批次号和映射状态回写逻辑
## 6. 后续动作
在对象、字段、状态、标识四张矩阵都具备后,下一步建议进入:
1. 试迁校验清单
2. 差异分类与复迁规则
3. 批次化执行与回滚方案

View File

@ -0,0 +1,55 @@
# Contract: REV-004 旧账务迁移映射矩阵
## 1. 目标
本合同用于统一旧账务对象到新系统承接对象的映射矩阵结构,供后续迁移实施、校验和差异定位复用。
## 2. 映射矩阵字段
| Field | Required | Description |
| --- | --- | --- |
| `legacyTable` | Yes | 旧表名 |
| `legacyObjectName` | Yes | 旧对象名称 |
| `legacyPrimaryKey` | Yes | 旧主键字段 |
| `legacyBusinessKey` | No | 旧业务单号 / 流水号 / 外部单号 |
| `legacyMeaning` | Yes | 业务语义说明 |
| `targetDomain` | Yes | 新承接领域 |
| `targetCarrier` | Yes | 新承接物理对象或只读对象 |
| `carrierLayer` | Yes | `online-main` / `mapping-layer` / `history-readonly` |
| `mappingType` | Yes | `one-to-one` / `many-to-one` / `one-to-many` / `readonly-only` |
| `statusMappingRequired` | Yes | 是否需要状态映射 |
| `identifierMappingRequired` | Yes | 是否需要新旧标识映射 |
| `historicalRetentionRequired` | Yes | 是否必须保留历史只读 |
| `evidenceSource` | Yes | backend / doc / archive 证据来源 |
| `migrationAction` | Yes | `migrate-online` / `retain-readonly` / `mapping-only` / `implement-later` |
| `riskNote` | No | 风险说明 |
## 3. 填写规则
- 不允许只填写旧表名,不说明业务语义。
- 每个旧对象必须明确进入哪个承接层。
- 若 `carrierLayer = online-main`,必须明确在线主模型对象和主键映射规则。
- 若 `carrierLayer = history-readonly`,必须明确最小查询维度和原始标识保留要求。
- 若 `mappingAction = implement-later`,必须附功能缺失理由和后续建议。
## 4. 最小保留字段要求
对于需要迁移或只读保留的旧对象,最少保留以下类型字段:
- 原单据标识
- 原账单标识
- 原流水标识
- 处理类型
- 处理原因
- 处理前后金额或水量
- 申请时间、审批时间、生效时间
- 经办人
- 附件或依据引用
## 5. 示例判定
| legacyObjectName | targetDomain | carrierLayer | migrationAction | 说明 |
| --- | --- | --- | --- | --- |
| 预存退款 | AccountingRequest / Transaction | online-main + history-readonly | migrate-online | 核心退款结果需在线承接,旧审批细节可只读保留 |
| 价差调整明细 | ChargeAggregate / OperationLog | online-main + mapping-layer | migrate-online | 调整结果进入账单主对象,旧明细字段保留映射 |
| 特账明细 | HistoricalReadonlyRecord | history-readonly | retain-readonly | 当前未见稳定在线承接能力 |

View File

@ -0,0 +1,80 @@
# Matrix: REV-004 旧表到新对象映射矩阵 v1
## 1. 说明
本矩阵用于回答三个问题:
1. 旧表在新系统中由谁承接。
2. 该承接属于在线主模型、兼容映射层还是历史只读层。
3. 当前应判定为复用、扩展、只读保留还是后续补实现。
本矩阵是 v1只覆盖 `REV-004` 迁移中最核心的账单、账户、收费、调整、退款、坏账、发票对象。
## 2. 承接层定义
- `online-main`:进入新系统在线主模型,后续继续参与业务处理。
- `mapping-layer`:不直接承载在线业务,但保存新旧标识、状态和关系映射。
- `history-readonly`:只提供查询、审计和迁移验收,不参与在线处理。
## 3. 映射矩阵
| 旧表名 | 旧对象名称 | 旧语义摘要 | 新承接领域 | 新承接对象 | 承接层 | 当前判定 | 建议动作 | 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `AT_CHARGES` | 营业账 | 账单主对象,承接账期、应收、收费、开票、调整痕迹 | `ChargeAggregate` | `biz_charge` | `online-main` | `implemented` | `reuse` | 新系统账单主表核心承接对象 |
| `AT_CHARGE_DETAILS` | 营业账明细 | 账单费用组成、水量、金额、开票状态 | `ChargeAggregate` | `biz_charge_detail` | `online-main` | `implemented` | `reuse` | 明细层已存在稳定承接对象 |
| `CT_ACCOUNTS` | 账户信息 | 预存余额、未到账金额、透支额度 | `AccountBalance` | `biz_account` / 账户相关对象 | `online-main` | `partial` | `extend` | 需进一步确认与旧 `CT_ACCOUNTS` 字段对齐程度 |
| `CT_ACCOUNT_LOGS` | 账户流水 | 预存进出、余额变化、暂收流水 | `AccountTransaction` | `legacy_account_log_mapping` + 账户流水承接对象 | `mapping-layer` + `history-readonly` | `partial` | `extend` | 当前 `REV-004` 正式主口径未把账户流水建成核心在线对象 |
| `PM_PAY_COLLECTS` | 收费汇总 | 结账周期、收费总笔数、总金额、柜员/网点汇总 | `CollectionSummary` | `biz_collection` + 汇总查询结果 | `online-main` + `history-readonly` | `partial` | `extend` | 汇总语义可承接,旧统计台账宜只读保留 |
| `PM_PAY_SUBTOTALS` | 收费小计 | 按渠道、收费类型、收退标识的小计结果 | `CollectionSummary` | `biz_collection` + 汇总查询结果 | `history-readonly` | `readonly` | `retain-readonly` | 不建议为旧小计表单独在线重建 |
| `PM_PAY_DETAILS` | 收费明细 | 实收、实销、滞纳金、红冲关联、收费员 | `Transaction` / `CollectionDetail` | `bk_transaction*` + 收费结果关系 | `online-main` + `mapping-layer` | `partial` | `extend` | 需补旧收费明细到新交易对象的映射关系 |
| `AT_REFUNDS` | 退款账 | 退款金额、退款状态、退款时间、退款人 | `AccountingRequest` / `Transaction` | `IF-REV-007` 退款场景 + `bk_transaction*` | `online-main` + `mapping-layer` | `partial` | `extend` | 退款语义已有,旧退款账未见独立在线对象 |
| `PM_ACCOUNT_RECORDS` | 预存退款汇总 | 预存退款申请单、申请人、流程审批信息 | `AccountingRequest` | `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 在线保留申请语义,旧审批字段按只读保留 |
| `PM_ACCOUNT_RECORD_DETAILS` | 预存退款详情 | 客户、退款金额、原流水、目标流水、处理方式 | `AccountingEvidence` / `Transaction` | `legacy_finance_record_mapping` + `bk_transaction*` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 需要重点保留原流水、目标流水和退款金额映射 |
| `PM_AMOUNT_RECORDS` | 调整减免汇总 | 水量/金额调整申请、原因、流程审批 | `AccountingRequest` | `IF-REV-007` 调整场景 + `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 新系统已有统一入口,但旧申请单结构未一对一承接 |
| `PM_AMOUNT_RECORD_DETAILS` | 调整减免明细 | 原账单、新账单、前后金额/水量、底码、累积量变化 | `AccountingEvidence` / `ChargeAggregate` | `biz_charge` / `biz_charge_detail` + 映射层 | `online-main` + `mapping-layer` | `partial` | `extend` | 关键是保留前后差异和新旧账单关联 |
| `PM_PRICE_RECORDS` | 价差调整汇总 | 调价差额修正申请、调价号、原因、审批 | `AccountingRequest` | `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 汇总申请宜按迁移映射保留 |
| `PM_PRICE_RECORD_DETAILS` | 价差调整明细 | 原账单、新账单、调价前后金额、滞纳金差额 | `ChargeAggregate` / `AccountingEvidence` | `biz_charge` / `biz_charge_detail` + 映射层 | `online-main` + `mapping-layer` | `partial` | `extend` | 语义已部分被账单重算能力承接 |
| `PM_PAYMENT_RECORDS` | 已销调整汇总 | 已收费后修正申请单、原因、流程 | `AccountingRequest` | `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 不建议按旧汇总表在线重建 |
| `PM_PAYMENT_RECORD_DETAILS` | 已销调整明细 | 原账单、新账单、实收金额、抵扣金额、退款人员 | `AccountingEvidence` / `Transaction` | `IF-REV-007` 冲正场景 + `bk_transaction*` + 映射层 | `online-main` + `mapping-layer` | `partial` | `extend` | 与冲正、退款、原交易校验强相关 |
| `PM_KNOTTY_RECORDS` | 呆坏账汇总 | 坏账申请单、申请原因、审批流程 | `AccountingRequest` | `IF-REV-007` 坏账场景 + `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `partial` | `extend` | 坏账申请语义已有,旧流程单需映射保留 |
| `PM_KNOTTY_RECORD_DETAILS` | 呆坏账明细 | 账单、账期、坏账金额、处理人、处理时间 | `AccountingEvidence` / `AccountingResult` | `biz_charge` + `biz_operat_log*` + 映射层 | `online-main` + `mapping-layer` | `partial` | `extend` | 需保留账龄、原因、结果和核销状态 |
| `PM_LATEFEE_RECORDS` | 违约金减免汇总 | 滞纳金减免申请、原因、审批 | `AccountingRequest` | `legacy_finance_record_mapping` | `mapping-layer` + `history-readonly` | `readonly` | `retain-readonly` | 当前一期不建议独立在线化 |
| `PM_LATEFEE_RECORD_DETAILS` | 违约金减免明细 | 原滞纳金、减免金额、调整后滞纳金 | `AccountingEvidence` | `legacy_finance_record_mapping` | `history-readonly` | `readonly` | `retain-readonly` | 保留明细追溯即可 |
| `IV_INVOICE_INFOS` | 发票信息表 | 发票状态、发票号、票据结果、查询重试、原票关联 | `InvoiceRecord` | `biz_invoice*` | `online-main` | `partial` | `reuse` + `extend` | 发票主对象已有,但旧字段需逐项核对 |
| `IV_INVOICE_DETAIL_INFOS` | 发票明细表 | 商品项、税率、税额、数量、单价 | `InvoiceRecord` | `biz_invoice*` + 历史明细映射 | `online-main` + `history-readonly` | `partial` | `extend` | 视当前 `biz_invoice` 明细承接能力决定在线化粒度 |
| `IV_CHARGE_INVOICE_MAPPINGS` | 营业账合并开票映射 | 账单与发票的多对多关系 | `InvoiceRelation` | `legacy_invoice_mapping` + 账单发票关系承接 | `mapping-layer` | `partial` | `extend` | 是迁移验收和历史补打的重要关系层 |
## 4. 当前 v1 的直接判断
### 4.1 已有稳定在线承接基础的对象
- `AT_CHARGES`
- `AT_CHARGE_DETAILS`
### 4.2 已有业务语义承接,但需要扩展映射与历史只读能力的对象
- `CT_ACCOUNTS`
- `CT_ACCOUNT_LOGS`
- `PM_PAY_*`
- `AT_REFUNDS`
- `PM_ACCOUNT_RECORD*`
- `PM_AMOUNT_RECORD*`
- `PM_PRICE_RECORD*`
- `PM_PAYMENT_RECORD*`
- `PM_KNOTTY_RECORD*`
- `IV_*`
### 4.3 当前更适合历史只读保留的对象
- `PM_LATEFEE_RECORDS`
- `PM_LATEFEE_RECORD_DETAILS`
## 5. 基于 v1 的后续动作
后续至少继续补三张矩阵:
1. 旧字段到新字段映射矩阵
2. 旧状态到新状态映射矩阵
3. 新旧标识映射矩阵
在这三张矩阵完成前,不建议直接进入批量迁移脚本开发。

View File

@ -0,0 +1,100 @@
# Matrix: REV-004 旧状态到新状态映射矩阵 v1
## 1. 说明
本矩阵用于定义旧账务模型中的关键状态、处理方式和流程状态,如何映射到当前新系统对象中的状态表达。
本版重点覆盖:
- 账单收费状态
- 发票状态
- 退款 / 冲正 / 坏账相关状态
- 旧流程字段与新审批能力位的关系
## 2. 映射原则
- 不允许只保留旧状态值而不说明新状态语义。
- 若新系统无法一一表达旧状态,必须采用“新状态 + 扩展追溯字段”组合承接。
- 若旧状态只对历史查询有意义,不强制进入在线主状态机,但必须保留 `legacyState`
- 审批节点级状态本轮优先保留为历史只读,不在线复刻完整流转。
## 3. 状态映射矩阵
| 旧对象 | 旧字段 | 旧值/旧语义 | 新对象 | 新状态字段 | 新状态值/新语义 | 映射方式 | 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `AT_CHARGES` | `PayState` | 未收费 | `ChargeAggregate` | `payState` | 未收费 | `direct/transform` | 需与当前 backend 枚举最终对齐 |
| `AT_CHARGES` | `PayState` | 已收费 | `ChargeAggregate` | `payState` | 已收费 | `direct/transform` | 是退款/冲正前置条件 |
| `AT_CHARGES` | `PayState` | 坏账 | `ChargeAggregate` | `payState` | `UNCOLLECTIBLE` / 坏账 | `transform` | 当前 backend 已见 `PayStateEnum.UNCOLLECTIBLE(-2)` |
| `AT_CHARGES` | `FeeState` | 正常 / 作废 / 特定旧费用状态 | `ChargeAggregate` | `chargeState``legacyState` | 当前有效状态 / 历史状态 | `transform` + `readonly-retain` | 若新系统无完全等价字段,则保留 `legacyState` |
| `AT_CHARGES` | `InvoiceState` | 未开票 | `InvoiceRecord` / `ChargeAggregate` | `invoiceState` | 待开票 / 未开票 | `transform` | 账单侧与发票主对象需保持一致 |
| `AT_CHARGES` | `InvoiceState` | 开票中 / 查询中 | `InvoiceRecord` | `invoiceState` | `PENDING` / 处理中 | `transform` | 当前新模型发票结果采用异步申请 + 查询兜底 |
| `AT_CHARGES` | `InvoiceState` | 已开票 | `InvoiceRecord` | `invoiceState` | `SUCCESS` | `transform` | 同时回写账单开票状态 |
| `AT_CHARGES` | `InvoiceState` | 开票失败 | `InvoiceRecord` | `invoiceState` | `FAIL` | `transform` | 失败原因保留在扩展字段或返回消息中 |
| `AT_CHARGES` | `InvoiceState` | 红冲 / 作废相关旧状态 | `InvoiceRecord` | `invoiceState` | `INVALID` / `RED_INK` | `transform` | 与 REV-005 二期口径保持一致 |
| `AT_REFUNDS` | `RefundState` | 申请中 / 未处理 | `AccountingResult` | `resultStatus` | `PENDING_APPROVAL``PENDING` | `transform` | 需结合是否存在审批链字段判定 |
| `AT_REFUNDS` | `RefundState` | 已退款成功 | `AccountingResult` | `resultStatus` | `SUCCESS` | `transform` | 同时要求 `writeBackStatus=WRITTEN` 或等价值 |
| `AT_REFUNDS` | `RefundState` | 退款失败 | `AccountingResult` | `resultStatus` | `FAIL` | `transform` | 保留失败原因 |
| `AT_REFUNDS` | `RefundState` | 已作废 / 已取消 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 不强制进入在线状态机 |
| `PM_ACCOUNT_RECORDS` | `State` | 正常 / 流程中 | `AccountingResult` | `resultStatus` | `PENDING_APPROVAL` / `PENDING` | `transform` | 汇总申请单优先映射为“待处理”状态 |
| `PM_ACCOUNT_RECORDS` | `State` | 作废 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 历史流程单保留只读即可 |
| `PM_AMOUNT_RECORDS` | `State` | 正常 | `AccountingResult` | `resultStatus` | `SUCCESS` / `PENDING_APPROVAL` | `transform` | 需结合是否已生效区分 |
| `PM_AMOUNT_RECORDS` | `State` | 作废 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 不强制在线复刻 |
| `PM_PRICE_RECORDS` | `State` | 正常 | `AccountingResult` | `resultStatus` | `SUCCESS` / `PENDING_APPROVAL` | `transform` | 调价差额生效逻辑需通过生效时间补充判断 |
| `PM_PRICE_RECORDS` | `State` | 作废 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 历史记录保留 |
| `PM_PAYMENT_RECORDS` | `State` | 正常 | `AccountingResult` | `resultStatus` | `SUCCESS` | `transform` | 主要对应冲正/已销调整结果 |
| `PM_PAYMENT_RECORDS` | `State` | 作废 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 历史记录保留 |
| `PM_KNOTTY_RECORDS` | `State` | 申请中 / 未审批 | `AccountingResult` | `resultStatus` | `PENDING_APPROVAL` | `transform` | 坏账申请典型待审批状态 |
| `PM_KNOTTY_RECORDS` | `State` | 已生效 / 已确认坏账 | `AccountingResult` | `resultStatus` | `SUCCESS` | `transform` | 同时影响账单收费状态为坏账 |
| `PM_KNOTTY_RECORDS` | `State` | 作废 / 驳回 | `AccountingResult` / `HistoricalReadonlyRecord` | `resultStatus``legacyState` | `FAIL` 或原状态保留 | `transform` + `readonly-retain` | 若驳回语义明确,优先映射为 `FAIL` |
| `PM_LATEFEE_RECORDS` | `State` | 正常 / 作废 | `HistoricalReadonlyRecord` | `legacyState` | 原状态保留 | `readonly-retain` | 当前一期不强制在线化 |
| `PM_ACCOUNT_RECORD_DETAILS` | `ProcType` | 转预存 | `AccountingResult` / `tracePayload` | `writeBackStatus` + `procType` | 结果已回写 + `TRANSFER_TO_DEPOSIT` | `transform` + `mapping-only` | 当前新模型需通过扩展字段保留旧处理方式 |
| `PM_ACCOUNT_RECORD_DETAILS` | `ProcType` | 转退款 | `AccountingResult` / `tracePayload` | `resultStatus` + `procType` | `SUCCESS` + `REFUND` | `transform` + `mapping-only` | 退款主语义进入结果状态,旧处理方式保留映射 |
| `PM_ACCOUNT_RECORD_DETAILS` | `ProcType` | 转销账 | `AccountingResult` / `tracePayload` | `writeBackStatus` + `procType` | 结果回写 + `WRITE_OFF` | `transform` + `mapping-only` | 当前新模型未见等价主字段,保留追溯值 |
| `PM_ACCOUNT_RECORD_DETAILS` | `ProcType` | 线下退款 | `AccountingResult` / `tracePayload` | `resultStatus` + `procType` | `SUCCESS/FAIL` + `OFFLINE_REFUND` | `transform` + `mapping-only` | 线下路径必须与普通退款区分 |
| `PM_AMOUNT_RECORD_DETAILS` | `ProcType` | 账务处理方式 | `AccountingResult` / `tracePayload` | `resultStatus` + `procType` | 统一结果位 + 原处理方式 | `transform` + `mapping-only` | 不建议原样进入主状态机 |
| `PM_PAYMENT_RECORD_DETAILS` | `ProcType` | 已销调整处理方式 | `AccountingResult` / `tracePayload` | `resultStatus` + `procType` | `SUCCESS/FAIL` + 原处理方式 | `transform` + `mapping-only` | 与冲正、退款结果强相关 |
| `PM_KNOTTY_RECORD_DETAILS` | `ProcType` | 呆坏账处理方式 | `AccountingResult` / `tracePayload` | `resultStatus` + `procType` | `PENDING_APPROVAL/SUCCESS/FAIL` + 原处理方式 | `transform` + `mapping-only` | 需结合坏账申请场景 |
| `PM_*_RECORDS` | `TaskId` | 审批流程任务 ID | `HistoricalReadonlyRecord` | `legacyTaskId` | 原值保留 | `readonly-retain` | 当前不在线复刻旧流程 |
| `PM_*_RECORDS` | `StepId` | 流程节点 ID | `HistoricalReadonlyRecord` | `legacyStepId` | 原值保留 | `readonly-retain` | 当前不在线复刻旧流程 |
| `PM_*_RECORDS` | `FlowRemark` | 流程审批意见 | `HistoricalReadonlyRecord` | `legacyFlowRemark` | 原值保留 | `readonly-retain` | 作为迁移验收与审计依据 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 未申请 / 初始 | `InvoiceRecord` | `invoiceState` | `INIT` / `PENDING` | `transform` | 需与当前发票流程状态定义对齐 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 已受理 / 查询中 | `InvoiceRecord` | `invoiceState` | `PENDING` | `transform` | 对应当前异步申请中间态 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 开票成功 | `InvoiceRecord` | `invoiceState` | `SUCCESS` | `transform` | 核心票据结果 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 开票失败 | `InvoiceRecord` | `invoiceState` | `FAIL` | `transform` | 失败消息保留在返回消息字段 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 已作废 | `InvoiceRecord` | `invoiceState` | `INVALID` | `transform` | 对应当前作废场景 |
| `IV_INVOICE_INFOS` | `InvoiceState` | 已红冲 | `InvoiceRecord` | `invoiceState` | `RED_INK` | `transform` | 对应当前红冲场景 |
## 4. 当前 v1 的关键判断
### 4.1 可较稳定归并到新状态机的状态
- 账单收费状态中的“未收费 / 已收费 / 坏账”
- 发票状态中的“处理中 / 成功 / 失败 / 作废 / 红冲”
- 坏账申请中的“待审批 / 已生效 / 驳回”
- 退款结果中的“待处理 / 成功 / 失败”
### 4.2 更适合保留为 `legacyState` 或历史只读的状态
- 各类旧汇总表中的“作废”细分状态
- 旧流程引擎相关 `TaskId / StepId / FlowRemark`
- 旧处理方式 `ProcType` 中难以直接等价为新主状态的值
### 4.3 迁移实现时必须补的辅助字段
若要保证状态迁移后可追溯,建议至少补以下扩展字段:
- `legacyState`
- `legacyTaskId`
- `legacyStepId`
- `legacyFlowRemark`
- `procType`
- `writeBackStatus`
## 5. 后续动作
下一步建议补:
1. `新旧标识映射矩阵`
2. `试迁校验清单`
只有状态矩阵和标识矩阵都具备后,迁移脚本的转换规则才能稳定落地。

View File

@ -0,0 +1,168 @@
# Checklist: REV-004 试迁校验清单 v1
**Purpose**: 用于指导 `REV-004` 旧账务数据试迁后的最小验收校验,确保数量、金额、关系、状态和追溯链条在试迁阶段就能被发现问题。
**Created**: 2026-03-23
**Feature**: [spec.md](/Volumes/Dpan/github/water-workspace/water-docs/specs/008-rev004-legacy-finance-migration/spec.md)
## 1. 使用方式
- 本清单面向“试迁批次”使用,不是最终割接验收清单。
- 每完成一个迁移批次,都应至少完成一次数量校验、金额校验、关系校验和抽样校验。
- 若某项校验失败,不应直接进入下一批次,而应先定位是:
- 映射矩阵问题
- 字段转换问题
- 状态归并问题
- 标识断链问题
## 2. 批次 1账户与营业账主明细
### 2.1 数量校验
- [ ] `AT_CHARGES` 迁移记录数与 `biz_charge` 承接数一致,或差异已登记并有解释
- [ ] `AT_CHARGE_DETAILS` 迁移记录数与 `biz_charge_detail` 承接数一致,或差异已登记并有解释
- [ ] `CT_ACCOUNTS` 迁移记录数与账户承接对象一致,或差异已登记并有解释
### 2.2 金额校验
- [ ] 旧 `AT_CHARGES.ExtendedAmount` 汇总与新账单主对象应收金额汇总一致
- [ ] 旧 `AT_CHARGES.BillAmount` 汇总与新账单主对象开账金额汇总一致
- [ ] 旧 `AT_CHARGES.LateFee` 汇总与新账单主对象滞纳金汇总一致
- [ ] 旧 `CT_ACCOUNTS.Deposit` 汇总与新账户余额汇总一致,或差异已解释
### 2.3 关系校验
- [ ] 每条迁移后的账单主记录都能找到对应明细记录
- [ ] 每条旧账单都能通过映射表找到新账单主键
- [ ] 若存在 `ParentFeeId` / `ContrastFeeId`,迁移后仍能定位原账单与目标账单关系
### 2.4 抽样校验
- [ ] 随机抽 3 笔普通账单,核对客户号、账期、金额、状态一致
- [ ] 随机抽 2 笔带调整关系的账单,核对原账单与新账单映射关系一致
- [ ] 随机抽 2 个账户,核对余额和客户关系一致
## 3. 批次 2收费结果与交易对象
### 3.1 数量校验
- [ ] `PM_PAY_COLLECTS``PM_PAY_SUBTOTALS``PM_PAY_DETAILS` 迁移或承接数量已核对
- [ ] 旧收费明细与新交易对象 / 收费承接对象数量差异已解释
- [ ] 实时收费日志如仅保留历史只读,其归档记录数已校对
### 3.2 金额校验
- [ ] 旧收费明细 `ActualMoney` 汇总与新交易金额汇总一致
- [ ] 旧收费明细 `SoldMoney` 汇总与新核销金额汇总一致,或差异已解释
- [ ] 旧收费汇总 `TotalMoney` 与新汇总结果一致
- [ ] 第三方交易流水涉及的金额抽样与新交易对象一致
### 3.3 关系校验
- [ ] 每笔收费明细都能追到原账单或新账单映射
- [ ] 每笔收费明细的重要流水号都能追到新交易对象
- [ ] 若存在红冲引用,原收费记录与后续记录链条未断裂
### 3.4 抽样校验
- [ ] 随机抽 3 笔柜台收费,核对原流水号、金额、收费时间、收费员
- [ ] 随机抽 2 笔第三方渠道收费,核对第三方流水号与新交易号映射
- [ ] 随机抽 1 笔红冲关联收费,核对原单与后续记录关系
## 4. 批次 3账务处理历史对象
### 4.1 数量校验
- [ ] `PM_ACCOUNT_RECORD*``PM_AMOUNT_RECORD*``PM_PRICE_RECORD*``PM_PAYMENT_RECORD*``PM_KNOTTY_RECORD*` 已按对象分类统计
- [ ] 每类旧账务处理对象都已判定为在线承接、历史只读或映射保留之一
- [ ] 当前未在线承接的对象,历史只读记录数已归档核对
### 4.2 金额校验
- [ ] 预存退款金额汇总与新退款承接结果或映射结果一致
- [ ] 调整前后金额差额与新账单差异值一致,或差异已解释
- [ ] 坏账金额汇总与新坏账场景承接结果一致,或差异已解释
- [ ] 已销调整 / 价差调整的关键差额字段已核对
### 4.3 关系校验
- [ ] 每笔退款类记录都能追到原流水和目标流水
- [ ] 每笔调整类记录都能追到原账单和目标账单
- [ ] 每笔坏账记录都能追到原账单和处理结果
- [ ] `TaskId``StepId``FlowRemark` 等审批痕迹已进入历史只读或追溯字段
### 4.4 抽样校验
- [ ] 随机抽 2 笔预存退款,核对申请、原流水、目标流水、退款金额
- [ ] 随机抽 2 笔金额/水量调整,核对前后金额/水量和原账单、新账单关系
- [ ] 随机抽 1 笔已销调整或冲正,核对原收费记录与后续处理关系
- [ ] 随机抽 1 笔坏账申请,核对账单、金额、审批痕迹、结果状态
## 5. 批次 4发票对象与账单关系
### 5.1 数量校验
- [ ] `IV_INVOICE_INFOS` 迁移数与新发票承接对象数量一致,或差异已解释
- [ ] `IV_INVOICE_DETAIL_INFOS` 若进入在线承接或历史只读,记录数已校对
- [ ] `IV_CHARGE_INVOICE_MAPPINGS` 关系数已校对
### 5.2 金额校验
- [ ] 发票总金额汇总与新发票对象金额汇总一致
- [ ] 发票与账单映射的金额汇总一致
### 5.3 关系校验
- [ ] 每张旧发票都能找到新发票主键或新业务单号
- [ ] 每张旧发票都能追到原账单或新账单关系
- [ ] 原发票代码 / 原发票号码在红冲或作废场景下未断链
### 5.4 抽样校验
- [ ] 随机抽 2 张正常发票,核对发票号、账单号、状态、电子票地址
- [ ] 随机抽 1 张作废或红冲相关发票,核对原票引用链是否完整
- [ ] 随机抽 1 张多账单映射发票,核对账单关系是否一致
## 6. 通用状态与标识校验
### 6.1 状态校验
- [ ] 收费状态已按状态矩阵归并,未出现无法解释的孤立状态
- [ ] 发票状态已按状态矩阵归并,未出现成功/失败/作废/红冲混乱
- [ ] 退款 / 冲正 / 坏账相关状态已按结果状态和 `legacyState` 双层承接
### 6.2 标识校验
- [ ] `FeeId` ↔ 新账单主键映射可查
- [ ] `AccLogId` ↔ 新流水映射可查
- [ ] 旧申请单号 ↔ 新调整单号映射可查
- [ ] 发票代码 / 号码 ↔ 新发票对象映射可查
## 7. 差异分类规则
若试迁中发现差异,优先按以下类型记录:
- [ ] A 类:对象映射错误
- [ ] B 类:字段转换错误
- [ ] C 类:状态归并错误
- [ ] D 类:标识断链
- [ ] E 类:历史只读遗漏
- [ ] F 类:真实功能缺失
## 8. 通过标准
一次试迁批次至少满足以下条件,才允许进入下一批次:
- [ ] 数量差异已全部解释
- [ ] 关键金额差异已全部解释
- [ ] 主关系链未断裂
- [ ] 抽样场景能完整追溯
- [ ] 状态和标识映射无重大断链
- [ ] 所有未解决问题已形成差异清单
## 9. 后续动作
本清单完成后,下一步应补:
1. 差异清单模板
2. 复迁规则
3. 批次回滚方案

View File

@ -0,0 +1,145 @@
# Data Model: REV-004 旧账务迁移映射与功能缺失分析
## 1. 模型目标
本模型用于定义旧账务迁移规划阶段的核心对象,重点覆盖:
- 旧对象如何分类
- 新系统如何承接
- 映射记录如何表达
- 功能缺失如何判定
- 迁移批次如何组织
## 2. Core Entities
### 2.1 LegacyFinanceObject
表示旧系统中的一个业务财务对象。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `legacyTable` | string | Yes | 旧表名,如 `AT_CHARGES``PM_ACCOUNT_RECORDS` |
| `legacyObjectName` | string | Yes | 旧对象名称,如“营业账”“预存退款” |
| `domainCategory` | string | Yes | 领域分类:账单 / 账户 / 收费 / 调整 / 退款 / 坏账 / 发票 |
| `primaryIdentifier` | string | Yes | 旧对象主标识,如 `FeeId``AccLogId``InvoiceInfoId` |
| `businessMeaning` | string | Yes | 旧对象业务语义 |
| `containsWorkflowFields` | boolean | Yes | 是否携带审批或流程字段 |
| `containsHistoricalOnlyData` | boolean | Yes | 是否包含明显历史只读数据 |
### 2.2 TargetDomainObject
表示新系统中用于承接旧语义的目标对象。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `targetDomain` | string | Yes | 新领域名,如 `ChargeAggregate``Transaction``OperationLog` |
| `targetPhysicalCarrier` | string | Yes | 物理承接对象,如 `biz_charge``bk_transaction` |
| `carrierType` | string | Yes | `online-main` / `mapping-layer` / `history-readonly` |
| `supportsOnlineProcessing` | boolean | Yes | 是否参与在线业务处理 |
| `supportsHistoricalQuery` | boolean | Yes | 是否支持历史查询 |
### 2.3 CompatibilityMappingRecord
表示旧对象与新对象之间的映射关系。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `legacySystem` | string | Yes | 原系统标识 |
| `legacyTable` | string | Yes | 旧表名 |
| `legacyId` | string | Yes | 旧主键值 |
| `legacyBizNo` | string | No | 旧业务单号/流水号 |
| `targetDomain` | string | Yes | 目标领域 |
| `targetId` | string | No | 新主键值 |
| `targetBizNo` | string | No | 新业务单号 |
| `mappingType` | string | Yes | `one-to-one` / `many-to-one` / `one-to-many` / `readonly-only` |
| `mappingStatus` | string | Yes | `planned` / `migrated` / `verified` / `failed` |
| `migrationBatchNo` | string | Yes | 迁移批次号 |
| `tracePayload` | string | No | 扩展追溯信息 |
### 2.4 HistoricalReadonlyRecord
表示仅保留查询与审计用途的历史记录。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `legacyTable` | string | Yes | 原表名 |
| `legacyId` | string | Yes | 原主键 |
| `legacyStatus` | string | No | 原状态 |
| `originalIdentifiers` | string[] | Yes | 原单号、原账单号、原流水号等 |
| `summarySnapshot` | string | Yes | 历史摘要 |
| `queryDimensions` | string[] | Yes | 可查询维度,如客户号、账期、处理类型 |
| `readonlyReason` | string | Yes | 为什么只读保留 |
### 2.5 GapVerdict
表示旧对象相对于当前 backend 的承接状态判定。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `legacyObjectName` | string | Yes | 旧对象名称 |
| `verdict` | string | Yes | `implemented` / `partial` / `readonly` / `missing` |
| `evidenceType` | string | Yes | `controller` / `service` / `do` / `table-mapping` / `doc-only` |
| `evidencePath` | string | Yes | 证据路径 |
| `gapDescription` | string | No | 缺失说明 |
| `recommendedAction` | string | Yes | `reuse` / `extend` / `readonly-retain` / `implement-later` |
### 2.6 MigrationBatch
表示一个迁移批次。
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `batchNo` | string | Yes | 批次编号 |
| `batchName` | string | Yes | 批次名称 |
| `scope` | string[] | Yes | 纳入对象列表 |
| `dependsOn` | string[] | No | 前置批次 |
| `validationFocus` | string[] | Yes | 校验重点 |
| `rollbackScope` | string[] | Yes | 回滚范围 |
## 3. Relationships
```text
LegacyFinanceObject
-> maps to -> TargetDomainObject
-> produces -> CompatibilityMappingRecord
-> may retain as -> HistoricalReadonlyRecord
-> is judged by -> GapVerdict
MigrationBatch
-> groups -> LegacyFinanceObject
-> validates -> CompatibilityMappingRecord / GapVerdict
```
## 4. Validation Rules
- 每个 `LegacyFinanceObject` 必须至少对应一种承接方式:在线主模型、兼容映射层或历史只读层之一。
- 每个被判定为 `implemented``partial``GapVerdict` 必须绑定 backend 证据路径。
- 每个 `HistoricalReadonlyRecord` 必须能返回原系统标识和最小查询维度。
- 每个 `CompatibilityMappingRecord` 必须带 `migrationBatchNo``mappingStatus`
- 每个迁移批次必须先定义校验重点,再进入实际迁移脚本设计。
## 5. Recommended State Semantics
### 5.1 Mapping Status
```text
planned -> migrated -> verified
planned -> failed
migrated -> failed
```
### 5.2 Gap Verdict
```text
implemented
partial
readonly
missing
```
语义说明:
- `implemented`:当前 backend 已有稳定在线承接能力
- `partial`:已有部分承接,但与旧模型仍有明显差距
- `readonly`:不建议在线重建,仅保留历史查询与追溯
- `missing`:当前确实未见稳定承接能力,后续需补设计或实现

View File

@ -0,0 +1,162 @@
# Implementation Plan: REV-004 旧账务迁移映射与功能缺失分析
**Branch**: `008-rev004-legacy-finance-migration` | **Date**: 2026-03-23 | **Spec**: `/specs/008-rev004-legacy-finance-migration/spec.md`
**Input**: Feature specification from `/specs/008-rev004-legacy-finance-migration/spec.md`
**Note**: This plan is document-first and brownfield-evidence aware. The current round focuses on migration planning artifacts rather than migration code execution.
## Summary
本轮围绕 `REV-004` 旧账务迁移规划形成一套可直接衔接后续实施的 planning 产物,核心目标有两项:一是明确旧账务对象如何形成迁移映射,按在线主模型、兼容映射层和历史只读层三层承接;二是明确当前 backend 相对旧模型存在的已承接能力、弱映射点和功能缺失,避免后续把“旧表未原样存在”误判为“必须整表重建”。
本次计划输出以现有正式文档和 backend 证据为准:继续保持 `REV-004` 账务处理一期的统一控制模型,不新增独立账本引擎,不直接写迁移脚本;后续迁移实施按“映射矩阵先行、批次设计其次、试迁与校验最后”的顺序组织。
## Repository Scope
- **Formal workflow home**: `water-docs`
- **Target repos in scope**:
- `water-docs`: Yes
- `water-backend`: Yes
- `water-frontend`: No
- **Primary delivery mode**: Document closure / Code evidence alignment
## Code Baseline
- **Backend baseline**: `water-backend` `HEAD` @ `1c47b922ceca9256482b7f5d2a39040fd2ef99e2`
- **Frontend baseline**: `water-frontend` `HEAD` @ `ae65939045449894c0fccab53fee08521e538ddd`
- **Baseline capture plan**: 所有功能缺失或已承接判断都绑定到当前 backend baseline并在 `research.md` 中记录代码路径、DO、Controller、Service 或测试证据。
## Technical Context
**Primary Work Product**: 迁移规划工件,包括研究结论、迁移数据模型、映射合同、缺失分析合同和执行 quickstart。
**Source of Truth Documents**:
- `specs/008-rev004-legacy-finance-migration/spec.md`
- `.specify/memory/constitution.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/guides/BACKEND_TABLE_MAPPING.md`
**Reference Sources**:
- `docs/design/04_Appendix/Archive/05_Data_Dictionary/营收数据字典.md`
- `docs/guides/REV004_LEGACY_FINANCE_MIGRATION_PLAN_V0.md`
- `docs/design/00_Management/07_Migration_Mapping_Template.md`
- `water-backend/sw-business/.../ChargeController.java`
- `water-backend/sw-business/.../ChargeServiceImpl.java`
- `water-backend/sw-business/.../ChargeServiceAccountingAdjustTest.java`
**Validation Commands**:
- `make validate-file FILE=specs/008-rev004-legacy-finance-migration/spec.md`
- `make validate-file FILE=specs/008-rev004-legacy-finance-migration/plan.md`
- `make validate-file FILE=specs/008-rev004-legacy-finance-migration/research.md`
- `make validate-file FILE=specs/008-rev004-legacy-finance-migration/data-model.md`
- `make validate-file FILE=specs/008-rev004-legacy-finance-migration/quickstart.md`
- `make check-links`
**Target Scope**:
- `REV-004` 旧账务迁移映射方法
- `REV-004` 相关旧对象的功能缺失判定
- 映射矩阵结构、迁移分批、最小校验动作
- 历史只读与迁移验收查询边界
**Project Type**: 文档治理仓库 + 多仓实现协作
**Constraints**:
- 不新增平行正式主稿
- 不发明超出主文档与 Archive 交集的新业务规则
- 本轮不写迁移脚本、不改 backend 业务代码
- 历史查询接口只读,不承担状态修正
- 不把旧表未原样存在直接等同于功能缺失
- 相对路径与现有 `IF-*` 编号体系保持稳定
**Scale/Scope**: 跨文档 migration planning覆盖 1 份 spec、1 份 plan、1 份 research、1 份 data-model、2 份 contracts 和 1 份 quickstart。
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- [x] **主文档归属已确认**:本轮 planning 产物落在 `specs/008-rev004-legacy-finance-migration/`,后续正式结论仍应回写 `12_REV_Detailed.md``03_Interface_Design.md``01_Database_Design.md`,不新增平行正式主稿。
- [x] **多仓范围已确认**:本轮涉及 `water-docs` 规划工件与 `water-backend` 取证,不涉及 `water-frontend`
- [x] **代码基线已确认**backend 和 frontend baseline 已记录,用于绑定当前实现判断和排除前端范围。
- [x] **Archive 使用方式合规**`营收数据字典.md` 仅作为旧模型来源和迁移核对依据,不直接替代正式口径。
- [x] **一致性影响已列出**:已识别旧对象命名、新旧状态映射、账单/流水/发票关系、历史查询接口口径与功能缺失判定标准。
- [x] **校验与台账动作已规划**:已明确 planning 产物最小校验命令;本轮仅生成规划工件,暂不强制更新 `01_Project_Progress.md``03_Task_Checklist.md`
## Project Structure
### Feature Artifacts
```text
specs/008-rev004-legacy-finance-migration/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
└── contracts/
├── rev004-legacy-mapping-contract.md
└── rev004-gap-assessment-contract.md
```
### Repository Touchpoints
```text
water-docs/
├── docs/design/
├── docs/guides/REV004_LEGACY_FINANCE_MIGRATION_PLAN_V0.md
├── docs/guides/BACKEND_TABLE_MAPPING.md
└── .specify/
water-backend/
└── sw-business/sw-business-server/src/main/java/...
```
**Structure Decision**:
- `spec.md`:定义迁移映射规划与功能缺失分析的边界、验收和工件范围。
- `plan.md`:组织本轮研究、设计、合同和 quickstart 结构。
- `research.md`:沉淀迁移分层模型、映射形成方法、功能缺失判定标准和现状结论。
- `data-model.md`:定义迁移映射对象、历史只读对象、缺失判定对象和批次对象。
- `contracts/rev004-legacy-mapping-contract.md`:固化旧对象到新对象的映射矩阵结构与字段规则。
- `contracts/rev004-gap-assessment-contract.md`:固化功能缺失判定的字段、取值和证据要求。
- `quickstart.md`:给后续迁移实施前的工件准备、批次顺序和最小校验动作提供统一入口。
## Phase 0: Research & Alignment
### Research Inputs
- 如何从“旧表平移”转成“语义映射 + 三层承接”?
- 哪些旧对象可视为已被当前 backend 语义承接?
- 哪些旧对象属于弱映射或真正功能缺失?
- 迁移验收需要保留哪些最小查询字段和关系?
- 后续迁移实施前必须先补哪些映射矩阵?
### Deliverables
- `research.md`
## Phase 1: Design & Contracts
### Planned Artifacts
- `data-model.md`
- `contracts/rev004-legacy-mapping-contract.md`
- `contracts/rev004-gap-assessment-contract.md`
- `quickstart.md`
### Design Decisions
- 采用“在线主模型 + 兼容映射层 + 历史只读层”三层迁移结构。
- 映射单元以旧业务对象语义为主,不以旧表名平移为主。
- 功能缺失按“已承接 / 部分承接 / 历史只读 / 功能缺失”四类判定。
- 后续迁移实施优先补映射矩阵和批次设计,再进入脚本开发与试迁。
## Validation Plan
- **Document validation**: `make validate-file FILE=specs/008-rev004-legacy-finance-migration/<file>.md``make check-links`
- **Backend validation**: 本轮以代码路径与测试证据核对为主N/A for compile
- **Frontend validation**: N/A
- **Evidence output**: `research.md``data-model.md``contracts/*``quickstart.md`
## Ledger Sync Plan
- **Project progress update required**: No
- **Task checklist update required**: No
- **Evidence or verification summary update required**: No
## Complexity Tracking
本计划未发生 Constitution 违规项,无需豁免说明。

View File

@ -0,0 +1,97 @@
# Quickstart: REV-004 旧账务迁移规划评审与最小校验
## 1. 评审入口
本轮目标是:
- 明确旧账务迁移映射如何形成
- 明确当前 backend 相对旧模型的功能缺失、弱映射点和历史只读对象
- 为后续迁移脚本设计、试迁和迁移验收提供统一规划入口
本轮验收重点检查:
- 映射方法是否稳定
- 功能缺失判定是否有证据
- 后续迁移批次和矩阵工件是否可直接执行
## 2. 评审步骤
### 步骤一:映射方法校验
确认方案采用以下原则:
- 以业务语义映射为主,而不是旧表逐表平移
- 采用在线主模型、兼容映射层、历史只读层三层承接
- 不把所有旧精细台账都当成必须重建的在线对象
### 步骤二:功能缺失判定校验
确认每个旧对象都按以下四类之一判定:
- `implemented`
- `partial`
- `readonly`
- `missing`
并确认每条判定都绑定 backend 或文档证据。
### 步骤三:最小保留集校验
确认迁移最少保留:
- 原单据标识
- 原账单标识
- 原流水标识
- 原发票标识
- 处理前后金额/水量
- 处理原因
- 申请/审批/生效时间
- 经办人与依据
### 步骤四:批次顺序校验
确认后续迁移按以下顺序推进:
1. 账户与营业账主明细
2. 收费结果与交易对象
3. 账务处理历史对象
4. 发票对象与账单关系
### 步骤五:接口边界校验
确认历史查询与迁移验收遵循以下边界:
- 接口只读
- 统一挂靠既有 `IF-REV-*` 接口族
- 同时支持汇总对账和明细追溯
- 返回新旧标识映射
## 3. 后续实施前必须补齐的工件
后续正式进入迁移脚本开发前,至少补齐:
1. 旧表到新对象映射矩阵
2. 旧字段到新字段映射矩阵
3. 旧状态到新状态映射矩阵
4. 新旧标识映射矩阵
## 4. 最小校验命令
```bash
make validate-file FILE=specs/008-rev004-legacy-finance-migration/spec.md
make validate-file FILE=specs/008-rev004-legacy-finance-migration/plan.md
make validate-file FILE=specs/008-rev004-legacy-finance-migration/research.md
make validate-file FILE=specs/008-rev004-legacy-finance-migration/data-model.md
make validate-file FILE=specs/008-rev004-legacy-finance-migration/quickstart.md
make check-links
```
## 5. 通过标准
满足以下条件即可进入下一阶段:
- 映射方法和三层承接模型已明确
- 功能缺失判定标准已明确
- 后续必做矩阵工件已明确
- 迁移分批顺序已明确
- 历史查询和迁移验收边界已明确

View File

@ -0,0 +1,85 @@
# Phase 0 Research: REV-004 旧账务迁移映射与功能缺失分析
## Decision 1: 迁移映射以“业务语义”而非“旧表平移”为主
- **Decision**: 迁移映射的基本单元定义为“旧业务对象语义”,如营业账、账户流水、预存退款、已销调整、价差调整、坏账、发票关系,而不是直接按旧表名原样对应一个新表。
- **Rationale**: 旧模型以“汇总表 + 明细表 + 流程字段”承载很多业务,而新 `REV-004` 正式模型强调统一账务控制入口和共性规则;若按旧表名逐表平移,会破坏现有正式口径并导致在线模型过度回退。
- **Alternatives considered**:
- 旧表逐表原样在线重建:被否决,因为与当前 `REV-004` 主口径“不新增独立账务台账表族”冲突。
- 完全忽略旧表结构,只保留抽象结论:被否决,因为不满足迁移验收和历史追溯要求。
## Decision 2: 采用“三层承接模型”组织迁移
- **Decision**: 新系统迁移承接采用三层结构:在线主模型承接层、兼容映射层、历史只读层。
- **Rationale**: `12_REV_Detailed.md` 已明确旧精细账务台账不要求全部转为独立在线实体,但必须形成可追溯闭环;`03_Interface_Design.md` 也要求历史查询接口只读且支持汇总对账与明细追溯。三层结构能同时满足在线处理、迁移核查和审计要求。
- **Alternatives considered**:
- 所有旧数据统一进入在线主模型:被否决,因为会污染新模型并放大状态机复杂度。
- 所有旧数据只做历史归档:被否决,因为当前有效账单、收费、发票和部分账务场景仍需在线承接。
## Decision 3: 在线主模型优先承接账单、交易、留痕和发票主对象
- **Decision**: 在线主模型优先由 `biz_charge``biz_charge_detail``bk_transaction*``biz_operat_log*``biz_invoice*``biz_cust_invoice` 等对象承接。
- **Rationale**: `BACKEND_TABLE_MAPPING.md` 和当前 backend 代码已明确这些对象存在;`ChargeServiceImpl.adjustAccounting` 也证明退款、冲正、坏账申请等场景已开始围绕账单、原交易和操作日志形成统一处理骨架。
- **Alternatives considered**:
- 为退款、坏账、价差、已销调整分别新增独立在线主实体:被否决,因为当前 backend 未形成稳定一一对应对象,且与正式文档边界不一致。
## Decision 4: 功能缺失判定必须区分四类状态
- **Decision**: 对旧对象相对当前 backend 的承接状态,统一使用四类判定:`已承接``部分承接``历史只读``功能缺失`
- **Rationale**: 当前仓库已出现大量“旧对象无同名表,但语义已被新对象承接”的情况;若只用“有表/没表”二元判断,会把语义承接误判为缺失。四类判定更适合迁移规划和后续开发排优先级。
- **Alternatives considered**:
- 仅用“已实现 / 未实现”:被否决,因为无法表达历史只读与弱映射。
- 仅用“有表 / 无表”:被否决,因为结构缺失不等于业务能力缺失。
## Decision 5: 当前 `REV-004` 核心账务控制能力已具备“部分承接”基础
- **Decision**: 当前 backend 对 `REV-004` 已具备部分承接基础,主要证据是:
- `ChargeController` 存在 `/business/charge/accounting-adjust`
- `ChargeServiceImpl.adjustAccounting` 已支持 `AMOUNT``WATER``REFUND``REVERSE``BAD_DEBT` 等调整类型
- `ChargeServiceAccountingAdjustTest` 已覆盖退款、冲正、坏账申请等关键路径
- `ChargeDO` / `ChargeDetailDO` / `OperatLogDO` / `OperatLogDetailDO` 已承接账单与留痕
- **Rationale**: 这些证据说明新系统不是从零开始,而是已经具备统一账务处理入口、原交易校验和日志留痕能力。
- **Alternatives considered**:
- 将 `REV-004` 判定为完全未实现:被否决,因为已有明确控制器、服务和测试证据。
- 将 `REV-004` 判定为完全闭环:被否决,因为旧模型的大量精细对象并未一一在线承接。
## Decision 6: 当前明显缺失的是“精细账务对象的独立在线承接”和“迁移专用映射/只读能力”
- **Decision**: 相对旧数据字典,当前 backend 明显缺失或未明确形成稳定在线承接的主要是:
- 退款账独立对象
- 特账 / 特账明细
- 跨周期水量
- 阶梯累计量
- 预存退款 / 已销调整 / 价差调整 / 滞纳金减免 / 呆坏账等旧汇总表和明细表的一对一在线对象
- 专门面向迁移验收的新旧标识映射层
- 历史只读查询的统一在线出口
- **Rationale**: `BACKEND_TABLE_MAPPING.md` 已明确这些属于弱映射或当前缺口;`ChargeServiceImpl` 虽有统一账务调整入口,但并未等价于旧模型的每类精细台账对象都已在线落地。
- **Alternatives considered**:
- 认为这些缺失都必须在线重建:被否决,因为部分对象应以历史只读和映射承接。
## Decision 7: 旧审批流字段优先保留为历史只读,不在线复刻
- **Decision**: 旧模型中的 `TaskId``StepId``FlowRemark` 等审批痕迹,本轮规划为优先按历史只读和追溯字段保留,不要求在线复刻完整审批引擎。
- **Rationale**: 当前 `REV-004` 一期正式口径只保留审批能力位与边界说明;若迁移阶段强行复刻旧审批流,会显著扩大范围。
- **Alternatives considered**:
- 在线完整复刻旧审批流:被否决,因为超出一期边界。
- 完全丢弃审批痕迹:被否决,因为不满足迁移验收和审计要求。
## Decision 8: 后续迁移实施必须先补四类矩阵工件
- **Decision**: 正式进入脚本开发前,必须先补四类矩阵:旧表到新对象映射、旧字段到新字段映射、旧状态到新状态映射、新旧标识映射。
- **Rationale**: 当前最大风险不是“脚本写不出来”,而是没有统一映射基线会导致每个批次各自理解旧模型,最终差异不可控。
- **Alternatives considered**:
- 先写 SQL再回头补映射被否决因为会放大返工和核对成本。
## Decision 9: 迁移实施按“基础主对象 -> 收费结果 -> 账务处理历史 -> 发票关系”分批
- **Decision**: 推荐四个迁移批次:
1. 账户与营业账主明细
2. 收费结果与交易对象
3. 账务处理历史对象
4. 发票对象与账单关系
- **Rationale**: 这种顺序能先锁定主键和核心关系,再逐步迁入衍生对象,降低断链风险。
- **Alternatives considered**:
- 按模块菜单顺序迁移:被否决,因为会打乱主键和依赖关系。
- 按表名前缀整体迁移:被否决,因为无法体现业务依赖。

View File

@ -0,0 +1,170 @@
# Feature Specification: REV-004 旧账务迁移映射与功能缺失分析
**Feature Branch**: `008-rev004-legacy-finance-migration`
**Created**: 2026-03-23
**Status**: Draft
**Input**: User description: "帮我根据这份文档进行规划下 怎么形成迁移 映射 ?以及是否存在功能缺失"
## Document Scope & Sources *(mandatory)*
- **Target documents**:
- `docs/guides/REV004_LEGACY_FINANCE_MIGRATION_PLAN_V0.md`
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `specs/008-rev004-legacy-finance-migration/spec.md`
- `specs/008-rev004-legacy-finance-migration/plan.md`
- `specs/008-rev004-legacy-finance-migration/research.md`
- `specs/008-rev004-legacy-finance-migration/data-model.md`
- `specs/008-rev004-legacy-finance-migration/quickstart.md`
- `specs/008-rev004-legacy-finance-migration/contracts/`
- **Primary source of truth**:
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`
- `docs/design/03_Technical_Design/03_Interface_Design.md`
- `docs/design/03_Technical_Design/01_Database_Design.md`
- `docs/guides/BACKEND_TABLE_MAPPING.md`
- `.specify/memory/constitution.md`
- **Reference sources**:
- `docs/design/04_Appendix/Archive/05_Data_Dictionary/营收数据字典.md`
- `docs/guides/REV004_LEGACY_FINANCE_MIGRATION_PLAN_V0.md`
- `docs/design/00_Management/07_Migration_Mapping_Template.md`
- `docs/design/04_Appendix/Archive/03_Design_Docs/营业收费管理系统-概要设计说明书20250912.md`
- **Scope decision**: In scope。本轮聚焦 `REV-004` 旧账务模型迁移规划,明确迁移映射如何形成、哪些旧对象进入在线主模型、哪些仅保留历史只读、以及当前 backend 相对旧模型存在的功能缺失;本轮不直接执行数据迁移脚本,不进入 backend 代码改造。
## Repository Scope *(mandatory)*
- **Target repos**:
- `water-docs`: Required
- `water-backend`: Required
- `water-frontend`: Not Required
- **Expected delivery type**: Document closure / Code evidence alignment
- **Out of scope for this round**:
- 直接开发迁移程序、ETL 脚本或数据库批处理脚本
- 在本轮为旧账务台账逐表重建新的在线实体表族
- 重建完整会计总账、明细账或借贷分录引擎
- 前端迁移、前端兼容页面和前端查询适配
## Code Baseline *(mandatory for brownfield work)*
- **Backend baseline**: `water-backend` `HEAD` @ `1c47b922ceca9256482b7f5d2a39040fd2ef99e2`
- **Frontend baseline**: `water-frontend` `HEAD` @ `ae65939045449894c0fccab53fee08521e538ddd`(仅用于范围排除)
- **Baseline capture rule**: 所有“已实现 / 部分实现 / 文档先行 / 历史只读”判断都必须绑定当前 backend baseline并在研究结论中写明对应代码路径、DO/Controller/Service 或表映射证据。
## Evidence Scope *(mandatory)*
- **Document evidence required**:
- `specs/008-rev004-legacy-finance-migration/spec.md`
- `specs/008-rev004-legacy-finance-migration/plan.md`
- `specs/008-rev004-legacy-finance-migration/research.md`
- `specs/008-rev004-legacy-finance-migration/data-model.md`
- `specs/008-rev004-legacy-finance-migration/contracts/rev004-legacy-mapping-contract.md`
- `specs/008-rev004-legacy-finance-migration/contracts/rev004-gap-assessment-contract.md`
- `specs/008-rev004-legacy-finance-migration/quickstart.md`
- **Backend evidence required**:
- `ChargeController.java`
- `ChargeServiceImpl.java`
- `ChargeServiceAccountingAdjustTest.java`
- `ChargeDO.java`
- `ChargeDetailDO.java`
- `OperatLogDO.java`
- `OperatLogDetailDO.java`
- `PriceCostAdjustmentDO.java`
- `PriceTierAdjustmentDO.java`
- `bk_transaction*` 相关实现与文档映射
- **Frontend evidence required**:
- N/A
- **Verification artifacts required**:
- `research.md`
- `data-model.md`
- `contracts/*`
- `quickstart.md`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - 形成旧到新的迁移映射方法 (Priority: P1)
作为迁移方案设计人员,我需要先把旧账务模型中的账单、账户、收费、调整、退款、坏账和发票对象映射到新系统的承接层、映射层和历史只读层,这样后续迁移时不会陷入“旧表逐表平移”或“语义丢失”的两种极端。
**Why this priority**: 如果迁移映射方法不先锁定,后续迁移脚本、校验方案和实现拆解都会失去统一基线。
**Independent Test**: 审阅者仅通过 `research.md``data-model.md` 与迁移映射合同,即可判断每类旧账务对象的承接方式、映射规则和保留层次。
**Acceptance Scenarios**:
1. **Given** 旧系统账务对象存在大量“汇总表 + 明细表 + 流程字段”组合, **When** 本轮规划完成, **Then** 审阅者能够明确哪些对象进入在线主模型、哪些仅作为历史只读、哪些只保留映射关系。
2. **Given** 新系统 `REV-004` 当前采用统一账务控制模型, **When** 评审迁移方案, **Then** 不会再要求按旧表名原样重建全部在线表族。
---
### User Story 2 - 判定功能缺失与实现成熟度 (Priority: P2)
作为技术评审人员,我需要看到旧账务模型相对于当前 backend 的功能缺失、弱映射点和已存在承接能力,这样后续才能区分“需要开发”“仅需迁移”“只需历史查询保留”三类工作。
**Why this priority**: 如果不区分功能缺失与仅缺表名映射,容易把迁移工程误做成无边界的功能重构。
**Independent Test**: 审阅者仅通过 `research.md` 和功能缺失合同,即可判断哪些 `REV-004` 相关能力已经存在、哪些属于部分实现、哪些仍然缺失。
**Acceptance Scenarios**:
1. **Given** backend 已存在 `/business/charge/accounting-adjust` 与相关测试, **When** 审阅本轮结论, **Then** 能明确退款、冲正、坏账申请等核心场景已具备一定承接能力,但并未完整覆盖旧模型所有精细台账对象。
2. **Given** 数据字典中存在退款账、特账、跨周期水量、坏账明细等旧对象, **When** 审阅缺失分析, **Then** 能明确哪些只是独立表缺失、哪些属于在线能力缺失、哪些应保留为历史只读。
---
### User Story 3 - 为后续迁移实施提供执行入口 (Priority: P3)
作为后续实施负责人,我需要一套可以直接衔接迁移脚本开发和验收的规划工件,包括分批策略、映射矩阵、最小校验动作和差异定位入口,这样后续不会重复讨论方案框架。
**Why this priority**: 没有执行入口,本轮规划只能停留在概念层,无法顺利进入试迁、校验和差异修正阶段。
**Independent Test**: 审阅者仅通过 `plan.md``quickstart.md`,即可明确后续迁移实施的批次顺序、必做矩阵和最小校验动作。
**Acceptance Scenarios**:
1. **Given** 后续需要开发迁移脚本或数据校验脚本, **When** 阅读本轮计划, **Then** 能直接知道应先产出哪些映射矩阵、按什么批次迁移、如何做最小校验。
2. **Given** 当前阶段仍以规划为主, **When** 审阅本轮结果, **Then** 能明确本轮尚未进入实际数据迁移执行,也不会把迁移脚本视为已完成产物。
---
### Edge Cases
- 当旧对象在数据字典中存在,但 backend 未看到同名独立表时,必须区分“语义已承接”“弱映射承接”和“功能缺失”,不得直接判定为必须原样重建。
- 当旧对象同时携带业务结果、审批流程和页面冗余字段时,必须拆分在线承接字段、历史只读字段和追溯映射字段,避免单表平移污染新模型。
- 当退款、冲正等场景与原交易、发票或收费结果强关联时,必须保留原交易标识与新标识映射关系,避免迁移后审计断链。
- 当历史查询需要支持迁移验收时,不得把历史只读接口设计成状态变更接口。
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: Specification MUST identify the exact migration-planning artifacts to be produced under `specs/008-rev004-legacy-finance-migration/`.
- **FR-002**: Specification MUST identify `12_REV_Detailed.md``03_Interface_Design.md``01_Database_Design.md``BACKEND_TABLE_MAPPING.md` as the main source-of-truth inputs for migration planning.
- **FR-003**: Specification MUST define the migration scope by old object categories rather than by flat table copy lists only.
- **FR-004**: Specification MUST record backend code baseline and use it to judge whether a capability is implemented, partially implemented, or document-first.
- **FR-005**: Specification MUST preserve the single-source-of-truth model and MUST NOT assume creation of new parallel formal main documents.
- **FR-006**: Specification MUST define a three-layer migration model: online main model, compatibility mapping layer, and historical readonly layer.
- **FR-007**: Specification MUST define at least one mapping contract for old object to new object, field/state mapping, and identifier traceability.
- **FR-008**: Specification MUST define how functionality gaps are judged, including distinctions between missing table, weak mapping, and missing business capability.
- **FR-009**: Specification MUST define the minimum retention set for legacy migration, including original identifiers, original bill/transaction references, before/after values, operator, timestamps, and approval traces where applicable.
- **FR-010**: Specification MUST define the migration batch order and minimum validation actions before implementation.
- **FR-011**: Specification MUST require readonly historical query capability for migration acceptance and traceability.
- **FR-012**: Specification MUST list the applicable validation commands for the planning artifacts.
### Key Entities *(include if feature involves data)*
- **Legacy Finance Object**: 数据字典中的旧账务对象,如营业账、预存退款、已销调整、坏账、发票关系等。
- **Online Main Model**: 新系统中继续承接在线业务处理的标准对象。
- **Compatibility Mapping Record**: 用于保存旧标识、新标识、状态映射、来源类型和迁移批次的映射记录。
- **Historical Readonly Record**: 仅保留查询与审计能力、不再作为在线处理主对象的历史记录。
- **Gap Verdict**: 对旧对象在当前 backend 中的承接状态判定,如已承接、部分承接、历史只读、功能缺失。
- **Migration Batch**: 数据迁移执行的分批单元,用于控制顺序、校验和回滚。
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: 审阅者可依据本轮工件明确至少 8 类旧账务对象的承接方式和迁移层次。
- **SC-002**: 审阅者可依据本轮工件明确至少 5 类 `REV-004` 相关旧对象是否存在已实现、部分实现或功能缺失。
- **SC-003**: 审阅者可依据本轮工件直接列出至少 4 份后续迁移实施前必须补齐的映射矩阵或校验工件。
- **SC-004**: 本轮工件不把“旧表未原样落地”等同于“功能完全缺失”,能够区分语义承接与结构缺失。
- **SC-005**: 本轮工件可直接作为后续迁移脚本设计、试迁和迁移验收的规划输入。