docs: 添加完整技术文档体系

- 系统架构文档 (architecture/README.md)
- 6个领域文档:
  - 账户域 (01-account.md)
  - 账务域 (02-ledger.md)
  - 交易域 (03-transaction.md)
  - 对账域 (04-reconciliation.md)
  - 补偿域 (05-compensation.md)
  - 积分域 (06-points.md)
- API 参考文档 (api/README.md)
- 前后端对接清单 (integration/frontend-backend.md)
This commit is contained in:
tangweijie 2026-01-05 18:12:37 +08:00
parent 94f72ca2a8
commit 1460187516
9 changed files with 3955 additions and 0 deletions

572
docs/api/README.md Normal file
View File

@ -0,0 +1,572 @@
# API 参考文档
## 一、API 概览
RustJR 账户管理系统提供 RESTful API 接口,支持账户管理、交易处理、账务查询、对账管理和积分操作等功能。
## 二、基础信息
### 2.1 基础 URL
| 环境 | URL |
|------|-----|
| 开发 | `http://localhost:8080/api/v1` |
| 测试 | `http://test-api.example.com/api/v1` |
| 生产 | `https://api.example.com/api/v1` |
### 2.2 认证方式
目前系统内部使用,暂未启用认证。生产环境建议添加 JWT 认证:
```http
Authorization: Bearer <token>
```
### 2.3 请求/响应格式
**请求头**
```http
Content-Type: application/json
Accept: application/json
```
**统一响应格式**
```json
{
"code": 200,
"message": "success",
"data": { ... }
}
```
**错误响应格式**
```json
{
"code": 400,
"message": "参数错误",
"error": "具体错误描述"
}
```
### 2.4 HTTP 状态码
| 状态码 | 说明 |
|--------|------|
| 200 | 成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 |
| 404 | 资源不存在 |
| 409 | 资源冲突 |
| 500 | 服务器内部错误 |
## 三、API 端点汇总
### 3.1 账户 API (11个端点)
| 方法 | 端点 | 说明 | 前端对接 |
|------|------|------|----------|
| POST | /physical-accounts | 创建实体账户 | ✅ |
| GET | /physical-accounts | 获取实体账户列表 | ✅ |
| GET | /physical-accounts/:id | 获取实体账户详情 | ✅ |
| POST | /physical-accounts/:id/freeze | 冻结实体账户资金 | ✅ |
| POST | /physical-accounts/:id/unfreeze | 解冻实体账户资金 | ✅ |
| POST | /sub-accounts | 创建虚拟子账户 | ✅ |
| GET | /sub-accounts/:id | 获取子账户详情 | ✅ |
| GET | /sub-accounts/:id/balance | 获取子账户余额 | ✅ |
| POST | /sub-accounts/:id/freeze | 冻结子账户资金 | ✅ |
| POST | /sub-accounts/:id/unfreeze | 解冻子账户资金 | ✅ |
| POST | /sub-accounts/:id/close | 关闭子账户 | ✅ |
### 3.2 交易 API (5个端点)
| 方法 | 端点 | 说明 | 前端对接 |
|------|------|------|----------|
| POST | /transactions/transfer | 发起转账 | ⚠️ 部分 |
| POST | /transactions/deposit | 发起充值 | ❌ |
| POST | /transactions/withdraw | 发起提现 | ❌ |
| GET | /transactions/:id | 获取交易详情 | ✅ |
| GET | /transactions | 获取交易列表 | ✅ |
### 3.3 账务 API (3个端点)
| 方法 | 端点 | 说明 | 前端对接 |
|------|------|------|----------|
| GET | /ledger/subjects | 获取会计科目列表 | ❌ |
| GET | /ledger/entries/:id | 获取分录详情 | ❌ |
| GET | /ledger/accounts/:id/entries | 获取账户分录列表 | ❌ |
### 3.4 对账 API (8个端点)
| 方法 | 端点 | 说明 | 前端对接 |
|------|------|------|----------|
| POST | /reconciliation/run | 执行对账 | ⚠️ 部分 |
| GET | /reconciliation/batches/:id | 获取对账批次 | ✅ |
| GET | /reconciliation/batches/:id/items | 获取对账明细 | ✅ |
| GET | /reconciliation/three-account/:id | 三账校验 | ✅ |
| POST | /reconciliation/adjustments | 创建手工补录 | ✅ |
| POST | /reconciliation/adjustments/:id/approve | 审批补录 | ❌ |
| POST | /reconciliation/adjustments/:id/reject | 拒绝补录 | ❌ |
| GET | /reconciliation/adjustments/pending | 获取待审批补录 | ❌ |
### 3.5 积分 API (5个端点)
| 方法 | 端点 | 说明 | 前端对接 |
|------|------|------|----------|
| GET | /points/accounts/:sub_account_id | 获取积分账户 | ❌ |
| POST | /points/earn | 获取积分 | ❌ |
| POST | /points/spend | 消费积分 | ❌ |
| POST | /points/transfer | 转移积分 | ❌ |
| GET | /points/transactions | 查询积分交易 | ❌ |
### 3.6 健康检查
| 方法 | 端点 | 说明 |
|------|------|------|
| GET | /health | 服务健康状态 |
## 四、账户 API 详细文档
### 4.1 创建实体账户
**请求**
```http
POST /api/v1/physical-accounts
Content-Type: application/json
{
"account_no": "6222021234567890123",
"account_name": "测试账户",
"bank_code": "ICBC",
"bank_name": "中国工商银行",
"consistency_mode": "eventual",
"outbound_control": "online_bank"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"account_no": "6222021234567890123",
"account_name": "测试账户",
"bank_code": "ICBC",
"bank_name": "中国工商银行",
"consistency_mode": "eventual",
"outbound_control": "online_bank",
"status": "active",
"personal_balance": "0.00",
"labor_balance": "0.00",
"frozen_balance": "0.00",
"bank_balance": "0.00",
"transit_amount": "0.00",
"available_balance": "0.00",
"version": 0,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
}
```
### 4.2 获取实体账户列表
**请求**
```http
GET /api/v1/physical-accounts?page=1&page_size=10&status=active&keyword=测试
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码默认1 |
| page_size | int | 否 | 每页数量默认10 |
| status | string | 否 | 状态筛选 |
| keyword | string | 否 | 关键词搜索 |
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"data": [
{
"id": 1,
"account_no": "6222021234567890123",
"account_name": "测试账户",
...
}
],
"total": 100,
"page": 1,
"page_size": 10
}
}
```
### 4.3 冻结账户资金
**请求**
```http
POST /api/v1/physical-accounts/1/freeze
Content-Type: application/json
{
"amount": "100.00"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": "账户资金已冻结"
}
```
### 4.4 解冻账户资金
**请求**
```http
POST /api/v1/physical-accounts/1/unfreeze
Content-Type: application/json
{
"amount": "50.00"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": "账户资金已解冻"
}
```
## 五、交易 API 详细文档
### 5.1 发起转账
**请求**
```http
POST /api/v1/transactions/transfer
Content-Type: application/json
{
"from_account_id": 1,
"to_account_id": 2,
"amount": "1000.00",
"remark": "转账测试"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"txn_no": "TXN20240101000001",
"txn_type": "transfer",
"from_account_id": 1,
"to_account_id": 2,
"amount": "1000.00",
"status": "pending",
"created_at": "2024-01-01T00:00:00Z"
}
}
```
### 5.2 获取交易详情
**请求**
```http
GET /api/v1/transactions/1
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"txn_no": "TXN20240101000001",
"txn_type": "transfer",
"from_account_id": 1,
"to_account_id": 2,
"amount": "1000.00",
"status": "success",
"bank_ref_no": "BANK123456",
"created_at": "2024-01-01T00:00:00Z",
"confirmed_at": "2024-01-01T00:01:00Z"
}
}
```
### 5.3 获取交易列表
**请求**
```http
GET /api/v1/transactions?page=1&page_size=10&status=success&from_account_id=1
```
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | int | 否 | 页码 |
| page_size | int | 否 | 每页数量 |
| status | string | 否 | 状态筛选 |
| from_account_id | int | 否 | 转出账户 |
| to_account_id | int | 否 | 转入账户 |
| start_date | date | 否 | 开始日期 |
| end_date | date | 否 | 结束日期 |
## 六、对账 API 详细文档
### 6.1 执行对账
**请求**
```http
POST /api/v1/reconciliation/run
Content-Type: application/json
{
"physical_account_id": 1,
"recon_date": "2024-01-01"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"batch_no": "RECON20240101001",
"physical_account_id": 1,
"recon_date": "2024-01-01",
"total_count": 100,
"matched_count": 98,
"mismatch_count": 2,
"status": "completed"
}
}
```
### 6.2 三账校验
**请求**
```http
GET /api/v1/reconciliation/three-account/1
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"physical_account_id": 1,
"bank_balance": "100000.00",
"transit_net": "5000.00",
"ledger_total": "105000.00",
"expected_total": "105000.00",
"difference": "0.00",
"is_balanced": true,
"verified_at": "2024-01-01T00:00:00Z"
}
}
```
### 6.3 创建手工补录
**请求**
```http
POST /api/v1/reconciliation/adjustments
Content-Type: application/json
{
"related_txn_no": "TXN20240101000001",
"adjustment_type": "add",
"account_id": 1,
"amount": "100.00",
"reason": "银行入账补录"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"adjustment_no": "ADJ20240101001",
"status": "pending"
}
}
```
## 七、积分 API 详细文档
### 7.1 获取积分账户
**请求**
```http
GET /api/v1/points/accounts/1
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": [
{
"id": 1,
"sub_account_id": 1,
"points_type": "production",
"balance": "1000.00",
"total_earned": "5000.00",
"total_spent": "4000.00",
"total_expired": "0.00"
}
]
}
```
### 7.2 获取积分
**请求**
```http
POST /api/v1/points/earn
Content-Type: application/json
{
"points_account_id": 1,
"amount": "100.00",
"related_business_id": "WORK20240101001",
"remark": "生产任务完成奖励"
}
```
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"txn_no": "PTS20240101001",
"txn_type": "earn",
"amount": "100.00",
"balance_after": "1100.00"
}
}
```
### 7.3 消费积分
**请求**
```http
POST /api/v1/points/spend
Content-Type: application/json
{
"points_account_id": 1,
"amount": "50.00",
"related_business_id": "ORDER20240101001",
"remark": "商品兑换"
}
```
### 7.4 转移积分
**请求**
```http
POST /api/v1/points/transfer
Content-Type: application/json
{
"from_account_id": 1,
"to_account_id": 2,
"amount": "100.00",
"remark": "积分转赠"
}
```
## 八、错误码说明
| 错误码 | 说明 |
|--------|------|
| 400001 | 参数验证失败 |
| 400002 | 余额不足 |
| 400003 | 账户状态异常 |
| 404001 | 账户不存在 |
| 404002 | 交易不存在 |
| 409001 | 资源已存在 |
| 500001 | 数据库错误 |
| 500002 | 银行接口错误 |
## 九、数据类型说明
### 9.1 金额类型
- 使用字符串格式的十进制数
- 保留2位小数
- 示例:`"1000.00"`, `"0.50"`
### 9.2 时间类型
- 使用 ISO 8601 格式
- 时区为 UTC
- 示例:`"2024-01-01T00:00:00Z"`
### 9.3 日期类型
- 使用 `YYYY-MM-DD` 格式
- 示例:`"2024-01-01"`
## 十、SDK 使用示例
### 10.1 JavaScript/TypeScript
```typescript
import { AccountAPI, TransactionAPI } from '@/api';
// 创建账户
const account = await AccountAPI.createPhysicalAccount({
account_no: '6222021234567890123',
bank_code: 'ICBC',
});
// 发起转账
const transaction = await TransactionAPI.createTransfer({
from_account_id: 1,
to_account_id: 2,
amount: '1000.00',
});
```
### 10.2 cURL
```bash
# 创建账户
curl -X POST http://localhost:8080/api/v1/physical-accounts \
-H "Content-Type: application/json" \
-d '{"account_no":"6222021234567890123","bank_code":"ICBC"}'
# 获取账户列表
curl http://localhost:8080/api/v1/physical-accounts?page=1&page_size=10
```

258
docs/architecture/README.md Normal file
View File

@ -0,0 +1,258 @@
# 系统架构文档
## 一、系统概述
RustJR 是一个基于 Rust 构建的银行账户管理平台采用领域驱动设计DDD架构支持实体账户、虚拟子账户、复式记账、对账补录、积分管理等核心功能。
## 二、技术栈
| 层次 | 技术 | 说明 |
|------|------|------|
| Web框架 | Axum | 高性能异步 Web 框架 |
| 数据库 | MySQL 8.0 | 关系型数据库 |
| ORM | SQLx | 编译时检查的 SQL 查询 |
| 序列化 | Serde | JSON/YAML 序列化 |
| 日志 | Tracing | 结构化日志 |
| 数值计算 | rust_decimal | 高精度十进制计算 |
| 时间处理 | Chrono | 日期时间处理 |
| UUID | uuid | 唯一标识符生成 |
## 三、系统架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ API 层 │
│ (HTTP REST API - axum) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │账户API │ │交易API │ │账务API │ │对账API │ │积分API │ │
│ │11端点 │ │5端点 │ │3端点 │ │8端点 │ │5端点 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 应用层 │
│ (Commands / Queries / DTOs) │
├─────────────────────────────────────────────────────────────────┤
│ 领域层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 账户域 │ │ 账务域 │ │ 交易域 │ │ 对账域 │ │ 积分域 │ │
│ │Account │ │ Ledger │ │ Txn │ │ Recon │ │ Points │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ┌─────────┐ │
│ │ 补偿域 │ (后台服务) │
│ │Compensa │ │
│ └─────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ 基础设施层 │
│ ┌─────────────────┐ ┌─────────────────────────────────────┐ │
│ │ MySQL 持久化 │ │ 银行对接 (直连/第三方) │ │
│ │ (SQLx) │ │ (MockBank / DirectConnect) │ │
│ └─────────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## 四、项目目录结构
```
rustjr/
├── src/
│ ├── main.rs # 应用入口
│ ├── lib.rs # 库入口
│ ├── config.rs # 配置管理
│ ├── error.rs # 错误定义
│ │
│ ├── api/ # API 层
│ │ ├── mod.rs # 路由注册
│ │ ├── state.rs # 应用状态
│ │ └── handlers/ # 请求处理器
│ │ ├── account.rs # 账户处理器
│ │ ├── transaction.rs # 交易处理器
│ │ ├── ledger.rs # 账务处理器
│ │ ├── reconciliation.rs # 对账处理器
│ │ └── points.rs # 积分处理器
│ │
│ ├── application/ # 应用层
│ │ ├── commands/ # 命令
│ │ ├── queries/ # 查询
│ │ └── dto/ # 数据传输对象
│ │
│ ├── domain/ # 领域层
│ │ ├── account/ # 账户域
│ │ │ ├── entity.rs # 实体
│ │ │ ├── repository.rs # 仓储接口
│ │ │ └── service.rs # 领域服务
│ │ ├── ledger/ # 账务域
│ │ ├── transaction/ # 交易域
│ │ ├── reconciliation/ # 对账域
│ │ ├── compensation/ # 补偿域
│ │ └── points/ # 积分域
│ │
│ └── infrastructure/ # 基础设施层
│ ├── persistence/ # 持久化
│ │ └── mysql/ # MySQL 实现
│ └── bank_integration/ # 银行对接
│ ├── mock_bank.rs # 模拟银行
│ ├── direct_connect.rs # 直连银行
│ └── third_party.rs # 第三方支付
├── migrations/ # 数据库迁移
│ ├── 001_init_schema.sql
│ └── 002_account_model_extension.sql
├── tests/ # 测试
│ ├── common/ # 测试公共代码
│ ├── unit/ # 单元测试
│ ├── integration/ # 集成测试
│ └── scenarios/ # 场景测试
└── docs/ # 文档
├── architecture/ # 架构文档
├── domains/ # 领域文档
├── api/ # API 文档
└── integration/ # 集成文档
```
## 五、领域划分
系统采用领域驱动设计,划分为 6 个核心领域:
| 领域 | 职责 | 核心实体 |
|------|------|----------|
| Account | 账户管理 | PhysicalAccount, VirtualSubAccount |
| Ledger | 账务管理 | AccountBalance, LedgerEntry |
| Transaction | 交易处理 | SystemTransaction, BankTransaction |
| Reconciliation | 对账管理 | ReconciliationBatch, ManualAdjustment |
| Compensation | 补偿处理 | CompensationTask |
| Points | 积分管理 | PointsAccount, PointsTransaction |
## 六、核心设计理念
### 6.1 三科目余额模型
```
personal_balance + labor_balance + frozen_balance = bank_balance
(个人) (劳动) (冻结) (银行)
```
**不变量约束**:三科目之和必须等于银行余额,确保资金一致性。
### 6.2 交易状态机
```
Created → Pending → BankSubmitted → Success/Failed/Timeout → Reversed
↑ ↓
└─────────────────── Retry ───────────────┘
```
### 6.3 三键幂等体系
| 键名 | 说明 | 用途 |
|------|------|------|
| JZTxId | 狱政交易号 (txn_no) | 系统内唯一标识 |
| BankTxId | 银行交易号 (bank_ref_no) | 银行回执关联 |
| SourceKey | 来源幂等键 | 外部入账去重 |
### 6.4 三账对账闭环
```
总账余额 = 银行账余额 + 在途净额
```
通过三账校验确保资金安全:
- 银行账:实际银行余额
- 在途账:已扣未确认的资金
- 总账:系统记账余额
## 七、数据流
### 7.1 出金流程
```mermaid
sequenceDiagram
participant Client
participant API
participant TxnService
participant LedgerService
participant Bank
Client->>API: 发起转账请求
API->>TxnService: 创建交易 (Created)
TxnService->>LedgerService: 优先级扣款 (个人→劳动)
LedgerService->>LedgerService: 建立在途
TxnService->>TxnService: 更新状态 (Pending)
TxnService->>Bank: 提交银行
TxnService->>TxnService: 更新状态 (BankSubmitted)
Bank-->>TxnService: 银行回执
alt 成功
TxnService->>LedgerService: 结转在途
TxnService->>TxnService: 更新状态 (Success)
else 失败
TxnService->>LedgerService: 回退在途
TxnService->>TxnService: 更新状态 (Failed)
end
API-->>Client: 返回结果
```
### 7.2 对账流程
```mermaid
sequenceDiagram
participant Scheduler
participant ReconService
participant LedgerService
participant BankRepo
Scheduler->>ReconService: 触发对账
ReconService->>BankRepo: 获取银行流水
ReconService->>ReconService: 匹配系统交易
ReconService->>LedgerService: 三账校验
alt 平衡
ReconService->>ReconService: 标记完成
else 不平衡
ReconService->>ReconService: 生成差异报告
ReconService->>ReconService: 需要人工审核
end
```
## 八、部署架构
### 8.1 Docker 部署
```yaml
services:
mysql:
image: mysql:8.0
ports: ["3306:3306"]
backend:
build: ./rustjr
ports: ["8080:8080"]
depends_on: [mysql]
frontend:
build: ./rustjr-vue-frontend
ports: ["3001:80"]
depends_on: [backend]
```
### 8.2 环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| DATABASE_URL | 数据库连接串 | - |
| SERVER_HOST | 服务地址 | 0.0.0.0 |
| SERVER_PORT | 服务端口 | 8080 |
| RUST_LOG | 日志级别 | info |
## 九、安全考虑
1. **资金安全**:三科目不变量约束,确保资金不会凭空消失或增加
2. **幂等性**:三键体系确保交易不重复
3. **并发控制**:乐观锁 + 版本号防止并发更新冲突
4. **审计追踪**:所有操作记录完整日志
## 十、性能优化
1. **异步处理**:基于 Tokio 的异步运行时
2. **连接池**SQLx 数据库连接池
3. **批量操作**:批量创建账户、批量对账
4. **索引优化**:关键查询字段建立索引

355
docs/domains/01-account.md Normal file
View File

@ -0,0 +1,355 @@
# 账户域 (Account Domain)
## 一、领域概述
账户域负责管理银行账户的完整生命周期,包括实体账户(对应真实银行账户)和虚拟子账户(在实体账户下创建的逻辑账户)。
## 二、核心实体
### 2.1 实体账户 (PhysicalAccount)
实体账户对应真实的银行账户,是资金管理的基础单元。
```rust
pub struct PhysicalAccount {
pub id: i64, // 账户ID
pub account_no: String, // 银行账号
pub bank_code: String, // 银行代码
pub bank_name: Option<String>, // 银行名称
pub consistency_mode: ConsistencyMode, // 一致性模式
pub outbound_control: OutboundControl, // 出金管控模式
pub status: AccountStatus, // 账户状态
pub created_at: DateTime<Utc>, // 创建时间
pub updated_at: DateTime<Utc>, // 更新时间
}
```
**核心方法**
- `is_active()` - 检查账户是否可用
- `can_outbound()` - 检查是否允许出金
### 2.2 虚拟子账户 (VirtualSubAccount)
虚拟子账户是在实体账户下创建的逻辑账户,用于资金隔离和管理。
```rust
pub struct VirtualSubAccount {
pub id: i64, // 子账户ID
pub physical_account_id: i64, // 所属实体账户ID
pub account_code: String, // 子账户编号
pub account_type: SubAccountType, // 子账户类型
pub valid_from: Option<DateTime<Utc>>, // 有效期开始
pub valid_to: Option<DateTime<Utc>>, // 有效期结束
pub status: AccountStatus, // 账户状态
pub created_at: DateTime<Utc>, // 创建时间
pub updated_at: DateTime<Utc>, // 更新时间
}
```
**核心方法**
- `is_active()` - 检查账户是否可用(考虑有效期)
- `is_temporary()` - 是否为临时账户
- `is_expired()` - 临时账户是否过期
### 2.3 账户控制 (AccountControl)
账户控制配置,用于设置对账频率和银行对接配置。
```rust
pub struct AccountControl {
pub id: i64,
pub physical_account_id: i64,
pub reconciliation_interval: i32, // 对账频率(分钟)
pub direct_connect_config: Option<Value>, // 银企直连配置
pub third_party_config: Option<Value>, // 第三方支付配置
}
```
### 2.4 临时账户池 (SubAccountPool)
管理临时子账户的池,支持批量创建和自动销户。
```rust
pub struct SubAccountPool {
pub id: i64,
pub physical_account_id: i64,
pub name: String,
pub valid_from: DateTime<Utc>,
pub valid_to: DateTime<Utc>,
pub auto_close_rule: Option<Value>, // 自动销户规则
}
```
## 三、枚举类型
### 3.1 账户状态 (AccountStatus)
```rust
pub enum AccountStatus {
Active, // 正常
Frozen, // 冻结
Closed, // 已关闭
}
```
**状态转移规则**
```
Active ──► Frozen ──► Active
│ │
└──────► Closed ◄────┘
```
### 3.2 一致性模式 (ConsistencyMode)
```rust
pub enum ConsistencyMode {
Strong, // 强一致性 - 交易需等待银行确认
Eventual, // 最终一致性 - 先记账后对账
}
```
| 模式 | 特点 | 适用场景 |
|------|------|----------|
| Strong | 实时确认,响应慢 | 大额转账 |
| Eventual | 异步确认,响应快 | 小额高频 |
### 3.3 出金管控模式 (OutboundControl)
```rust
pub enum OutboundControl {
ReceiveOnly, // 只收不付
OnlineBank, // 网银控制
Token, // 令牌控制
}
```
| 模式 | 说明 | 使用场景 |
|------|------|----------|
| ReceiveOnly | 禁止出金 | 归集账户 |
| OnlineBank | 网银审批出金 | 常规账户 |
| Token | 令牌验证出金 | 高安全账户 |
### 3.4 账户类型 (AccountType)
```rust
pub enum AccountType {
Physical, // 实体账户
Virtual, // 虚拟账户
}
```
### 3.5 子账户类型 (SubAccountType)
```rust
pub enum SubAccountType {
Settlement, // 结算子账户
Management, // 管理子账户
Temporary, // 临时子账户
}
```
| 类型 | 说明 | 特点 |
|------|------|------|
| Settlement | 结算账户 | 长期使用,用于日常结算 |
| Management | 管理账户 | 内部管理,资金归集 |
| Temporary | 临时账户 | 有有效期,自动销户 |
## 四、领域服务
### 4.1 AccountService
账户域的核心服务,提供账户管理功能。
```rust
impl AccountService {
// 创建实体账户
pub async fn create_physical_account(&self, req: CreatePhysicalAccountRequest) -> Result<PhysicalAccount>;
// 获取实体账户
pub async fn get_physical_account(&self, id: i64) -> Result<PhysicalAccount>;
// 获取实体账户列表(分页)
pub async fn list_physical_accounts_paginated(
&self,
status: Option<AccountStatus>,
keyword: Option<String>,
page: i32,
page_size: i32
) -> Result<(Vec<PhysicalAccount>, i64)>;
// 冻结实体账户
pub async fn freeze_physical_account(&self, id: i64) -> Result<()>;
// 解冻实体账户
pub async fn unfreeze_physical_account(&self, id: i64) -> Result<()>;
// 创建虚拟子账户
pub async fn create_sub_account(&self, req: CreateVirtualSubAccountRequest) -> Result<VirtualSubAccount>;
// 批量创建子账户
pub async fn batch_create_sub_accounts(&self, req: BatchCreateSubAccountRequest) -> Result<Vec<VirtualSubAccount>>;
// 关闭子账户
pub async fn close_sub_account(&self, id: i64) -> Result<()>;
// 获取实体账户下的子账户列表
pub async fn list_sub_accounts(&self, physical_account_id: i64) -> Result<Vec<VirtualSubAccount>>;
}
```
## 五、仓储接口
### 5.1 PhysicalAccountRepository
```rust
#[async_trait]
pub trait PhysicalAccountRepository: Send + Sync {
async fn create(&self, account: &PhysicalAccount) -> Result<i64>;
async fn find_by_id(&self, id: i64) -> Result<Option<PhysicalAccount>>;
async fn find_by_account_no(&self, account_no: &str) -> Result<Option<PhysicalAccount>>;
async fn find_all(&self) -> Result<Vec<PhysicalAccount>>;
async fn find_paginated(&self, status: Option<AccountStatus>, keyword: Option<String>, offset: i64, limit: i64) -> Result<Vec<PhysicalAccount>>;
async fn count(&self, status: Option<AccountStatus>, keyword: Option<String>) -> Result<i64>;
async fn update_status(&self, id: i64, status: AccountStatus) -> Result<()>;
}
```
### 5.2 VirtualSubAccountRepository
```rust
#[async_trait]
pub trait VirtualSubAccountRepository: Send + Sync {
async fn create(&self, account: &VirtualSubAccount) -> Result<i64>;
async fn find_by_id(&self, id: i64) -> Result<Option<VirtualSubAccount>>;
async fn find_by_physical_account(&self, physical_account_id: i64) -> Result<Vec<VirtualSubAccount>>;
async fn find_expired_temporary(&self) -> Result<Vec<VirtualSubAccount>>;
async fn update_status(&self, id: i64, status: AccountStatus) -> Result<()>;
async fn batch_create(&self, accounts: &[VirtualSubAccount]) -> Result<Vec<i64>>;
}
```
## 六、业务规则
### 6.1 账户创建规则
1. 银行账号必须唯一
2. 实体账户必须关联有效的银行代码
3. 子账户编号在同一实体账户下必须唯一
4. 临时账户必须设置有效期
### 6.2 账户状态规则
1. 只有 `Active` 状态的账户可以进行交易
2. `Frozen` 状态只影响出金,入金正常
3. `Closed` 状态不可进行任何交易
4. 账户关闭前必须余额为零
### 6.3 出金管控规则
1. `ReceiveOnly` 模式禁止任何出金操作
2. `OnlineBank` 模式需要通过网银审批
3. `Token` 模式需要提供有效令牌
## 七、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/physical-accounts | 创建实体账户 |
| GET | /api/v1/physical-accounts | 获取实体账户列表 |
| GET | /api/v1/physical-accounts/:id | 获取实体账户详情 |
| POST | /api/v1/physical-accounts/:id/freeze | 冻结实体账户资金 |
| POST | /api/v1/physical-accounts/:id/unfreeze | 解冻实体账户资金 |
| POST | /api/v1/sub-accounts | 创建虚拟子账户 |
| GET | /api/v1/sub-accounts/:id | 获取子账户详情 |
| GET | /api/v1/sub-accounts/:id/balance | 获取子账户余额 |
| POST | /api/v1/sub-accounts/:id/freeze | 冻结子账户资金 |
| POST | /api/v1/sub-accounts/:id/unfreeze | 解冻子账户资金 |
| POST | /api/v1/sub-accounts/:id/close | 关闭子账户 |
| GET | /api/v1/physical-accounts/:id/sub-accounts | 获取实体账户下的子账户列表 |
## 八、数据库表结构
### 8.1 physical_account 表
```sql
CREATE TABLE physical_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
account_no VARCHAR(64) NOT NULL UNIQUE,
account_name VARCHAR(128),
bank_code VARCHAR(32) NOT NULL,
bank_name VARCHAR(128),
consistency_mode VARCHAR(32) DEFAULT 'eventual',
outbound_control VARCHAR(32) DEFAULT 'online_bank',
status VARCHAR(32) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bank_code (bank_code),
INDEX idx_status (status)
);
```
### 8.2 virtual_sub_account 表
```sql
CREATE TABLE virtual_sub_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
physical_account_id BIGINT NOT NULL,
account_code VARCHAR(64) NOT NULL,
account_type VARCHAR(32) DEFAULT 'settlement',
valid_from TIMESTAMP NULL,
valid_to TIMESTAMP NULL,
status VARCHAR(32) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (physical_account_id) REFERENCES physical_account(id),
UNIQUE INDEX idx_account_code (physical_account_id, account_code),
INDEX idx_status (status),
INDEX idx_valid_to (valid_to)
);
```
## 九、使用示例
### 9.1 创建实体账户
```rust
let req = CreatePhysicalAccountRequest {
account_no: "6222021234567890123".to_string(),
bank_code: "ICBC".to_string(),
bank_name: Some("中国工商银行".to_string()),
consistency_mode: Some(ConsistencyMode::Eventual),
outbound_control: Some(OutboundControl::OnlineBank),
};
let account = account_service.create_physical_account(req).await?;
```
### 9.2 创建子账户
```rust
let req = CreateVirtualSubAccountRequest {
physical_account_id: 1,
account_code: "SUB001".to_string(),
account_type: SubAccountType::Settlement,
valid_from: None,
valid_to: None,
};
let sub_account = account_service.create_sub_account(req).await?;
```
### 9.3 批量创建临时账户
```rust
let req = BatchCreateSubAccountRequest {
physical_account_id: 1,
account_type: SubAccountType::Temporary,
prefix: "TEMP".to_string(),
count: 100,
valid_from: Some(Utc::now()),
valid_to: Some(Utc::now() + Duration::days(30)),
};
let accounts = account_service.batch_create_sub_accounts(req).await?;
```

488
docs/domains/02-ledger.md Normal file
View File

@ -0,0 +1,488 @@
# 账务域 (Ledger Domain)
## 一、领域概述
账务域是系统的核心财务引擎,负责余额管理、复式记账、三科目约束校验。该域实现了银行级别的资金安全保障机制。
## 二、核心概念:三科目余额模型
### 2.1 模型定义
```
┌─────────────────────────────────────────────────────────┐
│ 银行余额 (bank_balance) │
│ = 1000.00 │
├─────────────────────────────────────────────────────────┤
│ 个人余额 │ 劳动报酬 │ 冻结余额 │
│ personal │ labor │ frozen │
│ = 500.00 │ = 300.00 │ = 200.00 │
│ (可用) │ (可用) │ (不可用) │
└─────────────────────────────────────────────────────────┘
不变量约束: personal + labor + frozen = bank_balance
```
### 2.2 余额类型说明
| 余额类型 | 字段 | 说明 | 可用性 |
|----------|------|------|--------|
| 个人余额 | personal_balance | 个人可支配资金 | 可用 |
| 劳动报酬 | labor_balance | 劳动所得报酬 | 可用 |
| 冻结余额 | frozen_balance | 被冻结的资金 | 不可用 |
| 银行余额 | bank_balance | 银行账面余额 | 对照 |
| 在途金额 | transit_amount | 已扣未确认 | 过渡 |
### 2.3 可用余额计算
```rust
// 总可用余额 = 个人 + 劳动
fn total_available(&self) -> Decimal {
self.personal_balance + self.labor_balance
}
// 可支配余额 = 总可用 - 在途
fn calculate_available(&self) -> Decimal {
self.total_available() - self.transit_amount
}
```
## 三、核心实体
### 3.1 账户余额 (AccountBalance)
```rust
pub struct AccountBalance {
pub id: i64,
pub account_id: i64,
pub account_type: AccountType,
// 三科目余额
pub personal_balance: Decimal, // 个人余额
pub labor_balance: Decimal, // 劳动报酬
pub frozen_balance: Decimal, // 冻结余额
// 银行对照
pub bank_balance: Decimal, // 银行余额
// 在途管理
pub transit_amount: Decimal, // 在途金额
// 版本控制
pub version: i32, // 乐观锁版本
pub updated_at: DateTime<Utc>,
}
```
### 3.2 会计科目 (AccountingSubject)
```rust
pub struct AccountingSubject {
pub code: String, // 科目代码
pub name: String, // 科目名称
pub category: SubjectCategory, // 科目类别
pub direction_default: i8, // 默认增加方向
pub parent_code: Option<String>,// 父科目代码
pub level: i32, // 科目级别
}
```
**预定义科目**
| 代码 | 名称 | 类别 |
|------|------|------|
| 1002 | 银行存款 | 资产 |
| 1003 | 在途资金 | 资产 |
| 2001 | 客户存款 | 负债 |
| 2002 | 待清算款项 | 负债 |
| 3001 | 手续费收入 | 收入 |
| 4001 | 利息支出 | 支出 |
### 3.3 记账分录 (LedgerEntry)
```rust
pub struct LedgerEntry {
pub id: i64,
pub entry_no: String, // 分录编号
pub txn_no: String, // 关联交易号
pub post_date: NaiveDate, // 记账日期
pub post_time: DateTime<Utc>, // 记账时间
pub description: Option<String>,// 摘要描述
pub status: EntryStatus, // 状态
pub created_at: DateTime<Utc>,
}
```
### 3.4 分录明细 (LedgerLine)
```rust
pub struct LedgerLine {
pub id: i64,
pub entry_id: i64,
pub account_id: i64,
pub account_type: AccountType,
pub subject_code: String, // 科目代码
pub direction: Direction, // 借贷方向
pub amount: Decimal, // 金额
}
```
## 四、枚举类型
### 4.1 借贷方向 (Direction)
```rust
pub enum Direction {
Debit, // 借方
Credit, // 贷方
}
```
### 4.2 科目类别 (SubjectCategory)
```rust
pub enum SubjectCategory {
Asset, // 资产类 - 借方增加
Liability, // 负债类 - 贷方增加
Income, // 收入类 - 贷方增加
Expense, // 支出类 - 借方增加
}
```
### 4.3 分录状态 (EntryStatus)
```rust
pub enum EntryStatus {
Pending, // 待确认
Posted, // 已过账
Reversed, // 已冲销
}
```
## 五、核心业务逻辑
### 5.1 优先级扣款
出金时按优先级从余额中扣减:**先个人,后劳动**。
```rust
pub fn deduct_with_priority(&mut self, amount: Decimal) -> Result<DeductionResult> {
let available = self.available_balance();
if available < amount {
return Err(AppError::InsufficientBalance { available, required: amount });
}
let mut remaining = amount;
// 1. 先扣个人余额
let from_personal = remaining.min(self.personal_balance);
self.personal_balance -= from_personal;
remaining -= from_personal;
// 2. 再扣劳动报酬
let from_labor = remaining.min(self.labor_balance);
self.labor_balance -= from_labor;
// 3. 同步银行余额
self.bank_balance -= amount;
Ok(DeductionResult { from_personal, from_labor, total: amount })
}
```
**扣款示例**
| 场景 | 扣款金额 | 个人余额 | 劳动余额 | 扣款来源 |
|------|----------|----------|----------|----------|
| 1 | 300 | 500 | 200 | 个人300 |
| 2 | 600 | 500 | 200 | 个人500 + 劳动100 |
| 3 | 800 | 500 | 200 | 失败(余额不足) |
### 5.2 冻结/解冻
冻结操作将可用余额转移到冻结余额,按优先级从个人和劳动中扣减。
```rust
// 冻结
pub fn freeze(&mut self, amount: Decimal) {
let mut remaining = amount;
// 先从个人扣
let from_personal = remaining.min(self.personal_balance);
self.personal_balance -= from_personal;
remaining -= from_personal;
// 再从劳动扣
if remaining > Decimal::ZERO {
let from_labor = remaining.min(self.labor_balance);
self.labor_balance -= from_labor;
}
// 增加冻结余额
self.frozen_balance += amount;
}
// 解冻(默认返回到个人余额)
pub fn unfreeze(&mut self, amount: Decimal) {
let unfreeze_amount = amount.min(self.frozen_balance);
self.frozen_balance -= unfreeze_amount;
self.personal_balance += unfreeze_amount;
}
```
### 5.3 在途管理
在途金额表示已从可用余额扣减但尚未得到银行确认的资金。
```rust
// 建立在途(出金时)
pub fn add_transit(&mut self, amount: Decimal) {
self.transit_amount += amount;
}
// 结转在途(银行确认成功)
pub fn settle_transit(&mut self, amount: Decimal) -> Result<()> {
if self.transit_amount < amount {
return Err(AppError::BusinessRule("在途金额不足".into()));
}
self.transit_amount -= amount;
Ok(())
}
// 回退在途(银行失败)
pub fn rollback_transit(&mut self, amount: Decimal) {
let rollback = amount.min(self.transit_amount);
self.transit_amount -= rollback;
self.personal_balance += rollback; // 返回到个人余额
self.bank_balance += rollback; // 恢复银行余额
}
```
### 5.4 不变量校验
确保三科目之和等于银行余额。
```rust
pub fn validate_invariant(&self) -> Result<()> {
let sum = self.personal_balance + self.labor_balance + self.frozen_balance;
if sum == self.bank_balance {
Ok(())
} else {
Err(AppError::InvariantViolation {
account_id: self.account_id,
expected: self.bank_balance,
actual: sum,
})
}
}
```
## 六、领域服务
### 6.1 LedgerService
```rust
impl LedgerService {
// ========== 余额操作 ==========
// 获取账户余额
pub async fn get_balance(&self, account_id: i64, account_type: AccountType) -> Result<AccountBalance>;
// 冻结金额
pub async fn freeze_amount(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>;
// 解冻金额
pub async fn unfreeze_amount(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>;
// 优先级扣款
pub async fn deduct_with_priority(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<DeductionResult>;
// 建立在途
pub async fn add_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>;
// 结转在途
pub async fn settle_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>;
// 回退在途
pub async fn rollback_transit(&self, account_id: i64, account_type: AccountType, amount: Decimal) -> Result<()>;
// ========== 记账操作 ==========
// 创建分录
pub async fn create_entry(&self, req: CreateEntryRequest) -> Result<LedgerEntry>;
// 获取账户分录
pub async fn get_account_entries(&self, account_id: i64, account_type: AccountType) -> Result<Vec<LedgerEntry>>;
// ========== 科目操作 ==========
// 获取所有科目
pub async fn list_subjects(&self) -> Result<Vec<AccountingSubject>>;
// 初始化预定义科目
pub async fn initialize_subjects(&self) -> Result<()>;
// ========== 校验操作 ==========
// 校验不变量
pub async fn validate_invariant(&self, account_id: i64, account_type: AccountType) -> Result<()>;
}
```
## 七、复式记账
### 7.1 记账原则
1. **借贷必相等**:每笔分录的借方金额总和必须等于贷方金额总和
2. **有借必有贷**:每笔分录至少包含一个借方和一个贷方
3. **科目对应**:资产/费用借增贷减,负债/收入贷增借减
### 7.2 记账示例
**存款入账**
```
借: 银行存款 1002 1000.00
贷: 客户存款 2001 1000.00
```
**转账出金**
```
借: 客户存款 2001 500.00
贷: 银行存款 1002 500.00
```
**收取手续费**
```
借: 客户存款 2001 10.00
贷: 手续费收入 3001 10.00
```
### 7.3 分录验证
```rust
impl CreateEntryRequest {
pub fn validate_balance(&self) -> Result<(), (Decimal, Decimal)> {
let mut total_debit = Decimal::ZERO;
let mut total_credit = Decimal::ZERO;
for line in &self.lines {
match line.direction {
Direction::Debit => total_debit += line.amount,
Direction::Credit => total_credit += line.amount,
}
}
if total_debit == total_credit {
Ok(())
} else {
Err((total_debit, total_credit))
}
}
}
```
## 八、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/v1/ledger/subjects | 获取会计科目列表 |
| GET | /api/v1/ledger/entries/:id | 获取分录详情 |
| GET | /api/v1/ledger/accounts/:id/entries | 获取账户分录列表 |
## 九、数据库表结构
### 9.1 account_balance 表
```sql
CREATE TABLE account_balance (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
account_id BIGINT NOT NULL,
account_type VARCHAR(32) NOT NULL,
-- 三科目余额
personal_balance DECIMAL(20, 2) DEFAULT 0.00,
labor_balance DECIMAL(20, 2) DEFAULT 0.00,
frozen_balance DECIMAL(20, 2) DEFAULT 0.00,
-- 银行对照
bank_balance DECIMAL(20, 2) DEFAULT 0.00,
-- 在途管理
transit_amount DECIMAL(20, 2) DEFAULT 0.00,
-- 兼容字段
system_balance DECIMAL(20, 2) DEFAULT 0.00,
available_balance DECIMAL(20, 2) DEFAULT 0.00,
frozen_amount DECIMAL(20, 2) DEFAULT 0.00,
version INT DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_account (account_id, account_type)
);
```
### 9.2 ledger_entry 表
```sql
CREATE TABLE ledger_entry (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
entry_no VARCHAR(64) NOT NULL UNIQUE,
txn_no VARCHAR(64) NOT NULL,
post_date DATE NOT NULL,
post_time TIMESTAMP NOT NULL,
description TEXT,
status VARCHAR(32) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_txn_no (txn_no),
INDEX idx_post_date (post_date)
);
```
### 9.3 ledger_line 表
```sql
CREATE TABLE ledger_line (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
entry_id BIGINT NOT NULL,
account_id BIGINT NOT NULL,
account_type VARCHAR(32) NOT NULL,
subject_code VARCHAR(32) NOT NULL,
direction VARCHAR(16) NOT NULL,
amount DECIMAL(20, 2) NOT NULL,
FOREIGN KEY (entry_id) REFERENCES ledger_entry(id),
INDEX idx_account (account_id, account_type),
INDEX idx_subject (subject_code)
);
```
## 十、三账校验
### 10.1 校验公式
```
总账余额 = 银行账余额 + 在途净额
即: ledger_total = bank_balance + transit_net
```
### 10.2 校验结果
```rust
pub struct ThreeAccountResult {
pub bank_balance: Decimal, // 银行账余额
pub transit_net: Decimal, // 在途净额
pub ledger_total: Decimal, // 总账余额
pub is_balanced: bool, // 是否平衡
pub difference: Decimal, // 差异金额
}
```
### 10.3 不平衡处理
1. **记录差异**:生成差异报告
2. **人工审核**:提交人工对账
3. **自动调整**:小额差异自动调平
4. **告警通知**:大额差异触发告警

View File

@ -0,0 +1,481 @@
# 交易域 (Transaction Domain)
## 一、领域概述
交易域负责处理系统内的所有资金流转,包括转账、充值、提现等操作。该域实现了完整的交易状态机和三键幂等体系,确保交易的安全性和一致性。
## 二、核心概念
### 2.1 三键幂等体系
| 键名 | 字段 | 说明 | 唯一性 |
|------|------|------|--------|
| JZTxId | txn_no | 狱政交易号 | 系统全局唯一 |
| BankTxId | bank_ref_no | 银行交易号 | 银行返回,用于对账 |
| SourceKey | source_key | 来源幂等键 | 外部入账去重 |
**SourceKey 格式**
```
{银行流水号}_{金额}_{记账日}_{对方户名归一化}
```
### 2.2 交易状态机
```
┌──────────────────────────────────────┐
│ │
▼ │
┌─────────┐ ┌─────────┐ ┌──────────────┐ ┌───────┴───┐
│ Created │───►│ Pending │───►│ BankSubmitted│───►│ Success │
└─────────┘ └─────────┘ └──────────────┘ └───────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────┐
│ Failed │ │ Timeout │──────►│ Reversed │
└─────────┘ └─────────┘ └──────────┘
```
**状态说明**
| 状态 | 说明 | 后续操作 |
|------|------|----------|
| Created | 已创建(初始状态) | 继续处理或取消 |
| Pending | 待处理(已建立在途) | 提交银行 |
| BankSubmitted | 已提交银行 | 等待回执 |
| Success | 成功(银行确认) | 结转在途 |
| Failed | 失败(银行拒绝) | 回退在途 |
| Timeout | 超时(无回执) | 等待对账或重试 |
| Reversed | 已冲正 | 终态 |
## 三、核心实体
### 3.1 系统交易 (SystemTransaction)
```rust
pub struct SystemTransaction {
pub id: i64,
pub txn_no: String, // 狱政交易号 (JZTxId)
pub txn_type: TransactionType, // 交易类型
pub from_account_id: Option<i64>, // 转出账户ID
pub to_account_id: Option<i64>, // 转入账户ID
pub amount: Decimal, // 金额
pub status: TransactionStatus, // 状态
pub bank_ref_no: Option<String>, // 银行交易号 (BankTxId)
pub source_key: Option<String>, // 来源幂等键 (SourceKey)
pub remark: Option<String>, // 备注
pub created_at: DateTime<Utc>, // 创建时间
pub confirmed_at: Option<DateTime<Utc>>, // 确认时间
pub submitted_at: Option<DateTime<Utc>>, // 提交银行时间
pub version: i32, // 乐观锁版本
}
```
**核心方法**
```rust
impl SystemTransaction {
// 检查是否可以提交到银行
pub fn can_submit(&self) -> bool {
matches!(self.status, TransactionStatus::Pending | TransactionStatus::Created)
}
// 检查是否需要对账
pub fn needs_reconciliation(&self) -> bool {
matches!(self.status,
TransactionStatus::BankSubmitted |
TransactionStatus::Timeout |
TransactionStatus::Processing
)
}
// 检查是否为终态
pub fn is_terminal(&self) -> bool {
self.status.is_terminal()
}
// 检查是否超时
pub fn is_timeout(&self, timeout_seconds: i64) -> bool {
if self.status != TransactionStatus::BankSubmitted {
return false;
}
if let Some(submitted_at) = self.submitted_at {
let elapsed = Utc::now().signed_duration_since(submitted_at);
return elapsed.num_seconds() > timeout_seconds;
}
false
}
// 尝试状态转移
pub fn try_transition(&mut self, target: TransactionStatus) -> Result<(), String> {
if self.status.can_transition_to(target) {
self.status = target;
self.version += 1;
Ok(())
} else {
Err(format!("无效的状态转移: {:?} -> {:?}", self.status, target))
}
}
}
```
### 3.2 银行交易 (BankTransaction)
从银行同步的交易流水。
```rust
pub struct BankTransaction {
pub id: i64,
pub bank_ref_no: String, // 银行参考号
pub physical_account_id: i64, // 实体账户ID
pub txn_type: String, // 交易类型
pub direction: TransactionDirection, // 交易方向
pub amount: Decimal, // 金额
pub counterparty_name: Option<String>,// 对手方名称
pub counterparty_account: Option<String>,// 对手方账号
pub txn_time: DateTime<Utc>, // 交易时间
pub sync_time: DateTime<Utc>, // 同步时间
pub match_status: MatchStatus, // 匹配状态
pub matched_txn_no: Option<String>, // 匹配的系统交易号
pub remark: Option<String>, // 摘要
}
```
## 四、枚举类型
### 4.1 交易状态 (TransactionStatus)
```rust
pub enum TransactionStatus {
Created, // 已创建
Pending, // 待处理
BankSubmitted, // 已提交银行
Success, // 成功
Failed, // 失败
Timeout, // 超时
Reversed, // 已冲正
// 兼容旧状态
Processing, // 处理中 → BankSubmitted
Confirmed, // 已确认 → Success
Mismatch, // 不匹配
}
```
**状态转移规则**
```rust
impl TransactionStatus {
pub fn can_transition_to(&self, target: Self) -> bool {
match (self, target) {
// Created -> Pending
(Self::Created, Self::Pending) => true,
// Pending -> BankSubmitted | Failed
(Self::Pending, Self::BankSubmitted) => true,
(Self::Pending, Self::Failed) => true,
// BankSubmitted -> Success | Failed | Timeout
(Self::BankSubmitted, Self::Success) => true,
(Self::BankSubmitted, Self::Failed) => true,
(Self::BankSubmitted, Self::Timeout) => true,
// Timeout -> Success | Failed (对账确认)
(Self::Timeout, Self::Success) => true,
(Self::Timeout, Self::Failed) => true,
// Success -> Reversed (冲正)
(Self::Success, Self::Reversed) => true,
_ => false,
}
}
}
```
### 4.2 交易类型 (TransactionType)
```rust
pub enum TransactionType {
Transfer, // 转账
Deposit, // 充值
Withdrawal, // 提现
Fee, // 手续费
Interest, // 利息
Adjustment, // 调整
Other(String), // 其他
}
```
### 4.3 交易方向 (TransactionDirection)
```rust
pub enum TransactionDirection {
Inbound, // 入账
Outbound, // 出账
}
```
### 4.4 匹配状态 (MatchStatus)
```rust
pub enum MatchStatus {
Unmatched, // 未匹配
Matched, // 已匹配
Mismatch, // 不匹配
}
```
## 五、交易处理流程
### 5.1 出金流程
```mermaid
sequenceDiagram
participant Client
participant TxnService
participant LedgerService
participant BankClient
participant CompService
Client->>TxnService: 1. 发起转账
TxnService->>TxnService: 2. 创建交易 (Created)
TxnService->>LedgerService: 3. 优先级扣款
LedgerService-->>TxnService: 扣款结果
TxnService->>LedgerService: 4. 建立在途
TxnService->>TxnService: 5. 更新状态 (Pending)
TxnService->>BankClient: 6. 提交银行
TxnService->>TxnService: 7. 更新状态 (BankSubmitted)
alt 银行成功
BankClient-->>TxnService: 成功回执
TxnService->>LedgerService: 8a. 结转在途
TxnService->>TxnService: 9a. 更新状态 (Success)
else 银行失败
BankClient-->>TxnService: 失败回执
TxnService->>LedgerService: 8b. 回退在途
TxnService->>TxnService: 9b. 更新状态 (Failed)
else 超时
Note over TxnService: 等待超时
TxnService->>CompService: 8c. 创建补偿任务
TxnService->>TxnService: 9c. 更新状态 (Timeout)
end
TxnService-->>Client: 返回结果
```
### 5.2 入金流程
```mermaid
sequenceDiagram
participant Bank
participant TxnService
participant LedgerService
Bank->>TxnService: 1. 银行入账通知
TxnService->>TxnService: 2. 检查 SourceKey 幂等
alt 重复入账
TxnService-->>Bank: 返回已处理
else 新入账
TxnService->>TxnService: 3. 创建交易 (Success)
TxnService->>LedgerService: 4. 增加个人余额
TxnService->>LedgerService: 5. 同步银行余额
TxnService-->>Bank: 处理成功
end
```
### 5.3 冲正流程
```mermaid
sequenceDiagram
participant Operator
participant TxnService
participant LedgerService
Operator->>TxnService: 1. 发起冲正
TxnService->>TxnService: 2. 检查是否可冲正
alt 可以冲正
TxnService->>LedgerService: 3. 反向记账
TxnService->>TxnService: 4. 更新状态 (Reversed)
TxnService-->>Operator: 冲正成功
else 不可冲正
TxnService-->>Operator: 冲正失败
end
```
## 六、领域服务
### 6.1 TransactionService
```rust
impl TransactionService {
// ========== 交易创建 ==========
// 创建转账交易
pub async fn transfer(&self, req: TransferRequest) -> Result<SystemTransaction>;
// 创建充值交易
pub async fn deposit(&self, req: DepositRequest) -> Result<SystemTransaction>;
// 创建提现交易
pub async fn withdraw(&self, req: WithdrawRequest) -> Result<SystemTransaction>;
// ========== 交易查询 ==========
// 获取交易详情
pub async fn get_transaction(&self, id: i64) -> Result<SystemTransaction>;
// 根据交易号查询
pub async fn get_by_txn_no(&self, txn_no: &str) -> Result<SystemTransaction>;
// 查询交易列表
pub async fn list_transactions(&self, query: TransactionQuery) -> Result<Vec<SystemTransaction>>;
// ========== 状态管理 ==========
// 提交到银行
pub async fn submit_to_bank(&self, id: i64) -> Result<String>;
// 确认交易
pub async fn confirm_transaction(&self, id: i64, bank_ref_no: &str) -> Result<()>;
// 标记失败
pub async fn mark_failed(&self, id: i64, reason: &str) -> Result<()>;
// 标记超时
pub async fn mark_timeout(&self, id: i64) -> Result<()>;
// 冲正交易
pub async fn reverse_transaction(&self, id: i64) -> Result<()>;
// ========== 幂等检查 ==========
// 检查 SourceKey 是否存在
pub async fn check_source_key(&self, source_key: &str) -> Result<Option<SystemTransaction>>;
}
```
## 七、仓储接口
### 7.1 SystemTransactionRepository
```rust
#[async_trait]
pub trait SystemTransactionRepository: Send + Sync {
async fn create(&self, req: &CreateSystemTransactionRequest) -> Result<SystemTransaction>;
async fn find_by_id(&self, id: i64) -> Result<Option<SystemTransaction>>;
async fn find_by_txn_no(&self, txn_no: &str) -> Result<Option<SystemTransaction>>;
async fn find_by_bank_ref_no(&self, bank_ref_no: &str) -> Result<Option<SystemTransaction>>;
async fn find_by_source_key(&self, source_key: &str) -> Result<Option<SystemTransaction>>;
async fn find_by_status(&self, status: TransactionStatus) -> Result<Vec<SystemTransaction>>;
async fn find_pending(&self) -> Result<Vec<SystemTransaction>>;
async fn find_needs_reconciliation(&self) -> Result<Vec<SystemTransaction>>;
async fn query(&self, query: &TransactionQuery) -> Result<Vec<SystemTransaction>>;
async fn update_status(&self, id: i64, status: TransactionStatus) -> Result<()>;
async fn set_bank_ref_no(&self, id: i64, bank_ref_no: &str) -> Result<()>;
async fn set_submitted_at(&self, id: i64) -> Result<()>;
async fn confirm(&self, id: i64, bank_ref_no: &str) -> Result<()>;
}
```
### 7.2 BankTransactionRepository
```rust
#[async_trait]
pub trait BankTransactionRepository: Send + Sync {
async fn create(&self, txn: &BankTransaction) -> Result<i64>;
async fn find_by_bank_ref_no(&self, bank_ref_no: &str) -> Result<Option<BankTransaction>>;
async fn find_unmatched(&self) -> Result<Vec<BankTransaction>>;
async fn update_match_status(&self, id: i64, status: MatchStatus, matched_txn_no: Option<&str>) -> Result<()>;
}
```
## 八、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/transactions/transfer | 发起转账 |
| POST | /api/v1/transactions/deposit | 发起充值 |
| POST | /api/v1/transactions/withdraw | 发起提现 |
| GET | /api/v1/transactions/:id | 获取交易详情 |
| GET | /api/v1/transactions | 获取交易列表 |
## 九、数据库表结构
### 9.1 system_transaction 表
```sql
CREATE TABLE system_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
txn_no VARCHAR(64) NOT NULL UNIQUE,
txn_type VARCHAR(32) NOT NULL,
from_account_id BIGINT,
to_account_id BIGINT,
amount DECIMAL(20, 2) NOT NULL,
status VARCHAR(32) DEFAULT 'created',
bank_ref_no VARCHAR(64),
source_key VARCHAR(256),
remark TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
confirmed_at TIMESTAMP NULL,
submitted_at TIMESTAMP NULL,
version INT DEFAULT 0,
INDEX idx_status (status),
INDEX idx_from_account (from_account_id),
INDEX idx_to_account (to_account_id),
INDEX idx_bank_ref_no (bank_ref_no),
INDEX idx_source_key (source_key),
INDEX idx_created_at (created_at)
);
```
### 9.2 bank_transaction 表
```sql
CREATE TABLE bank_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bank_ref_no VARCHAR(64) NOT NULL UNIQUE,
physical_account_id BIGINT NOT NULL,
txn_type VARCHAR(32) NOT NULL,
direction VARCHAR(16) NOT NULL,
amount DECIMAL(20, 2) NOT NULL,
counterparty_name VARCHAR(128),
counterparty_account VARCHAR(64),
txn_time TIMESTAMP NOT NULL,
sync_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
match_status VARCHAR(32) DEFAULT 'unmatched',
matched_txn_no VARCHAR(64),
remark TEXT,
INDEX idx_physical_account (physical_account_id),
INDEX idx_match_status (match_status),
INDEX idx_txn_time (txn_time)
);
```
## 十、并发控制
### 10.1 乐观锁
使用版本号防止并发更新冲突:
```rust
// 更新时检查版本
UPDATE system_transaction
SET status = ?, version = version + 1
WHERE id = ? AND version = ?
```
### 10.2 幂等性保证
1. **txn_no 唯一**:系统交易号全局唯一
2. **source_key 去重**:外部入账通过 SourceKey 去重
3. **状态机约束**:只允许合法的状态转移
## 十一、错误处理
| 错误类型 | 说明 | 处理方式 |
|----------|------|----------|
| InsufficientBalance | 余额不足 | 拒绝交易 |
| InvalidStateTransition | 非法状态转移 | 返回错误 |
| DuplicateTransaction | 重复交易 | 返回已有交易 |
| BankTimeout | 银行超时 | 创建补偿任务 |
| OptimisticLockFailed | 乐观锁冲突 | 重试 |

View File

@ -0,0 +1,454 @@
# 对账域 (Reconciliation Domain)
## 一、领域概述
对账域负责确保系统账务与银行账务的一致性,包括交易匹配、差异处理、手工补录、三账校验等功能。
## 二、核心概念
### 2.1 三账对账闭环
```
┌─────────────────────────────────────────────────────────┐
│ 三账校验公式 │
│ │
│ 总账余额 = 银行账余额 + 在途净额 │
│ │
│ ledger_total = bank_balance + transit_net │
└─────────────────────────────────────────────────────────┘
银行账: 银行实际余额(通过银行接口查询)
在途账: 已扣未确认的资金transit_amount
总账: 系统记账余额personal + labor + frozen
```
### 2.2 对账流程
```mermaid
graph TD
A[获取银行流水] --> B[获取系统交易]
B --> C[交易匹配]
C --> D{匹配结果}
D -->|匹配| E[标记已匹配]
D -->|系统未达| F[创建系统未达项]
D -->|银行未达| G[创建银行未达项]
D -->|金额不匹配| H[创建差异项]
E --> I[三账校验]
F --> I
G --> I
H --> I
I --> J{是否平衡}
J -->|是| K[对账完成]
J -->|否| L[需要审核]
```
## 三、核心实体
### 3.1 对账批次 (ReconciliationBatch)
```rust
pub struct ReconciliationBatch {
pub id: i64,
pub batch_no: String, // 批次编号
pub physical_account_id: i64, // 实体账户ID
pub recon_date: NaiveDate, // 对账日期
pub total_count: i32, // 总记录数
pub matched_count: i32, // 匹配数
pub mismatch_count: i32, // 不匹配数
pub status: ReconciliationStatus, // 状态
pub created_at: DateTime<Utc>, // 创建时间
pub completed_at: Option<DateTime<Utc>>, // 完成时间
// 三账对账结果
pub bank_total: Option<Decimal>, // 银行账汇总
pub transit_net: Option<Decimal>, // 在途净额
pub ledger_total: Option<Decimal>, // 总账汇总
pub three_account_balanced: Option<bool>, // 三账是否平衡
}
```
**核心方法**
```rust
impl ReconciliationBatch {
// 计算匹配率
pub fn match_rate(&self) -> f64 {
if self.total_count == 0 {
return 100.0;
}
(self.matched_count as f64 / self.total_count as f64) * 100.0
}
// 是否全部匹配
pub fn is_all_matched(&self) -> bool {
self.mismatch_count == 0
}
// 是否已完成三账对账
pub fn has_three_account_result(&self) -> bool {
self.three_account_balanced.is_some()
}
// 三账差异金额
pub fn three_account_difference(&self) -> Option<Decimal> {
match (self.ledger_total, self.bank_total, self.transit_net) {
(Some(ledger), Some(bank), Some(transit)) => {
Some(ledger - (bank + transit))
}
_ => None,
}
}
}
```
### 3.2 对账明细项 (ReconciliationItem)
```rust
pub struct ReconciliationItem {
pub id: i64,
pub batch_id: i64, // 批次ID
pub system_txn_no: Option<String>, // 系统交易号
pub bank_ref_no: Option<String>, // 银行参考号
pub system_amount: Option<Decimal>, // 系统金额
pub bank_amount: Option<Decimal>, // 银行金额
pub diff_amount: Decimal, // 差异金额
pub status: ReconciliationItemStatus, // 状态
pub remark: Option<String>, // 处理备注
pub created_at: DateTime<Utc>,
}
```
**核心方法**
```rust
impl ReconciliationItem {
// 检查是否需要手工处理
pub fn needs_manual_handling(&self) -> bool {
matches!(
self.status,
ReconciliationItemStatus::SystemUnreached
| ReconciliationItemStatus::BankUnreached
| ReconciliationItemStatus::AmountMismatch
)
}
}
```
### 3.3 手工补录 (ManualAdjustment)
```rust
pub struct ManualAdjustment {
pub id: i64,
pub adjustment_no: String, // 补录编号
pub related_txn_no: Option<String>, // 关联交易号
pub reconciliation_item_id: Option<i64>,// 关联对账项ID
pub adjustment_type: AdjustmentType, // 补录类型
pub account_id: i64, // 账户ID
pub amount: Decimal, // 金额
pub reason: String, // 原因说明
pub operator: String, // 操作人
pub approver: Option<String>, // 审批人
pub status: AdjustmentStatus, // 状态
pub created_at: DateTime<Utc>,
pub approved_at: Option<DateTime<Utc>>, // 审批时间
}
```
## 四、枚举类型
### 4.1 对账批次状态 (ReconciliationStatus)
```rust
pub enum ReconciliationStatus {
Processing, // 处理中
Completed, // 已完成
NeedReview, // 需要审核
}
```
### 4.2 对账项状态 (ReconciliationItemStatus)
```rust
pub enum ReconciliationItemStatus {
Matched, // 已匹配
SystemUnreached, // 系统未达(系统有银行无)
BankUnreached, // 银行未达(银行有系统无)
AmountMismatch, // 金额不匹配
Adjusted, // 已调整
}
```
| 状态 | 说明 | 处理方式 |
|------|------|----------|
| Matched | 系统与银行完全匹配 | 无需处理 |
| SystemUnreached | 系统有记录但银行无 | 核实后补录或冲销 |
| BankUnreached | 银行有记录但系统无 | 核实后补录 |
| AmountMismatch | 金额不一致 | 核实后调整 |
| Adjusted | 已手工调整 | 已处理 |
### 4.3 补录类型 (AdjustmentType)
```rust
pub enum AdjustmentType {
Add, // 补录(新增)
Reverse, // 冲销
Modify, // 修改
}
```
### 4.4 补录状态 (AdjustmentStatus)
```rust
pub enum AdjustmentStatus {
Pending, // 待审批
Approved, // 已审批
Rejected, // 已拒绝
}
```
## 五、对账匹配规则
### 5.1 匹配策略
1. **精确匹配**:银行交易号 = 系统银行参考号
2. **金额匹配**:金额完全一致
3. **时间匹配**:交易时间在允许范围内
4. **方向匹配**:入账/出账方向一致
### 5.2 匹配优先级
```
1. 按银行交易号精确匹配
2. 按金额 + 时间 + 方向模糊匹配
3. 按 SourceKey 匹配(入账)
4. 无法匹配则标记为未达
```
### 5.3 差异处理
| 差异类型 | 处理方式 |
|----------|----------|
| 金额差异 < 1分 | 自动调平 |
| 金额差异 < 100元 | 人工审核 |
| 金额差异 >= 100元 | 升级处理 |
## 六、三账校验
### 6.1 校验结果
```rust
pub struct ThreeAccountResult {
pub bank_balance: Decimal, // 银行账余额
pub transit_net: Decimal, // 在途净额
pub ledger_total: Decimal, // 总账余额
pub is_balanced: bool, // 是否平衡
pub difference: Decimal, // 差异金额
}
```
### 6.2 校验公式
```
期望总账 = 银行余额 + 在途净额
差异 = 实际总账 - 期望总账
平衡 = (差异 == 0)
```
### 6.3 不平衡原因分析
| 原因 | 说明 | 处理 |
|------|------|------|
| 银行延迟 | 银行入账延迟 | 等待银行同步 |
| 系统错误 | 记账错误 | 查找并修正 |
| 在途异常 | 在途状态不正确 | 核实在途交易 |
| 并发问题 | 对账期间有新交易 | 锁定后重新对账 |
## 七、领域服务
### 7.1 ReconciliationService
```rust
impl ReconciliationService {
// ========== 对账批次管理 ==========
// 执行对账
pub async fn run_reconciliation(&self, req: CreateReconciliationBatchRequest) -> Result<ReconciliationBatch>;
// 获取对账批次
pub async fn get_batch(&self, id: i64) -> Result<ReconciliationBatch>;
// 获取对账明细
pub async fn get_batch_items(&self, batch_id: i64) -> Result<Vec<ReconciliationItem>>;
// ========== 三账校验 ==========
// 执行三账校验
pub async fn verify_three_accounts(&self, physical_account_id: i64) -> Result<ThreeAccountVerificationResult>;
// 更新批次三账结果
pub async fn update_three_account_result(&self, batch_id: i64, result: &ThreeAccountResult) -> Result<()>;
// ========== 手工补录 ==========
// 创建手工补录
pub async fn create_adjustment(&self, req: CreateManualAdjustmentRequest) -> Result<ManualAdjustment>;
// 审批补录
pub async fn approve_adjustment(&self, id: i64, approver: &str) -> Result<()>;
// 拒绝补录
pub async fn reject_adjustment(&self, id: i64, approver: &str, reason: &str) -> Result<()>;
// 获取待审批补录
pub async fn list_pending_adjustments(&self) -> Result<Vec<ManualAdjustment>>;
// ========== 统计查询 ==========
// 获取对账统计
pub async fn get_stats(&self, batch_id: i64) -> Result<ReconciliationStats>;
}
```
### 7.2 三账校验实现
```rust
pub async fn verify_three_accounts(&self, physical_account_id: i64) -> Result<ThreeAccountVerificationResult> {
// 1. 获取银行余额
let bank_balance = self.bank_client.query_balance(physical_account_id).await?;
// 2. 计算在途净额
let transit_net = self.ledger_service.get_transit_total(physical_account_id).await?;
// 3. 计算总账余额
let ledger_total = self.ledger_service.get_ledger_total(physical_account_id).await?;
// 4. 校验
let expected_total = bank_balance + transit_net;
let difference = ledger_total - expected_total;
let is_balanced = difference.abs() < Decimal::new(1, 2); // 允许1分误差
Ok(ThreeAccountVerificationResult {
physical_account_id,
bank_balance,
transit_net,
ledger_total,
expected_total,
difference,
is_balanced,
verified_at: Utc::now(),
})
}
```
## 八、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/reconciliation/run | 执行对账 |
| GET | /api/v1/reconciliation/batches/:id | 获取对账批次 |
| GET | /api/v1/reconciliation/batches/:id/items | 获取对账明细 |
| GET | /api/v1/reconciliation/three-account/:id | 三账校验 |
| POST | /api/v1/reconciliation/adjustments | 创建手工补录 |
| POST | /api/v1/reconciliation/adjustments/:id/approve | 审批补录 |
| POST | /api/v1/reconciliation/adjustments/:id/reject | 拒绝补录 |
| GET | /api/v1/reconciliation/adjustments/pending | 获取待审批补录 |
## 九、数据库表结构
### 9.1 reconciliation_batch 表
```sql
CREATE TABLE reconciliation_batch (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
batch_no VARCHAR(64) NOT NULL UNIQUE,
physical_account_id BIGINT NOT NULL,
recon_date DATE NOT NULL,
total_count INT DEFAULT 0,
matched_count INT DEFAULT 0,
mismatch_count INT DEFAULT 0,
status VARCHAR(32) DEFAULT 'processing',
bank_total DECIMAL(20, 2),
transit_net DECIMAL(20, 2),
ledger_total DECIMAL(20, 2),
three_account_balanced BOOLEAN,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
INDEX idx_physical_account (physical_account_id),
INDEX idx_recon_date (recon_date),
INDEX idx_status (status)
);
```
### 9.2 reconciliation_item 表
```sql
CREATE TABLE reconciliation_item (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
batch_id BIGINT NOT NULL,
system_txn_no VARCHAR(64),
bank_ref_no VARCHAR(64),
system_amount DECIMAL(20, 2),
bank_amount DECIMAL(20, 2),
diff_amount DECIMAL(20, 2) DEFAULT 0.00,
status VARCHAR(32) NOT NULL,
remark TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (batch_id) REFERENCES reconciliation_batch(id),
INDEX idx_batch (batch_id),
INDEX idx_status (status)
);
```
### 9.3 manual_adjustment 表
```sql
CREATE TABLE manual_adjustment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
adjustment_no VARCHAR(64) NOT NULL UNIQUE,
related_txn_no VARCHAR(64),
reconciliation_item_id BIGINT,
adjustment_type VARCHAR(32) NOT NULL,
account_id BIGINT NOT NULL,
amount DECIMAL(20, 2) NOT NULL,
reason TEXT NOT NULL,
operator VARCHAR(64) NOT NULL,
approver VARCHAR(64),
status VARCHAR(32) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP NULL,
INDEX idx_status (status),
INDEX idx_operator (operator)
);
```
## 十、对账时机
### 10.1 自动对账
- 每日定时对账T+1
- 配置对账频率(按账户)
- 批量处理
### 10.2 手动对账
- 运营人员触发
- 指定日期范围
- 单账户或多账户
### 10.3 实时对账
- 大额交易实时校验
- 异常交易即时告警
- 关键节点检查
## 十一、告警机制
| 告警级别 | 触发条件 | 处理方式 |
|----------|----------|----------|
| 警告 | 匹配率 < 99% | 记录日志 |
| 严重 | 三账不平衡 | 通知运营 |
| 紧急 | 大额差异 | 升级处理 |

View File

@ -0,0 +1,488 @@
# 补偿域 (Compensation Domain)
## 一、领域概述
补偿域负责处理交易过程中的异常情况,包括超时检测、失败重试、死信队列处理等。该域是保障交易最终一致性的关键组件。
## 二、核心概念
### 2.1 补偿机制
```
┌─────────────────────────────────────────────────────────┐
│ 补偿流程 │
│ │
│ 交易超时 ──► 创建补偿任务 ──► 重试处理 ──► 成功/死信 │
│ │ │
│ └──► 指数退避 ──► 最大重试次数 │
└─────────────────────────────────────────────────────────┘
```
### 2.2 指数退避策略
```
重试间隔 = 基础间隔 × 2^重试次数
示例基础间隔30秒
- 第1次重试: 30秒后
- 第2次重试: 60秒后
- 第3次重试: 120秒后
- 第4次重试: 240秒后
```
### 2.3 死信队列
超过最大重试次数的任务进入死信队列,需要人工处理。
```
正常队列 ──► 重试失败 ──► 死信队列 ──► 人工处理
```
## 三、核心实体
### 3.1 补偿任务 (CompensationTask)
```rust
pub struct CompensationTask {
pub id: i64,
pub txn_no: String, // 关联交易号
pub task_type: CompensationTaskType, // 任务类型
pub status: CompensationTaskStatus, // 任务状态
pub retry_count: i32, // 重试次数
pub max_retries: i32, // 最大重试次数
pub next_retry_at: Option<DateTime<Utc>>,// 下次重试时间
pub error_message: Option<String>, // 错误信息
pub created_at: DateTime<Utc>, // 创建时间
pub updated_at: DateTime<Utc>, // 更新时间
pub completed_at: Option<DateTime<Utc>>, // 完成时间
}
```
**核心方法**
```rust
impl CompensationTask {
// 检查是否可重试
pub fn can_retry(&self) -> bool {
self.status == CompensationTaskStatus::Failed
&& self.retry_count < self.max_retries
}
// 检查是否应该进入死信队列
pub fn should_dead_letter(&self) -> bool {
self.status == CompensationTaskStatus::Failed
&& self.retry_count >= self.max_retries
}
// 检查是否已到重试时间
pub fn is_ready_for_retry(&self) -> bool {
if let Some(next_retry) = self.next_retry_at {
Utc::now() >= next_retry
} else {
true
}
}
}
```
### 3.2 超时配置 (TimeoutConfig)
```rust
pub struct TimeoutConfig {
pub bank_timeout_seconds: i64, // 银行交易超时秒数
pub check_interval_seconds: i64, // 检查间隔秒数
pub retry_base_interval_seconds: i64, // 重试间隔基数
pub max_retries: i32, // 最大重试次数
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
bank_timeout_seconds: 300, // 5分钟
check_interval_seconds: 60, // 1分钟检查一次
retry_base_interval_seconds: 30, // 30秒基础重试间隔
max_retries: 3,
}
}
}
```
**计算下次重试时间**
```rust
impl TimeoutConfig {
pub fn calculate_next_retry(&self, retry_count: i32) -> DateTime<Utc> {
let delay_seconds = self.retry_base_interval_seconds * (2_i64.pow(retry_count as u32));
Utc::now() + chrono::Duration::seconds(delay_seconds)
}
}
```
## 四、枚举类型
### 4.1 补偿任务类型 (CompensationTaskType)
```rust
pub enum CompensationTaskType {
TimeoutCheck, // 超时检查
Reconcile, // 对账补偿
Reverse, // 冲正处理
Retry, // 重试
}
```
| 类型 | 触发条件 | 处理方式 |
|------|----------|----------|
| TimeoutCheck | 交易提交银行后超时 | 查询银行状态 |
| Reconcile | 对账发现差异 | 调整账务 |
| Reverse | 需要冲正 | 执行冲正 |
| Retry | 失败需重试 | 重新提交 |
### 4.2 补偿任务状态 (CompensationTaskStatus)
```rust
pub enum CompensationTaskStatus {
Pending, // 待处理
Processing, // 处理中
Completed, // 已完成
Failed, // 失败
DeadLetter, // 死信
}
```
**状态转移图**
```
┌─────────┐ ┌────────────┐ ┌───────────┐
│ Pending │───►│ Processing │───►│ Completed │
└─────────┘ └────────────┘ └───────────┘
┌──────────┐ ┌────────────┐
│ Failed │───►│ DeadLetter │
└──────────┘ └────────────┘
┌──────────┐
│ Retry │ (返回 Pending)
└──────────┘
```
## 五、处理结果
### 5.1 补偿处理结果 (CompensationResult)
```rust
pub struct CompensationResult {
pub task_id: i64,
pub success: bool,
pub message: String,
pub needs_retry: bool,
}
```
### 5.2 超时检测结果 (TimeoutDetectionResult)
```rust
pub struct TimeoutDetectionResult {
pub timeout_count: i32, // 检测到的超时交易数
pub task_created: i32, // 创建的补偿任务数
pub failed_count: i32, // 处理失败数
}
```
## 六、领域服务
### 6.1 CompensationService
```rust
impl CompensationService {
// ========== 超时检测 ==========
// 检测超时交易
pub async fn detect_timeout_transactions(&self) -> Result<TimeoutDetectionResult>;
// ========== 任务管理 ==========
// 创建补偿任务
pub async fn create_compensation_task(&self, txn_no: &str, task_type: CompensationTaskType) -> Result<CompensationTask>;
// 处理单个补偿任务
pub async fn process_task(&self, task_id: i64) -> Result<CompensationResult>;
// 批量处理待处理任务
pub async fn process_pending_tasks(&self) -> Result<Vec<CompensationResult>>;
// ========== 重试管理 ==========
// 处理待重试任务
pub async fn process_ready_for_retry(&self) -> Result<Vec<CompensationResult>>;
// 标记任务完成
pub async fn mark_completed(&self, task_id: i64) -> Result<()>;
// 标记任务失败并计划重试
pub async fn mark_failed_with_retry(&self, task_id: i64, error: &str) -> Result<()>;
// ========== 死信处理 ==========
// 处理死信任务
pub async fn process_dead_letter_tasks(&self) -> Result<Vec<CompensationResult>>;
// 移入死信队列
pub async fn move_to_dead_letter(&self, task_id: i64) -> Result<()>;
// 手动重试死信任务
pub async fn retry_dead_letter(&self, task_id: i64) -> Result<()>;
}
```
### 6.2 超时检测实现
```rust
pub async fn detect_timeout_transactions(&self) -> Result<TimeoutDetectionResult> {
// 查找需要检查超时的交易
let bank_submitted_txns = self.txn_repo
.find_by_status(TransactionStatus::BankSubmitted)
.await?;
let mut timeout_count = 0;
let mut task_created = 0;
let mut failed_count = 0;
for txn in bank_submitted_txns {
// 检查是否超时
if txn.is_timeout(self.config.bank_timeout_seconds) {
timeout_count += 1;
// 检查是否已有补偿任务
if self.task_repo.has_pending_task(&txn.txn_no, CompensationTaskType::TimeoutCheck).await? {
continue;
}
// 更新交易状态为 Timeout
match self.txn_repo.update_status(txn.id, TransactionStatus::Timeout).await {
Ok(_) => {
// 创建补偿任务
match self.create_compensation_task(&txn.txn_no, CompensationTaskType::TimeoutCheck).await {
Ok(_) => task_created += 1,
Err(e) => {
warn!("创建补偿任务失败: {}", e);
failed_count += 1;
}
}
}
Err(e) => {
warn!("更新交易状态失败: {}", e);
failed_count += 1;
}
}
}
}
Ok(TimeoutDetectionResult { timeout_count, task_created, failed_count })
}
```
### 6.3 任务处理实现
```rust
pub async fn process_task(&self, task_id: i64) -> Result<CompensationResult> {
let task = self.task_repo.find_by_id(task_id).await?
.ok_or_else(|| AppError::NotFound("补偿任务不存在".into()))?;
// 更新状态为处理中
self.task_repo.update_status(task_id, CompensationTaskStatus::Processing, None).await?;
match task.task_type {
CompensationTaskType::TimeoutCheck => {
self.handle_timeout_check(&task).await
}
CompensationTaskType::Reconcile => {
self.handle_reconcile(&task).await
}
CompensationTaskType::Reverse => {
self.handle_reverse(&task).await
}
CompensationTaskType::Retry => {
self.handle_retry(&task).await
}
}
}
async fn handle_timeout_check(&self, task: &CompensationTask) -> Result<CompensationResult> {
// 1. 获取关联交易
let txn = self.txn_repo.find_by_txn_no(&task.txn_no).await?
.ok_or_else(|| AppError::NotFound("交易不存在".into()))?;
// 2. 查询银行状态
let bank_status = self.bank_client.query_transaction_status(&txn.bank_ref_no).await;
match bank_status {
Ok(status) if status.is_success() => {
// 银行确认成功,结转在途
self.ledger_service.settle_transit(
txn.from_account_id.unwrap(),
AccountType::Physical,
txn.amount
).await?;
self.txn_repo.update_status(txn.id, TransactionStatus::Success).await?;
self.task_repo.mark_completed(task.id).await?;
Ok(CompensationResult {
task_id: task.id,
success: true,
message: "银行确认成功".to_string(),
needs_retry: false,
})
}
Ok(status) if status.is_failed() => {
// 银行确认失败,回退在途
self.ledger_service.rollback_transit(
txn.from_account_id.unwrap(),
AccountType::Physical,
txn.amount
).await?;
self.txn_repo.update_status(txn.id, TransactionStatus::Failed).await?;
self.task_repo.mark_completed(task.id).await?;
Ok(CompensationResult {
task_id: task.id,
success: true,
message: "银行确认失败,已回退".to_string(),
needs_retry: false,
})
}
_ => {
// 仍然无法确认,需要重试
Ok(CompensationResult {
task_id: task.id,
success: false,
message: "无法确认银行状态".to_string(),
needs_retry: true,
})
}
}
}
```
## 七、仓储接口
### 7.1 CompensationTaskRepository
```rust
#[async_trait]
pub trait CompensationTaskRepository: Send + Sync {
// 创建任务
async fn create(&self, task: &CompensationTask) -> Result<i64>;
// 查询
async fn find_by_id(&self, id: i64) -> Result<Option<CompensationTask>>;
async fn find_by_txn_no(&self, txn_no: &str) -> Result<Vec<CompensationTask>>;
async fn find_pending(&self) -> Result<Vec<CompensationTask>>;
async fn find_ready_for_retry(&self) -> Result<Vec<CompensationTask>>;
async fn find_dead_letter(&self) -> Result<Vec<CompensationTask>>;
// 状态更新
async fn update_status(&self, id: i64, status: CompensationTaskStatus, error_message: Option<&str>) -> Result<()>;
async fn increment_retry(&self, id: i64, next_retry_at: DateTime<Utc>) -> Result<()>;
async fn mark_completed(&self, id: i64) -> Result<()>;
async fn mark_dead_letter(&self, id: i64) -> Result<()>;
// 检查
async fn has_pending_task(&self, txn_no: &str, task_type: CompensationTaskType) -> Result<bool>;
}
```
## 八、数据库表结构
### 8.1 compensation_task 表
```sql
CREATE TABLE compensation_task (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
txn_no VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL,
status VARCHAR(32) DEFAULT 'pending',
retry_count INT DEFAULT 0,
max_retries INT DEFAULT 3,
next_retry_at TIMESTAMP NULL,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
INDEX idx_txn_no (txn_no),
INDEX idx_status (status),
INDEX idx_next_retry (next_retry_at),
INDEX idx_task_type (task_type)
);
```
## 九、调度机制
### 9.1 定时任务
| 任务 | 频率 | 说明 |
|------|------|------|
| 超时检测 | 每分钟 | 扫描超时交易 |
| 待处理任务 | 每30秒 | 处理待处理任务 |
| 重试任务 | 每分钟 | 处理到期重试任务 |
| 死信巡检 | 每小时 | 检查死信队列 |
### 9.2 并发控制
- 分布式锁防止重复处理
- 乐观锁防止并发更新
- 任务状态机保证正确流转
## 十、监控告警
### 10.1 监控指标
| 指标 | 说明 | 阈值 |
|------|------|------|
| pending_tasks | 待处理任务数 | > 100 告警 |
| failed_tasks | 失败任务数 | > 10 告警 |
| dead_letter_count | 死信数量 | > 0 告警 |
| avg_retry_count | 平均重试次数 | > 2 关注 |
### 10.2 告警规则
```yaml
alerts:
- name: compensation_task_backlog
condition: pending_tasks > 100
severity: warning
- name: dead_letter_alert
condition: dead_letter_count > 0
severity: critical
- name: high_failure_rate
condition: failed_tasks / total_tasks > 0.1
severity: error
```
## 十一、最佳实践
### 11.1 任务设计
1. 任务幂等:同一任务多次执行结果相同
2. 原子操作:任务内操作要么全成功要么全失败
3. 超时设置:合理设置任务超时时间
### 11.2 重试策略
1. 指数退避:避免系统过载
2. 最大重试:防止无限重试
3. 死信处理:异常任务人工介入
### 11.3 监控运维
1. 任务可追溯:完整记录任务生命周期
2. 告警及时:异常情况及时通知
3. 手动干预:提供手动处理入口

503
docs/domains/06-points.md Normal file
View File

@ -0,0 +1,503 @@
# 积分域 (Points Domain)
## 一、领域概述
积分域负责管理用户积分的完整生命周期,包括积分获取、消费、转移和过期处理。支持多种积分类型和灵活的积分规则配置。
## 二、核心实体
### 2.1 积分账户 (PointsAccount)
```rust
pub struct PointsAccount {
pub id: i64,
pub sub_account_id: i64, // 关联子账户ID
pub points_type: PointsType, // 积分类型
pub balance: Decimal, // 积分余额
pub total_earned: Decimal, // 累计获得
pub total_spent: Decimal, // 累计消费
pub total_expired: Decimal, // 累计过期
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
```
**核心方法**
```rust
impl PointsAccount {
// 检查是否有足够积分
pub fn has_sufficient_points(&self, amount: Decimal) -> bool {
self.balance >= amount
}
// 增加积分
pub fn add_points(&mut self, amount: Decimal) {
self.balance += amount;
self.total_earned += amount;
self.updated_at = Utc::now();
}
// 减少积分
pub fn subtract_points(&mut self, amount: Decimal) {
self.balance -= amount;
self.total_spent += amount;
self.updated_at = Utc::now();
}
// 过期积分
pub fn expire_points(&mut self, amount: Decimal) {
self.balance -= amount;
self.total_expired += amount;
self.updated_at = Utc::now();
}
}
```
### 2.2 积分交易 (PointsTransaction)
```rust
pub struct PointsTransaction {
pub id: i64,
pub txn_no: String, // 交易编号
pub points_account_id: i64, // 积分账户ID
pub txn_type: PointsTransactionType, // 交易类型
pub amount: Decimal, // 积分数量
pub balance_before: Decimal, // 交易前余额
pub balance_after: Decimal, // 交易后余额
pub related_business_id: Option<String>, // 关联业务ID
pub remark: Option<String>, // 备注
pub created_at: DateTime<Utc>,
}
```
### 2.3 积分规则 (PointsRule)
```rust
pub struct PointsRule {
pub id: i64,
pub name: String, // 规则名称
pub points_type: PointsType, // 积分类型
pub rule_type: String, // 规则类型
pub config: serde_json::Value, // 规则配置
pub enabled: bool, // 是否启用
pub created_at: DateTime<Utc>,
}
```
**规则配置示例**
```json
{
"earn_rule": {
"base_points": 100,
"multiplier": 1.5,
"max_daily": 1000,
"valid_days": 365
},
"spend_rule": {
"min_points": 100,
"exchange_rate": 0.01
}
}
```
## 三、枚举类型
### 3.1 积分类型 (PointsType)
```rust
pub enum PointsType {
Production, // 生产积分
Management, // 管理积分
Other, // 其他积分
}
```
| 类型 | 说明 | 获取方式 |
|------|------|----------|
| Production | 生产积分 | 完成生产任务 |
| Management | 管理积分 | 管理工作奖励 |
| Other | 其他积分 | 活动奖励等 |
### 3.2 积分交易类型 (PointsTransactionType)
```rust
pub enum PointsTransactionType {
Earn, // 获取
Spend, // 消费
Transfer, // 转移
Expire, // 过期
Adjust, // 调整
}
```
## 四、业务流程
### 4.1 积分获取流程
```mermaid
sequenceDiagram
participant Business
participant PointsService
participant PointsRepo
Business->>PointsService: 1. 发起积分获取
PointsService->>PointsService: 2. 校验规则
PointsService->>PointsRepo: 3. 获取积分账户
PointsService->>PointsService: 4. 计算积分
PointsService->>PointsRepo: 5. 更新余额
PointsService->>PointsRepo: 6. 记录交易
PointsService-->>Business: 7. 返回结果
```
### 4.2 积分消费流程
```mermaid
sequenceDiagram
participant User
participant PointsService
participant PointsRepo
User->>PointsService: 1. 发起积分消费
PointsService->>PointsRepo: 2. 获取积分账户
PointsService->>PointsService: 3. 检查余额
alt 余额充足
PointsService->>PointsRepo: 4a. 扣减余额
PointsService->>PointsRepo: 5a. 记录交易
PointsService-->>User: 6a. 消费成功
else 余额不足
PointsService-->>User: 4b. 返回余额不足
end
```
### 4.3 积分转移流程
```mermaid
sequenceDiagram
participant UserA
participant PointsService
participant PointsRepo
UserA->>PointsService: 1. 发起转移
PointsService->>PointsRepo: 2. 获取转出账户
PointsService->>PointsService: 3. 检查余额
PointsService->>PointsRepo: 4. 获取转入账户
PointsService->>PointsRepo: 5. 扣减转出账户
PointsService->>PointsRepo: 6. 增加转入账户
PointsService->>PointsRepo: 7. 记录两笔交易
PointsService-->>UserA: 8. 转移成功
```
## 五、领域服务
### 5.1 PointsService
```rust
impl PointsService {
// ========== 账户管理 ==========
// 获取积分账户
pub async fn get_accounts(&self, sub_account_id: i64) -> Result<Vec<PointsAccount>>;
// 创建积分账户
pub async fn create_account(&self, req: CreatePointsAccountRequest) -> Result<PointsAccount>;
// ========== 积分操作 ==========
// 获取积分
pub async fn earn_points(&self, req: PointsTransactionRequest) -> Result<PointsTransaction>;
// 消费积分
pub async fn spend_points(&self, req: PointsTransactionRequest) -> Result<PointsTransaction>;
// 转移积分
pub async fn transfer_points(&self, req: PointsTransferRequest) -> Result<(PointsTransaction, PointsTransaction)>;
// 调整积分
pub async fn adjust_points(&self, req: PointsTransactionRequest) -> Result<PointsTransaction>;
// ========== 过期处理 ==========
// 处理过期积分
pub async fn process_expired_points(&self) -> Result<i32>;
// ========== 查询 ==========
// 查询积分交易
pub async fn list_transactions(&self, query: PointsTransactionQuery) -> Result<Vec<PointsTransaction>>;
// 获取积分统计
pub async fn get_statistics(&self, account_id: i64) -> Result<PointsStatistics>;
}
```
### 5.2 积分获取实现
```rust
pub async fn earn_points(&self, req: PointsTransactionRequest) -> Result<PointsTransaction> {
// 1. 获取积分账户
let mut account = self.account_repo
.find_by_id(req.points_account_id)
.await?
.ok_or_else(|| AppError::NotFound("积分账户不存在".into()))?;
// 2. 校验规则(可选)
if let Some(rule) = self.get_earn_rule(&account.points_type).await? {
self.validate_earn_rule(&rule, req.amount)?;
}
// 3. 记录交易前余额
let balance_before = account.balance;
// 4. 增加积分
account.add_points(req.amount);
// 5. 更新账户
self.account_repo.update(&account).await?;
// 6. 创建交易记录
let txn = PointsTransaction {
id: 0,
txn_no: self.generate_txn_no(),
points_account_id: account.id,
txn_type: PointsTransactionType::Earn,
amount: req.amount,
balance_before,
balance_after: account.balance,
related_business_id: req.related_business_id,
remark: req.remark,
created_at: Utc::now(),
};
let txn_id = self.txn_repo.create(&txn).await?;
Ok(PointsTransaction { id: txn_id, ..txn })
}
```
### 5.3 积分转移实现
```rust
pub async fn transfer_points(&self, req: PointsTransferRequest) -> Result<(PointsTransaction, PointsTransaction)> {
// 1. 获取转出账户
let mut from_account = self.account_repo
.find_by_id(req.from_account_id)
.await?
.ok_or_else(|| AppError::NotFound("转出账户不存在".into()))?;
// 2. 检查余额
if !from_account.has_sufficient_points(req.amount) {
return Err(AppError::InsufficientBalance {
available: from_account.balance,
required: req.amount,
});
}
// 3. 获取转入账户
let mut to_account = self.account_repo
.find_by_id(req.to_account_id)
.await?
.ok_or_else(|| AppError::NotFound("转入账户不存在".into()))?;
// 4. 执行转移
let from_before = from_account.balance;
let to_before = to_account.balance;
from_account.subtract_points(req.amount);
to_account.add_points(req.amount);
// 5. 更新账户
self.account_repo.update(&from_account).await?;
self.account_repo.update(&to_account).await?;
// 6. 创建交易记录
let txn_no = self.generate_txn_no();
let from_txn = PointsTransaction {
id: 0,
txn_no: format!("{}-OUT", txn_no),
points_account_id: from_account.id,
txn_type: PointsTransactionType::Transfer,
amount: -req.amount,
balance_before: from_before,
balance_after: from_account.balance,
related_business_id: Some(format!("TRANSFER_TO_{}", to_account.id)),
remark: req.remark.clone(),
created_at: Utc::now(),
};
let to_txn = PointsTransaction {
id: 0,
txn_no: format!("{}-IN", txn_no),
points_account_id: to_account.id,
txn_type: PointsTransactionType::Transfer,
amount: req.amount,
balance_before: to_before,
balance_after: to_account.balance,
related_business_id: Some(format!("TRANSFER_FROM_{}", from_account.id)),
remark: req.remark,
created_at: Utc::now(),
};
let from_id = self.txn_repo.create(&from_txn).await?;
let to_id = self.txn_repo.create(&to_txn).await?;
Ok((
PointsTransaction { id: from_id, ..from_txn },
PointsTransaction { id: to_id, ..to_txn },
))
}
```
## 六、仓储接口
### 6.1 PointsAccountRepository
```rust
#[async_trait]
pub trait PointsAccountRepository: Send + Sync {
async fn create(&self, account: &PointsAccount) -> Result<i64>;
async fn find_by_id(&self, id: i64) -> Result<Option<PointsAccount>>;
async fn find_by_sub_account(&self, sub_account_id: i64) -> Result<Vec<PointsAccount>>;
async fn update(&self, account: &PointsAccount) -> Result<()>;
}
```
### 6.2 PointsTransactionRepository
```rust
#[async_trait]
pub trait PointsTransactionRepository: Send + Sync {
async fn create(&self, txn: &PointsTransaction) -> Result<i64>;
async fn find_by_account(&self, account_id: i64, query: &PointsTransactionQuery) -> Result<Vec<PointsTransaction>>;
async fn find_by_txn_no(&self, txn_no: &str) -> Result<Option<PointsTransaction>>;
}
```
## 七、API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/v1/points/accounts/:sub_account_id | 获取积分账户 |
| POST | /api/v1/points/earn | 获取积分 |
| POST | /api/v1/points/spend | 消费积分 |
| POST | /api/v1/points/transfer | 转移积分 |
| GET | /api/v1/points/transactions | 查询积分交易 |
## 八、数据库表结构
### 8.1 points_account 表
```sql
CREATE TABLE points_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sub_account_id BIGINT NOT NULL,
points_type VARCHAR(32) NOT NULL,
balance DECIMAL(20, 2) DEFAULT 0.00,
total_earned DECIMAL(20, 2) DEFAULT 0.00,
total_spent DECIMAL(20, 2) DEFAULT 0.00,
total_expired DECIMAL(20, 2) DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE INDEX idx_sub_account_type (sub_account_id, points_type),
INDEX idx_points_type (points_type)
);
```
### 8.2 points_transaction 表
```sql
CREATE TABLE points_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
txn_no VARCHAR(64) NOT NULL UNIQUE,
points_account_id BIGINT NOT NULL,
txn_type VARCHAR(32) NOT NULL,
amount DECIMAL(20, 2) NOT NULL,
balance_before DECIMAL(20, 2) NOT NULL,
balance_after DECIMAL(20, 2) NOT NULL,
related_business_id VARCHAR(128),
remark TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (points_account_id) REFERENCES points_account(id),
INDEX idx_account (points_account_id),
INDEX idx_txn_type (txn_type),
INDEX idx_created_at (created_at)
);
```
### 8.3 points_rule 表
```sql
CREATE TABLE points_rule (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
points_type VARCHAR(32) NOT NULL,
rule_type VARCHAR(32) NOT NULL,
config JSON NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_points_type (points_type),
INDEX idx_enabled (enabled)
);
```
## 九、积分规则
### 9.1 获取规则
| 参数 | 说明 | 示例 |
|------|------|------|
| base_points | 基础积分 | 100 |
| multiplier | 倍率 | 1.5 |
| max_daily | 每日上限 | 1000 |
| valid_days | 有效期(天) | 365 |
### 9.2 消费规则
| 参数 | 说明 | 示例 |
|------|------|------|
| min_points | 最低消费 | 100 |
| exchange_rate | 兑换比例 | 0.01 |
### 9.3 过期规则
| 参数 | 说明 | 示例 |
|------|------|------|
| expire_days | 过期天数 | 365 |
| notify_before | 提前通知天数 | 30 |
## 十、统计报表
### 10.1 账户统计
```rust
pub struct PointsStatistics {
pub account_id: i64,
pub balance: Decimal,
pub total_earned: Decimal,
pub total_spent: Decimal,
pub total_expired: Decimal,
pub earn_count: i64,
pub spend_count: i64,
pub transfer_in_count: i64,
pub transfer_out_count: i64,
}
```
### 10.2 周期统计
- 日统计:每日积分变动
- 月统计:月度积分汇总
- 年统计:年度积分报表
## 十一、注意事项
1. **积分精度**:使用 Decimal 类型保留2位小数
2. **并发控制**:积分操作需加锁,防止超发
3. **审计追踪**:所有积分变动必须有交易记录
4. **过期处理**:定期扫描处理过期积分

View File

@ -0,0 +1,356 @@
# 前后端对接清单
## 一、对接状态总览
### 1.1 后端 API 统计
| 领域 | 端点总数 | 已对接 | 未对接 |
|------|----------|--------|--------|
| Account | 11 | 11 | 0 |
| Transaction | 5 | 3 | 2 |
| Ledger | 3 | 0 | 3 |
| Reconciliation | 8 | 4 | 4 |
| Points | 5 | 0 | 5 |
| **总计** | **32** | **18** | **14** |
### 1.2 前端多余 API
前端定义了以下 API 但后端尚未实现:
| 前端方法 | 预期端点 | 状态 |
|----------|----------|------|
| submitToBank | POST /transactions/:id/submit | 待补充 |
| cancelTransaction | POST /transactions/:id/cancel | 待补充 |
| retryTransaction | POST /transactions/:id/retry | 待补充 |
| getTransactionStatus | GET /transactions/:id/status | 待补充 |
| getBankStatements | GET /bank-statements | 待补充 |
| getTransactionStats | GET /transactions/stats | 待补充 |
| executeBatch | POST /reconciliation/batch | 待补充 |
| getAdjustments | GET /reconciliation/adjustments | 待补充 |
| getStats | GET /reconciliation/stats | 待补充 |
| exportReport | GET /reconciliation/export | 待补充 |
## 二、详细对接清单
### 2.1 账户 API 对接
| 后端端点 | 前端方法 | 状态 | 说明 |
|----------|----------|------|------|
| POST /physical-accounts | AccountAPI.createPhysicalAccount | ✅ 已对接 | |
| GET /physical-accounts | AccountAPI.getPhysicalAccounts | ✅ 已对接 | |
| GET /physical-accounts/:id | AccountAPI.getPhysicalAccount | ✅ 已对接 | |
| POST /physical-accounts/:id/freeze | AccountAPI.freezeAccount | ✅ 已对接 | |
| POST /physical-accounts/:id/unfreeze | AccountAPI.unfreezeAccount | ✅ 已对接 | |
| POST /sub-accounts | AccountAPI.createSubAccount | ✅ 已对接 | |
| GET /sub-accounts/:id | AccountAPI.getSubAccount | ✅ 已对接 | |
| GET /sub-accounts/:id/balance | AccountAPI.getSubAccountBalance | ✅ 已对接 | |
| POST /sub-accounts/:id/freeze | AccountAPI.freezeSubAccount | ✅ 已对接 | |
| POST /sub-accounts/:id/unfreeze | AccountAPI.unfreezeSubAccount | ✅ 已对接 | |
| POST /sub-accounts/:id/close | AccountAPI.closeSubAccount | ✅ 已对接 | |
**前端文件**: `src/api/account.ts`
### 2.2 交易 API 对接
| 后端端点 | 前端方法 | 状态 | 说明 |
|----------|----------|------|------|
| POST /transactions/transfer | TransactionAPI.createTransfer | ✅ 已对接 | |
| POST /transactions/deposit | - | ❌ 未对接 | 需添加 createDeposit |
| POST /transactions/withdraw | - | ❌ 未对接 | 需添加 createWithdraw |
| GET /transactions/:id | TransactionAPI.getTransaction | ✅ 已对接 | |
| GET /transactions | TransactionAPI.getTransactions | ✅ 已对接 | |
**前端文件**: `src/api/transaction.ts`
**需要补充的前端方法**
```typescript
// 充值
static async createDeposit(data: DepositRequest): Promise<Transaction> {
return apiClient.post('/transactions/deposit', data)
}
// 提现
static async createWithdraw(data: WithdrawRequest): Promise<Transaction> {
return apiClient.post('/transactions/withdraw', data)
}
```
### 2.3 账务 API 对接
| 后端端点 | 前端方法 | 状态 | 说明 |
|----------|----------|------|------|
| GET /ledger/subjects | - | ❌ 未对接 | 需创建 ledger.ts |
| GET /ledger/entries/:id | - | ❌ 未对接 | 需创建 ledger.ts |
| GET /ledger/accounts/:id/entries | - | ❌ 未对接 | 需创建 ledger.ts |
**需要创建的前端文件**: `src/api/ledger.ts`
### 2.4 对账 API 对接
| 后端端点 | 前端方法 | 状态 | 说明 |
|----------|----------|------|------|
| POST /reconciliation/run | ReconciliationAPI.runReconciliation | ✅ 已对接 | |
| GET /reconciliation/batches/:id | ReconciliationAPI.getBatch | ✅ 已对接 | |
| GET /reconciliation/batches/:id/items | ReconciliationAPI.getBatchItems | ✅ 已对接 | |
| GET /reconciliation/three-account/:id | ReconciliationAPI.verifyThreeAccounts | ✅ 已对接 | |
| POST /reconciliation/adjustments | ReconciliationAPI.createAdjustment | ✅ 已对接 | |
| POST /reconciliation/adjustments/:id/approve | - | ❌ 未对接 | 需添加 approveAdjustment |
| POST /reconciliation/adjustments/:id/reject | - | ❌ 未对接 | 需添加 rejectAdjustment |
| GET /reconciliation/adjustments/pending | - | ❌ 未对接 | 需添加 getPendingAdjustments |
**前端文件**: `src/api/reconciliation.ts`
**需要补充的前端方法**
```typescript
// 审批补录
static async approveAdjustment(id: number, approver: string): Promise<void> {
return apiClient.post(`/reconciliation/adjustments/${id}/approve`, { approver })
}
// 拒绝补录
static async rejectAdjustment(id: number, approver: string, reason: string): Promise<void> {
return apiClient.post(`/reconciliation/adjustments/${id}/reject`, { approver, reason })
}
// 获取待审批补录
static async getPendingAdjustments(): Promise<ManualAdjustment[]> {
return apiClient.get('/reconciliation/adjustments/pending')
}
```
### 2.5 积分 API 对接
| 后端端点 | 前端方法 | 状态 | 说明 |
|----------|----------|------|------|
| GET /points/accounts/:id | - | ❌ 未对接 | 需创建 points.ts |
| POST /points/earn | - | ❌ 未对接 | 需创建 points.ts |
| POST /points/spend | - | ❌ 未对接 | 需创建 points.ts |
| POST /points/transfer | - | ❌ 未对接 | 需创建 points.ts |
| GET /points/transactions | - | ❌ 未对接 | 需创建 points.ts |
**需要创建的前端文件**: `src/api/points.ts`
## 三、类型定义对照
### 3.1 账户相关类型
| 后端类型 | 前端类型 | 位置 |
|----------|----------|------|
| PhysicalAccount | PhysicalAccount | types/account.ts |
| VirtualSubAccount | SubAccount | types/account.ts |
| AccountStatus | AccountStatus | types/account.ts |
| ConsistencyMode | ConsistencyMode | types/account.ts |
| OutboundControl | OutboundControl | types/account.ts |
### 3.2 交易相关类型
| 后端类型 | 前端类型 | 位置 |
|----------|----------|------|
| SystemTransaction | Transaction | types/transaction.ts |
| TransactionStatus | TransactionStatus | types/transaction.ts |
| TransactionType | TransactionType | types/transaction.ts |
| TransactionDirection | TransactionDirection | types/transaction.ts |
### 3.3 对账相关类型
| 后端类型 | 前端类型 | 位置 |
|----------|----------|------|
| ReconciliationBatch | ReconciliationBatch | types/reconciliation.ts |
| ReconciliationItem | ReconciliationItem | types/reconciliation.ts |
| ManualAdjustment | ManualAdjustment | types/reconciliation.ts |
| ThreeAccountResult | ThreeAccountResult | types/reconciliation.ts |
### 3.4 积分相关类型 (需添加)
```typescript
// types/points.ts
export interface PointsAccount {
id: number
sub_account_id: number
points_type: PointsType
balance: string
total_earned: string
total_spent: string
total_expired: string
created_at: string
updated_at: string
}
export interface PointsTransaction {
id: number
txn_no: string
points_account_id: number
txn_type: PointsTransactionType
amount: string
balance_before: string
balance_after: string
related_business_id?: string
remark?: string
created_at: string
}
export type PointsType = 'production' | 'management' | 'other'
export type PointsTransactionType = 'earn' | 'spend' | 'transfer' | 'expire' | 'adjust'
```
### 3.5 账务相关类型 (需添加)
```typescript
// types/ledger.ts
export interface AccountingSubject {
code: string
name: string
category: SubjectCategory
direction_default: number
parent_code?: string
level: number
}
export interface LedgerEntry {
id: number
entry_no: string
txn_no: string
post_date: string
post_time: string
description?: string
status: EntryStatus
created_at: string
lines: LedgerLine[]
}
export interface LedgerLine {
id: number
entry_id: number
account_id: number
account_type: string
subject_code: string
direction: Direction
amount: string
}
export type SubjectCategory = 'asset' | 'liability' | 'income' | 'expense'
export type Direction = 'debit' | 'credit'
export type EntryStatus = 'pending' | 'posted' | 'reversed'
```
## 四、待办事项
### 4.1 高优先级
1. ✅ 创建 `src/api/points.ts` - 积分 API 客户端
2. ✅ 创建 `src/api/ledger.ts` - 账务 API 客户端
3. 补充 `src/api/transaction.ts` 中的 deposit 和 withdraw 方法
4. 补充 `src/api/reconciliation.ts` 中的审批相关方法
### 4.2 中优先级
1. 添加 `src/types/points.ts` - 积分类型定义
2. 添加 `src/types/ledger.ts` - 账务类型定义
3. 更新前端界面支持新的 API
### 4.3 低优先级
1. 后端补充前端多余 API 的实现
2. 添加 API 版本管理
3. 添加 API 文档自动生成OpenAPI/Swagger
## 五、接口规范
### 5.1 请求规范
```typescript
// 统一请求配置
const apiClient = axios.create({
baseURL: '/api/v1',
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
})
```
### 5.2 响应处理
```typescript
// 响应拦截器
apiClient.interceptors.response.use(
(response) => response.data.data,
(error) => {
const message = error.response?.data?.message || '请求失败'
ElMessage.error(message)
return Promise.reject(error)
}
)
```
### 5.3 错误处理
```typescript
// 统一错误处理
interface ApiError {
code: number
message: string
error?: string
}
// 使用示例
try {
await AccountAPI.createPhysicalAccount(data)
} catch (error) {
if (axios.isAxiosError(error)) {
const apiError = error.response?.data as ApiError
console.error('API错误:', apiError.message)
}
}
```
## 六、Mock 数据
开发环境使用 MSW (Mock Service Worker) 进行 API 模拟:
**配置文件**: `src/mocks/handlers.ts`
```typescript
// MSW handlers 示例
export const handlers = [
rest.get('/api/v1/physical-accounts', (req, res, ctx) => {
return res(ctx.json({
code: 200,
message: 'success',
data: mockPhysicalAccounts
}))
}),
// ... 其他 handlers
]
```
## 七、环境配置
### 7.1 开发环境 (.env.development)
```env
VITE_API_BASE_URL=/api/v1
VITE_USE_MOCK=true
```
### 7.2 生产环境 (.env.production)
```env
VITE_API_BASE_URL=https://api.example.com/api/v1
VITE_USE_MOCK=false
```
## 八、对接检查清单
在进行前后端对接时,请确认以下事项:
- [ ] 接口路径是否正确
- [ ] 请求方法是否匹配 (GET/POST/PUT/DELETE)
- [ ] 请求参数格式是否正确 (Query/Body/Path)
- [ ] 响应数据结构是否匹配
- [ ] 错误码处理是否完整
- [ ] 类型定义是否同步
- [ ] Mock 数据是否更新