积分域 (Points Domain)
一、领域概述
积分域负责管理用户积分的完整生命周期,包括积分获取、消费、转移和过期处理。支持多种积分类型和灵活的积分规则配置。
二、核心实体
2.1 积分账户 (PointsAccount)
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>,
}
核心方法:
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)
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)
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>,
}
规则配置示例:
{
"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)
pub enum PointsType {
Production, // 生产积分
Management, // 管理积分
Other, // 其他积分
}
| 类型 |
说明 |
获取方式 |
| Production |
生产积分 |
完成生产任务 |
| Management |
管理积分 |
管理工作奖励 |
| Other |
其他积分 |
活动奖励等 |
3.2 积分交易类型 (PointsTransactionType)
pub enum PointsTransactionType {
Earn, // 获取
Spend, // 消费
Transfer, // 转移
Expire, // 过期
Adjust, // 调整
}
四、业务流程
4.1 积分获取流程
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 积分消费流程
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 积分转移流程
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
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 积分获取实现
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 积分转移实现
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
#[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
#[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 表
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 表
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 表
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 账户统计
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 周期统计
- 日统计:每日积分变动
- 月统计:月度积分汇总
- 年统计:年度积分报表
十一、注意事项
- 积分精度:使用 Decimal 类型,保留2位小数
- 并发控制:积分操作需加锁,防止超发
- 审计追踪:所有积分变动必须有交易记录
- 过期处理:定期扫描处理过期积分