- 系统架构文档 (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)
504 lines
14 KiB
Markdown
504 lines
14 KiB
Markdown
# 积分域 (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. **过期处理**:定期扫描处理过期积分
|
||
|