15 KiB
15 KiB
2.0 贡献值计算与钱包存储方案
一、系统架构总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1.0 系统 (数据源) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ auth-service │ │ planting-svc │ │ referral-svc │ │
│ │ (用户注册) │ │ (认种订单) │ │ (推荐关系) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────────────────────────┘
│ │ │
│ Kafka CDC │ Kafka CDC │ Kafka CDC
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2.0 系统 │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ contribution-service │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ CDC Consumer │ │ 贡献值计算引擎 │ │ Outbox 发布器 │ │ │
│ │ │ (同步数据) │→ │ (分配逻辑) │→ │ (Kafka) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │
│ └──────────────────────────────────────────────────────┼─────────────────┘ │
│ │ │
│ Kafka: contribution.distribution.completed │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ mining-wallet-service │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Kafka Consumer │ │ 钱包账户管理 │ │ 分类账记录 │ │ │
│ │ │ (接收分配结果) │→ │ (存储贡献值) │→ │ (交易明细) │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ mining-admin-service │ │
│ │ 订阅 contribution-service 和 mining-wallet-service 的事件进行数据同步 │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
二、贡献值分配规则
2.1 分配比例
| 类型 | 比例 | 说明 |
|---|---|---|
| 个人贡献 | 70% | 认种人自己获得 |
| 运营账户 | 12% | 系统运营账户(永不过期) |
| 省级公司 | 1% | 认种所在省份公司 |
| 市级公司 | 2% | 认种所在城市公司 |
| 团队层级 | 7.5% | 上线1-15级,每级0.5% |
| 团队奖励 | 7.5% | 直接上线的3档奖励,每档2.5% |
2.2 解锁条件
层级解锁(每级0.5%,共15级)
| 档位 | 层级 | 解锁条件 |
|---|---|---|
| 第1档 | L1-L5 | 直推≥1人认种 |
| 第2档 | L6-L10 | 直推≥3人认种 |
| 第3档 | L11-L15 | 直推≥5人认种 |
奖励解锁(每档2.5%,共3档)
| 档位 | 解锁条件 |
|---|---|
| 第1档 | 自己认种 |
| 第2档 | 直推≥2人认种 |
| 第3档 | 直推≥4人认种 |
2.3 有效期规则
- 用户贡献值:2年有效期(从认种次日开始计算)
- 运营账户:永不过期
- 未分配贡献值:归总部账户(永不过期)
三、Kafka 事件设计
3.1 用户注册事件
Topic: auth.user.registered
interface UserRegisteredEvent {
eventType: 'UserRegistered';
eventId: string;
timestamp: string;
payload: {
accountSequence: string;
phone: string;
referrerAccountSequence: string | null;
registeredAt: string;
source: 'LEGACY_MIGRATION' | 'NEW_REGISTRATION';
};
}
消费者:
contribution-service- 创建 ContributionAccountmining-wallet-service- 创建用户钱包(CONTRIBUTION 类型)
3.2 认种完成事件
Topic: planting.adoption.completed
interface AdoptionCompletedEvent {
eventType: 'AdoptionCompleted';
eventId: string;
timestamp: string;
payload: {
adoptionId: string;
accountSequence: string;
treeCount: number;
contributionPerTree: string;
adoptionDate: string;
provinceCode: string;
cityCode: string;
};
}
消费者:
contribution-service- 触发贡献值计算
3.3 贡献值分配完成事件
Topic: contribution.distribution.completed
interface ContributionDistributionCompletedEvent {
eventType: 'ContributionDistributionCompleted';
eventId: string;
timestamp: string;
payload: {
// 认种信息
adoptionId: string;
adopterAccountSequence: string;
treeCount: number;
adoptionDate: string;
// 用户贡献值分配
userContributions: {
accountSequence: string;
contributionType: 'PERSONAL' | 'TEAM_LEVEL' | 'TEAM_BONUS';
amount: string;
levelDepth?: number; // 1-15 for TEAM_LEVEL
bonusTier?: number; // 1-3 for TEAM_BONUS
effectiveDate: string;
expireDate: string;
sourceAdoptionId: string;
sourceAccountSequence: string;
}[];
// 系统账户分配
systemContributions: {
accountType: 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
amount: string;
provinceCode?: string;
cityCode?: string;
neverExpires: boolean;
}[];
// 未分配(归总部)
unallocatedToHeadquarters: {
reason: string;
amount: string;
wouldBeAccountSequence?: string;
levelDepth?: number;
bonusTier?: number;
}[];
};
}
消费者:
mining-wallet-service- 存储贡献值到钱包
3.4 贡献值入账事件
Topic: mining-wallet.contribution.credited
interface ContributionCreditedEvent {
eventType: 'ContributionCredited';
eventId: string;
timestamp: string;
payload: {
accountSequence: string;
walletType: 'CONTRIBUTION';
amount: string;
balanceAfter: string;
transactionId: string;
sourceType: 'ADOPTION_DISTRIBUTION';
referenceId: string;
};
}
消费者:
mining-admin-service- 同步数据用于展示
四、Outbox 模式
4.1 Outbox 表结构
model OutboxEvent {
id BigInt @id @default(autoincrement()) @map("outbox_id")
eventType String @map("event_type") @db.VarChar(100)
topic String @map("topic") @db.VarChar(100)
key String @map("key") @db.VarChar(200)
payload Json @map("payload")
aggregateId String @map("aggregate_id") @db.VarChar(100)
aggregateType String @map("aggregate_type") @db.VarChar(50)
status String @default("PENDING") @map("status") @db.VarChar(20)
retryCount Int @default(0) @map("retry_count")
maxRetries Int @default(10) @map("max_retries")
lastError String? @map("last_error") @db.Text
createdAt DateTime @default(now()) @map("created_at")
publishedAt DateTime? @map("published_at")
nextRetryAt DateTime? @map("next_retry_at")
@@index([status, createdAt])
@@index([status, nextRetryAt])
@@map("outbox_events")
}
4.2 退避策略(最大4小时)
| 重试次数 | 等待时间 | 累计时间 |
|---|---|---|
| 1 | 30秒 | 30秒 |
| 2 | 1分钟 | 1.5分钟 |
| 3 | 2分钟 | 3.5分钟 |
| 4 | 5分钟 | 8.5分钟 |
| 5 | 10分钟 | 18.5分钟 |
| 6 | 30分钟 | 48.5分钟 |
| 7 | 1小时 | 1小时48分 |
| 8 | 2小时 | 3小时48分 |
| 9 | 4小时 | 7小时48分 |
| 10 | 4小时(max) | 11小时48分 |
const BACKOFF_INTERVALS = [
30_000, // 30 seconds
60_000, // 1 minute
120_000, // 2 minutes
300_000, // 5 minutes
600_000, // 10 minutes
1_800_000, // 30 minutes
3_600_000, // 1 hour
7_200_000, // 2 hours
14_400_000, // 4 hours (max)
14_400_000, // 4 hours (max)
];
4.3 幂等性保证(4小时去重窗口)
model ProcessedEvent {
id String @id @default(uuid())
eventId String @unique @map("event_id")
eventType String @map("event_type")
sourceService String @map("source_service")
processedAt DateTime @default(now()) @map("processed_at")
@@index([sourceService])
@@index([processedAt])
@@map("processed_events")
}
- Redis 缓存:4小时 TTL,快速路径检查
- DB 持久化:ProcessedEvent 表,24小时后清理
- 双重检查:先查 Redis,未命中再查 DB
五、服务职责划分
| 服务 | 职责 | 数据存储 |
|---|---|---|
| auth-service | 用户注册/迁移,发送 UserRegistered 事件 | 用户基础信息 |
| planting-service | 认种订单,发送 AdoptionCompleted 事件 | 认种订单 |
| contribution-service | 贡献值计算逻辑,解锁状态管理 | 计算明细、解锁事件(用于审计) |
| mining-wallet-service | 贡献值存储,余额管理,过期处理 | 钱包余额、交易明细(分类账) |
| mining-admin-service | 数据聚合展示 | 同步缓存 |
六、数据模型
6.1 contribution-service(已实现)
ContributionAccount- 用户贡献值账户(汇总)ContributionRecord- 贡献值明细(审计)UnlockEvent- 解锁事件记录UnallocatedContribution- 未分配贡献值SystemAccount- 系统账户(运营/省/市/总部)
6.2 mining-wallet-service(需扩展)
现有模型:
UserWallet- 用户钱包(CONTRIBUTION 类型)UserWalletTransaction- 交易明细(分类账)SystemAccount- 系统账户
需要添加:
- SystemAccount 添加
contributionBalance字段 - TransactionType 添加
CONTRIBUTION_CREDIT、CONTRIBUTION_EXPIRE枚举值
七、实施步骤
Phase 1: mining-wallet-service 扩展
- 修改 schema 添加 SystemAccount 贡献值字段
- 实现 ContributionWalletService
- 实现 ContributionDistributionConsumer
- 实现 UserRegisteredConsumer(创建用户钱包)
- 实现 ContributionExpiryScheduler
- 实现 Outbox Scheduler(4小时退避)
Phase 2: contribution-service 扩展
- 实现 ContributionDistributionPublisherService
- 修改 ContributionCalculationService 调用发布器
- 测试完整的分配流程
Phase 3: auth-service 集成
- 确保 UserRegistered 事件正确发送
- 包含 referrerAccountSequence 信息
Phase 4: 历史数据迁移
- 批量处理1.0的历史认种数据
- 逐笔计算并发送分配事件
- 验证钱包余额正确性
八、关键流程
8.1 认种触发贡献值分配
1. planting-service 发送 AdoptionCompleted 事件
↓
2. contribution-service 消费事件
├─ 查询认种人的上线链条(最多15级)
├─ 查询各上线的解锁状态
├─ 计算贡献值分配
└─ 写入 Outbox 表
↓
3. Outbox Scheduler 发送到 Kafka
↓
4. mining-wallet-service 消费 ContributionDistributionCompleted
├─ 幂等性检查
├─ 更新用户钱包余额
├─ 更新系统账户余额
└─ 记录交易明细
↓
5. mining-admin-service 消费 ContributionCredited
└─ 同步缓存数据
8.2 贡献值过期处理
1. 每日凌晨1点定时任务启动
↓
2. 查询 expireDate <= today 的交易记录
↓
3. 逐笔处理过期
├─ 扣减钱包余额
├─ 创建过期交易记录
└─ 标记原交易为已过期
九、监控与告警
9.1 关键指标
- Outbox 积压数量
- 事件处理延迟
- 重试次数分布
- 失败事件数量
9.2 告警规则
- Outbox 积压 > 1000 条
- 事件处理延迟 > 5 分钟
- 失败事件数量 > 10 条/小时