tangweijie 1460187516 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)
2026-01-05 18:12:37 +08:00

14 KiB
Raw Permalink Blame History

积分域 (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 周期统计

  • 日统计:每日积分变动
  • 月统计:月度积分汇总
  • 年统计:年度积分报表

十一、注意事项

  1. 积分精度:使用 Decimal 类型保留2位小数
  2. 并发控制:积分操作需加锁,防止超发
  3. 审计追踪:所有积分变动必须有交易记录
  4. 过期处理:定期扫描处理过期积分