512 lines
16 KiB
Markdown
512 lines
16 KiB
Markdown
# Reward Service 架构概览
|
||
|
||
## 服务概述
|
||
|
||
Reward Service 是 RWA Durian Queen 平台的核心收益分配服务,负责处理用户认种榴莲树后的收益计算、分配、领取和结算。
|
||
|
||
### 技术栈
|
||
|
||
- **框架**: NestJS 11.x
|
||
- **语言**: TypeScript 5.7
|
||
- **数据库**: PostgreSQL 15 (通过 Prisma 7.x ORM)
|
||
- **缓存**: Redis 7.x
|
||
- **消息队列**: Apache Kafka
|
||
- **测试**: Jest 30.x + Supertest 7.x
|
||
- **API文档**: Swagger/OpenAPI
|
||
|
||
---
|
||
|
||
## 领域驱动设计 (DDD) 架构
|
||
|
||
本服务采用领域驱动设计架构,分为以下四层:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ API Layer │
|
||
│ Controllers, DTOs, Guards │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Application Layer │
|
||
│ Application Services, Schedulers, Use Cases │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Domain Layer │
|
||
│ Aggregates, Value Objects, Domain Events, Domain Services │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Infrastructure Layer │
|
||
│ Repositories, External Clients, Kafka, Redis │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
src/
|
||
├── api/ # API层
|
||
│ ├── controllers/ # 控制器
|
||
│ │ ├── health.controller.ts
|
||
│ │ ├── reward.controller.ts
|
||
│ │ └── settlement.controller.ts
|
||
│ └── dto/ # 数据传输对象
|
||
│ ├── request/
|
||
│ └── response/
|
||
│
|
||
├── application/ # 应用层
|
||
│ ├── services/
|
||
│ │ └── reward-application.service.ts
|
||
│ └── schedulers/
|
||
│ └── reward-expiration.scheduler.ts
|
||
│
|
||
├── domain/ # 领域层 (核心)
|
||
│ ├── aggregates/ # 聚合根
|
||
│ │ ├── reward-ledger-entry/
|
||
│ │ └── reward-summary/
|
||
│ ├── value-objects/ # 值对象
|
||
│ │ ├── money.vo.ts
|
||
│ │ ├── hashpower.vo.ts
|
||
│ │ ├── reward-source.vo.ts
|
||
│ │ ├── reward-status.enum.ts
|
||
│ │ └── right-type.enum.ts
|
||
│ ├── events/ # 领域事件
|
||
│ │ ├── reward-created.event.ts
|
||
│ │ ├── reward-claimed.event.ts
|
||
│ │ ├── reward-expired.event.ts
|
||
│ │ └── reward-settled.event.ts
|
||
│ ├── services/ # 领域服务
|
||
│ │ ├── reward-calculation.service.ts
|
||
│ │ └── reward-expiration.service.ts
|
||
│ └── repositories/ # 仓储接口
|
||
│
|
||
├── infrastructure/ # 基础设施层
|
||
│ ├── persistence/
|
||
│ │ ├── prisma/
|
||
│ │ ├── repositories/ # 仓储实现
|
||
│ │ └── mappers/ # 持久化映射
|
||
│ ├── external/ # 外部服务客户端
|
||
│ │ ├── referral-service/
|
||
│ │ ├── authorization-service/
|
||
│ │ └── wallet-service/
|
||
│ ├── kafka/ # Kafka消息
|
||
│ └── redis/ # Redis缓存
|
||
│
|
||
├── shared/ # 共享模块
|
||
│ ├── guards/
|
||
│ └── strategies/
|
||
│
|
||
└── config/ # 配置
|
||
```
|
||
|
||
---
|
||
|
||
## 核心领域模型
|
||
|
||
### 聚合根
|
||
|
||
#### 1. RewardLedgerEntry (奖励流水)
|
||
|
||
奖励流水是核心聚合根,记录每一笔奖励的详细信息和状态变化。
|
||
|
||
```typescript
|
||
class RewardLedgerEntry {
|
||
// 身份
|
||
id: bigint
|
||
userId: bigint
|
||
rewardSource: RewardSource
|
||
|
||
// 金额
|
||
usdtAmount: Money
|
||
hashpowerAmount: Hashpower
|
||
|
||
// 状态
|
||
rewardStatus: RewardStatus // PENDING | SETTLEABLE | SETTLED | EXPIRED
|
||
|
||
// 时间戳
|
||
createdAt: Date
|
||
expireAt: Date | null // 待领取奖励24h后过期
|
||
claimedAt: Date | null
|
||
settledAt: Date | null
|
||
expiredAt: Date | null
|
||
|
||
// 行为
|
||
claim(): void // 领取 (PENDING → SETTLEABLE)
|
||
expire(): void // 过期 (PENDING → EXPIRED)
|
||
settle(): void // 结算 (SETTLEABLE → SETTLED)
|
||
}
|
||
```
|
||
|
||
**状态机**:
|
||
|
||
```
|
||
┌──────────┐
|
||
│ PENDING │
|
||
└────┬─────┘
|
||
│
|
||
┌────────────┼────────────┐
|
||
│ 用户认种 │ │ 24h超时
|
||
▼ │ ▼
|
||
┌───────────┐ │ ┌─────────┐
|
||
│ SETTLEABLE│ │ │ EXPIRED │
|
||
└─────┬─────┘ │ └─────────┘
|
||
│ │
|
||
│ 用户结算 │
|
||
▼ │
|
||
┌──────────┐ │
|
||
│ SETTLED │ │
|
||
└──────────┘ │
|
||
```
|
||
|
||
#### 2. RewardSummary (奖励汇总)
|
||
|
||
用户维度的奖励汇总,提供快速查询能力。
|
||
|
||
```typescript
|
||
class RewardSummary {
|
||
userId: bigint
|
||
|
||
// 待领取 (24h倒计时)
|
||
pendingUsdt: Money
|
||
pendingHashpower: Hashpower
|
||
pendingExpireAt: Date | null
|
||
|
||
// 可结算
|
||
settleableUsdt: Money
|
||
settleableHashpower: Hashpower
|
||
|
||
// 累计已结算
|
||
settledTotalUsdt: Money
|
||
settledTotalHashpower: Hashpower
|
||
|
||
// 累计已过期
|
||
expiredTotalUsdt: Money
|
||
expiredTotalHashpower: Hashpower
|
||
}
|
||
```
|
||
|
||
### 值对象
|
||
|
||
| 值对象 | 描述 | 不变式 |
|
||
|--------|------|--------|
|
||
| `Money` | 货币金额 | 金额 >= 0, 货币类型不可变 |
|
||
| `Hashpower` | 算力 | 值 >= 0 |
|
||
| `RewardSource` | 奖励来源 | 关联订单和用户不可变 |
|
||
| `RewardStatus` | 奖励状态 | 枚举值 |
|
||
| `RightType` | 权益类型 | 枚举值 |
|
||
|
||
---
|
||
|
||
## 收益类型
|
||
|
||
系统支持6种收益类型,每种有不同的计算规则:
|
||
|
||
| 权益类型 | USDT金额 | 算力百分比 | 接收方 |
|
||
|---------|---------|-----------|--------|
|
||
| 分享权益 (SHARE_RIGHT) | 500 U/棵 | 0% | 直接推荐人 |
|
||
| 省区域权益 (PROVINCE_AREA_RIGHT) | 15 U/棵 | 1% | 系统省公司 |
|
||
| 省团队权益 (PROVINCE_TEAM_RIGHT) | 20 U/棵 | 0% | 授权省公司 |
|
||
| 市区域权益 (CITY_AREA_RIGHT) | 35 U/棵 | 2% | 系统市公司 |
|
||
| 市团队权益 (CITY_TEAM_RIGHT) | 40 U/棵 | 0% | 授权市公司 |
|
||
| 社区权益 (COMMUNITY_RIGHT) | 80 U/棵 | 0% | 所属社区 |
|
||
|
||
**总计**: 690 USDT + 3% 算力 / 棵
|
||
|
||
---
|
||
|
||
## 核心业务流程
|
||
|
||
### 1. 奖励分配流程
|
||
|
||
```
|
||
订单支付成功事件
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 计算6种权益奖励 │
|
||
│ RewardCalculation│
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 保存奖励流水 │
|
||
│ RewardLedgerEntry│
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 更新用户汇总 │
|
||
│ RewardSummary │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 发布领域事件 │
|
||
│ Kafka │
|
||
└─────────────────┘
|
||
```
|
||
|
||
### 2. 分享权益24小时机制
|
||
|
||
```
|
||
推荐人已认种?
|
||
│
|
||
├── 是 → 直接可结算 (SETTLEABLE)
|
||
│
|
||
└── 否 → 待领取 (PENDING, 24h倒计时)
|
||
│
|
||
├── 24h内认种 → claim() → 可结算
|
||
│
|
||
└── 24h超时 → expire() → 进总部社区
|
||
```
|
||
|
||
### 3. 结算流程
|
||
|
||
```
|
||
用户发起结算请求 (选择BNB/OG/USDT/DST)
|
||
│
|
||
▼
|
||
┌───────────────┐
|
||
│ 获取可结算奖励 │
|
||
└───────┬───────┘
|
||
│
|
||
▼
|
||
┌───────────────┐
|
||
│ 调用钱包服务 │
|
||
│ 执行SWAP │
|
||
└───────┬───────┘
|
||
│
|
||
▼
|
||
┌───────────────┐
|
||
│ 更新奖励状态 │
|
||
│ SETTLED │
|
||
└───────┬───────┘
|
||
│
|
||
▼
|
||
┌───────────────┐
|
||
│ 发布结算事件 │
|
||
└───────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 外部服务集成
|
||
|
||
### 防腐层设计
|
||
|
||
通过接口定义与外部服务解耦:
|
||
|
||
```typescript
|
||
// 推荐关系服务
|
||
interface IReferralServiceClient {
|
||
getReferralChain(userId: bigint): Promise<{
|
||
ancestors: Array<{ userId: bigint; hasPlanted: boolean }>;
|
||
}>;
|
||
}
|
||
|
||
// 授权体系服务
|
||
interface IAuthorizationServiceClient {
|
||
findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise<bigint | null>;
|
||
findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise<bigint | null>;
|
||
findNearestCommunity(userId: bigint): Promise<bigint | null>;
|
||
}
|
||
|
||
// 钱包服务
|
||
interface IWalletServiceClient {
|
||
executeSwap(params: {
|
||
userId: bigint;
|
||
usdtAmount: number;
|
||
targetCurrency: string;
|
||
}): Promise<SwapResult>;
|
||
}
|
||
```
|
||
|
||
### 服务依赖
|
||
|
||
```
|
||
┌──────────────────┐ ┌───────────────────┐
|
||
│ Planting Service │────▶│ │
|
||
│ (认种服务) │ │ │
|
||
└──────────────────┘ │ │
|
||
│ Reward Service │
|
||
┌──────────────────┐ │ │
|
||
│ Referral Service │────▶│ │
|
||
│ (推荐服务) │ │ │
|
||
└──────────────────┘ │ │
|
||
│ │
|
||
┌──────────────────┐ │ │
|
||
│Authorization Svc │────▶│ │
|
||
│ (授权服务) │ │ │
|
||
└──────────────────┘ └─────────┬─────────┘
|
||
│
|
||
▼
|
||
┌───────────────────┐
|
||
│ Wallet Service │
|
||
│ (钱包服务) │
|
||
└───────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 消息与事件
|
||
|
||
### Kafka Topics
|
||
|
||
| Topic | 描述 | 生产者 | 消费者 |
|
||
|-------|------|--------|--------|
|
||
| `planting.order.paid` | 认种订单支付成功 | Planting Service | Reward Service |
|
||
| `planting.user.planted` | 用户完成认种 | Planting Service | Reward Service |
|
||
| `reward.created` | 奖励创建 | Reward Service | - |
|
||
| `reward.claimed` | 奖励领取 | Reward Service | - |
|
||
| `reward.settled` | 奖励结算 | Reward Service | Wallet Service |
|
||
| `reward.expired` | 奖励过期 | Reward Service | - |
|
||
|
||
### 领域事件
|
||
|
||
```typescript
|
||
// 奖励创建事件
|
||
interface RewardCreatedEvent {
|
||
entryId: string;
|
||
userId: string;
|
||
sourceOrderId: string;
|
||
rightType: RightType;
|
||
usdtAmount: number;
|
||
hashpowerAmount: number;
|
||
rewardStatus: RewardStatus;
|
||
expireAt: Date | null;
|
||
}
|
||
|
||
// 奖励领取事件
|
||
interface RewardClaimedEvent {
|
||
entryId: string;
|
||
userId: string;
|
||
usdtAmount: number;
|
||
hashpowerAmount: number;
|
||
}
|
||
|
||
// 奖励结算事件
|
||
interface RewardSettledEvent {
|
||
entryId: string;
|
||
userId: string;
|
||
usdtAmount: number;
|
||
settleCurrency: string;
|
||
receivedAmount: number;
|
||
}
|
||
|
||
// 奖励过期事件
|
||
interface RewardExpiredEvent {
|
||
entryId: string;
|
||
userId: string;
|
||
usdtAmount: number;
|
||
transferredTo: string; // 'HEADQUARTERS_COMMUNITY'
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 定时任务
|
||
|
||
### 奖励过期检查
|
||
|
||
```typescript
|
||
@Cron('0 * * * * *') // 每分钟执行
|
||
async handleExpiredRewards() {
|
||
await this.rewardApplicationService.expireOverdueRewards();
|
||
}
|
||
```
|
||
|
||
处理逻辑:
|
||
1. 查找所有过期的待领取奖励
|
||
2. 将奖励状态改为已过期
|
||
3. 更新原用户的汇总数据
|
||
4. 将金额转入总部社区账户
|
||
5. 发布过期事件
|
||
|
||
---
|
||
|
||
## 数据库设计
|
||
|
||
### 核心表结构
|
||
|
||
```sql
|
||
-- 奖励流水表
|
||
CREATE TABLE reward_ledger_entries (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
user_id BIGINT NOT NULL,
|
||
source_order_id BIGINT NOT NULL,
|
||
source_user_id BIGINT NOT NULL,
|
||
right_type VARCHAR(50) NOT NULL,
|
||
usdt_amount DECIMAL(18,2) NOT NULL,
|
||
hashpower_amount DECIMAL(18,8) NOT NULL,
|
||
reward_status VARCHAR(20) NOT NULL,
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
expire_at TIMESTAMP,
|
||
claimed_at TIMESTAMP,
|
||
settled_at TIMESTAMP,
|
||
expired_at TIMESTAMP,
|
||
memo TEXT,
|
||
|
||
INDEX idx_user_status (user_id, reward_status),
|
||
INDEX idx_expire_at (expire_at) WHERE reward_status = 'PENDING'
|
||
);
|
||
|
||
-- 用户奖励汇总表
|
||
CREATE TABLE reward_summaries (
|
||
user_id BIGINT PRIMARY KEY,
|
||
pending_usdt DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||
pending_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0,
|
||
pending_expire_at TIMESTAMP,
|
||
settleable_usdt DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||
settleable_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0,
|
||
settled_total_usdt DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||
settled_total_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0,
|
||
expired_total_usdt DECIMAL(18,2) NOT NULL DEFAULT 0,
|
||
expired_total_hashpower DECIMAL(18,8) NOT NULL DEFAULT 0,
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 安全设计
|
||
|
||
### 认证与授权
|
||
|
||
- **JWT认证**: 所有API请求需要携带有效的JWT Token
|
||
- **用户隔离**: 用户只能访问自己的奖励数据
|
||
- **Passport策略**: 使用`passport-jwt`进行Token验证
|
||
|
||
```typescript
|
||
@Injectable()
|
||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||
constructor(configService: ConfigService) {
|
||
super({
|
||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||
ignoreExpiration: false,
|
||
secretOrKey: configService.get('JWT_SECRET'),
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 数据完整性
|
||
|
||
- 使用数据库事务保证数据一致性
|
||
- 领域模型不变式检查
|
||
- 状态机约束防止非法状态转换
|
||
|
||
---
|
||
|
||
## 性能优化
|
||
|
||
### 缓存策略
|
||
|
||
- Redis缓存用户奖励汇总
|
||
- 热点数据预加载
|
||
|
||
### 数据库优化
|
||
|
||
- 复合索引优化查询
|
||
- 分页查询避免全表扫描
|
||
- 批量写入减少IO
|
||
|
||
### 消息处理
|
||
|
||
- Kafka消费者组实现负载均衡
|
||
- 幂等性设计防止重复处理
|