24 KiB
24 KiB
Contribution Service (贡献值/算力服务) 开发指导
1. 服务概述
1.1 核心职责
Contribution Service 负责管理用户的贡献值(算力),这是挖矿系统的核心计算基础。
主要功能:
- 通过 Debezium CDC 同步用户、认种、推荐关系数据
- 计算用户算力(来自自己认种 + 团队贡献)
- 维护算力明细账(每笔算力的来源可追溯)
- 处理算力过期(2年有效期)
- 管理未分配算力归总部逻辑
1.2 技术栈
- 框架: NestJS + TypeScript
- 数据库: PostgreSQL (事务型)
- ORM: Prisma
- 消息队列: Kafka (Debezium CDC + 事件发布)
- 缓存: Redis
1.3 端口分配
- HTTP: 3020
- 数据库: rwa_contribution
2. 架构设计
2.1 六边形架构分层
┌─────────────────────────────────────────────────────────────┐
│ API Layer (api/) │
│ Controllers, DTOs - 处理 HTTP 请求 │
├─────────────────────────────────────────────────────────────┤
│ Application Layer (application/) │
│ Commands, Queries, Event Handlers - 业务流程编排 │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer (domain/) │
│ Aggregates, Value Objects, Domain Events - 核心业务规则 │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer (infrastructure/) │
│ Prisma, Kafka, Redis - 技术实现细节 │
└─────────────────────────────────────────────────────────────┘
2.2 目录结构
contribution-service/
├── src/
│ ├── api/ # API层
│ │ ├── controllers/
│ │ │ ├── contribution.controller.ts # 算力查询API
│ │ │ ├── sync-status.controller.ts # 同步状态API
│ │ │ └── health.controller.ts
│ │ └── dto/
│ │ ├── request/
│ │ └── response/
│ │ ├── contribution-account.response.ts
│ │ └── contribution-detail.response.ts
│ │
│ ├── application/ # 应用层
│ │ ├── commands/
│ │ │ ├── calculate-user-contribution.command.ts
│ │ │ ├── process-adoption-contribution.command.ts
│ │ │ ├── expire-contributions.command.ts
│ │ │ └── recalculate-all-contributions.command.ts
│ │ ├── queries/
│ │ │ ├── get-user-contribution.query.ts
│ │ │ ├── get-contribution-details.query.ts
│ │ │ └── get-network-total-contribution.query.ts
│ │ ├── services/
│ │ │ └── contribution-calculation.service.ts
│ │ ├── event-handlers/
│ │ │ ├── adoption-synced.handler.ts
│ │ │ ├── user-synced.handler.ts
│ │ │ └── referral-synced.handler.ts
│ │ └── schedulers/
│ │ ├── contribution-expiry.scheduler.ts
│ │ └── daily-snapshot.scheduler.ts
│ │
│ ├── domain/ # 领域层
│ │ ├── aggregates/
│ │ │ ├── contribution-account.aggregate.ts
│ │ │ └── contribution-record.aggregate.ts
│ │ ├── repositories/
│ │ │ ├── contribution-account.repository.interface.ts
│ │ │ ├── contribution-record.repository.interface.ts
│ │ │ ├── synced-user.repository.interface.ts
│ │ │ ├── synced-adoption.repository.interface.ts
│ │ │ └── synced-referral.repository.interface.ts
│ │ ├── value-objects/
│ │ │ ├── contribution-amount.vo.ts
│ │ │ ├── distribution-rate.vo.ts
│ │ │ └── account-sequence.vo.ts
│ │ ├── events/
│ │ │ ├── contribution-calculated.event.ts
│ │ │ ├── contribution-expired.event.ts
│ │ │ └── daily-snapshot-created.event.ts
│ │ └── services/
│ │ ├── contribution-calculator.service.ts
│ │ └── team-contribution-calculator.service.ts
│ │
│ ├── infrastructure/ # 基础设施层
│ │ ├── persistence/
│ │ │ ├── prisma/
│ │ │ │ └── prisma.service.ts
│ │ │ ├── repositories/
│ │ │ │ ├── contribution-account.repository.impl.ts
│ │ │ │ ├── contribution-record.repository.impl.ts
│ │ │ │ ├── synced-user.repository.impl.ts
│ │ │ │ ├── synced-adoption.repository.impl.ts
│ │ │ │ └── synced-referral.repository.impl.ts
│ │ │ └── unit-of-work/
│ │ │ └── unit-of-work.service.ts
│ │ ├── kafka/
│ │ │ ├── cdc-consumers/
│ │ │ │ ├── user-cdc.consumer.ts
│ │ │ │ ├── adoption-cdc.consumer.ts
│ │ │ │ └── referral-cdc.consumer.ts
│ │ │ ├── event-publisher.service.ts
│ │ │ └── kafka.module.ts
│ │ ├── redis/
│ │ │ └── contribution-cache.service.ts
│ │ └── infrastructure.module.ts
│ │
│ ├── shared/
│ ├── config/
│ ├── app.module.ts
│ └── main.ts
│
├── prisma/
│ ├── schema.prisma
│ └── migrations/
├── package.json
├── tsconfig.json
├── Dockerfile
└── docker-compose.yml
3. 数据库设计
3.1 数据库类型选择
| 表类型 | 数据库类型 | 原因 |
|---|---|---|
| 同步数据表 | 事务型 (PostgreSQL) | CDC 数据需要精确同步,支持事务 |
| 算力账户表 | 事务型 (PostgreSQL) | 余额变更需要强一致性 |
| 算力明细表 | 事务型 (PostgreSQL) | 明细账需要完整性约束 |
| 快照表 | 事务型 (PostgreSQL) | 历史数据需要持久化 |
3.2 核心表结构
-- ============================================
-- CDC 同步数据表(从其他服务同步)
-- ============================================
-- 同步的用户数据
CREATE TABLE synced_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_sequence VARCHAR(20) NOT NULL UNIQUE, -- 跨服务关联键
original_user_id UUID NOT NULL,
phone VARCHAR(20),
status VARCHAR(20),
created_at TIMESTAMP WITH TIME ZONE,
-- CDC 同步元数据
source_sequence_num BIGINT NOT NULL, -- 源数据的序列号
synced_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 算力计算状态
contribution_calculated BOOLEAN DEFAULT FALSE,
contribution_calculated_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_synced_users_sequence ON synced_users(account_sequence);
CREATE INDEX idx_synced_users_not_calculated ON synced_users(contribution_calculated) WHERE contribution_calculated = FALSE;
-- 同步的认种数据
CREATE TABLE synced_adoptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
original_adoption_id UUID NOT NULL UNIQUE,
account_sequence VARCHAR(20) NOT NULL,
tree_count INT NOT NULL,
adoption_date DATE NOT NULL,
status VARCHAR(20),
-- 贡献值计算参数(从认种时的配置)
contribution_per_tree DECIMAL(20,10) NOT NULL,
-- CDC 同步元数据
source_sequence_num BIGINT NOT NULL,
synced_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
-- 算力分配状态
contribution_distributed BOOLEAN DEFAULT FALSE,
contribution_distributed_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX idx_synced_adoptions_account ON synced_adoptions(account_sequence);
CREATE INDEX idx_synced_adoptions_not_distributed ON synced_adoptions(contribution_distributed) WHERE contribution_distributed = FALSE;
-- 同步的推荐关系数据
CREATE TABLE synced_referrals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_sequence VARCHAR(20) NOT NULL, -- 用户
referrer_account_sequence VARCHAR(20), -- 推荐人
-- 预计算的层级路径(便于快速查询上下级)
ancestor_path TEXT, -- 格式: /root/seq1/seq2/.../
depth INT DEFAULT 0,
-- CDC 同步元数据
source_sequence_num BIGINT NOT NULL,
synced_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(account_sequence)
);
CREATE INDEX idx_synced_referrals_referrer ON synced_referrals(referrer_account_sequence);
CREATE INDEX idx_synced_referrals_path ON synced_referrals USING gin(ancestor_path gin_trgm_ops);
-- ============================================
-- 算力账户与明细表
-- ============================================
-- 算力账户表(汇总)
CREATE TABLE contribution_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_sequence VARCHAR(20) NOT NULL UNIQUE,
-- 算力汇总
personal_contribution DECIMAL(30,10) DEFAULT 0, -- 来自自己认种 (70%)
team_level_contribution DECIMAL(30,10) DEFAULT 0, -- 来自团队层级 (0.5%×N级)
team_bonus_contribution DECIMAL(30,10) DEFAULT 0, -- 来自团队额外奖励 (2.5%×N)
total_contribution DECIMAL(30,10) DEFAULT 0, -- 总算力
effective_contribution DECIMAL(30,10) DEFAULT 0, -- 有效算力(未过期)
-- 用户条件(决定能获得多少团队算力)
has_adopted BOOLEAN DEFAULT FALSE,
direct_referral_adopted_count INT DEFAULT 0,
-- 解锁状态
unlocked_level_depth INT DEFAULT 0, -- 5/10/15
unlocked_bonus_tiers INT DEFAULT 0, -- 1/2/3
-- 版本号(乐观锁)
version INT DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 算力明细表(分类账)
CREATE TABLE contribution_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_sequence VARCHAR(20) NOT NULL, -- 算力归属用户
-- 来源信息(可追溯)
source_type VARCHAR(30) NOT NULL, -- PERSONAL / TEAM_LEVEL / TEAM_BONUS
source_adoption_id UUID NOT NULL, -- 来源认种记录
source_account_sequence VARCHAR(20) NOT NULL, -- 认种人
-- 计算参数(审计用)
tree_count INT NOT NULL,
base_contribution DECIMAL(20,10) NOT NULL,
distribution_rate DECIMAL(10,6) NOT NULL, -- 70% / 0.5% / 2.5%
level_depth INT, -- 层级(TEAM_LEVEL时)
bonus_tier INT, -- 档位(TEAM_BONUS时,1/2/3)
-- 结果
amount DECIMAL(30,10) NOT NULL,
-- 有效期
effective_date DATE NOT NULL, -- 次日生效
expire_date DATE NOT NULL, -- 2年后过期
is_expired BOOLEAN DEFAULT FALSE,
expired_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_contribution_records_account ON contribution_records(account_sequence);
CREATE INDEX idx_contribution_records_source ON contribution_records(source_adoption_id);
CREATE INDEX idx_contribution_records_expire ON contribution_records(expire_date) WHERE is_expired = FALSE;
-- 未分配算力记录(归总部)
CREATE TABLE unallocated_contributions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_adoption_id UUID NOT NULL,
source_account_sequence VARCHAR(20) NOT NULL,
unalloc_type VARCHAR(30) NOT NULL, -- LEVEL_OVERFLOW / BONUS_TIER_1/2/3
would_be_account_sequence VARCHAR(20), -- 本应获得的上线
level_depth INT,
amount DECIMAL(30,10) NOT NULL,
reason VARCHAR(200),
-- 归总部后的处理
allocated_to_headquarters BOOLEAN DEFAULT FALSE,
allocated_at TIMESTAMP WITH TIME ZONE,
effective_date DATE NOT NULL,
expire_date DATE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 系统账户(运营/省/市/总部)
CREATE TABLE system_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_type VARCHAR(20) NOT NULL UNIQUE, -- OPERATION/PROVINCE/CITY/HEADQUARTERS
name VARCHAR(100) NOT NULL,
contribution_balance DECIMAL(30,10) DEFAULT 0,
contribution_never_expires BOOLEAN DEFAULT FALSE,
version INT DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 系统账户算力明细
CREATE TABLE system_contribution_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
system_account_id UUID NOT NULL REFERENCES system_accounts(id),
source_adoption_id UUID NOT NULL,
source_account_sequence VARCHAR(20) NOT NULL,
distribution_rate DECIMAL(10,6) NOT NULL, -- 12% / 1% / 2%
amount DECIMAL(30,10) NOT NULL,
effective_date DATE NOT NULL,
expire_date DATE, -- NULL = 永不过期
is_expired BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ============================================
-- 快照与统计表
-- ============================================
-- 每日算力快照(用于挖矿分配计算)
CREATE TABLE daily_contribution_snapshots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
snapshot_date DATE NOT NULL,
account_sequence VARCHAR(20) NOT NULL,
effective_contribution DECIMAL(30,10) NOT NULL,
network_total_contribution DECIMAL(30,10) NOT NULL,
contribution_ratio DECIMAL(30,18) NOT NULL, -- 占比(高精度)
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(snapshot_date, account_sequence)
);
CREATE INDEX idx_daily_snapshots_date ON daily_contribution_snapshots(snapshot_date);
-- 用户团队统计(缓存,定期更新)
CREATE TABLE user_team_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
account_sequence VARCHAR(20) NOT NULL,
stats_date DATE NOT NULL,
-- 各级认种统计
level_1_trees INT DEFAULT 0,
level_2_trees INT DEFAULT 0,
level_3_trees INT DEFAULT 0,
level_4_trees INT DEFAULT 0,
level_5_trees INT DEFAULT 0,
level_6_trees INT DEFAULT 0,
level_7_trees INT DEFAULT 0,
level_8_trees INT DEFAULT 0,
level_9_trees INT DEFAULT 0,
level_10_trees INT DEFAULT 0,
level_11_trees INT DEFAULT 0,
level_12_trees INT DEFAULT 0,
level_13_trees INT DEFAULT 0,
level_14_trees INT DEFAULT 0,
level_15_trees INT DEFAULT 0,
total_team_trees INT DEFAULT 0,
direct_adopted_referrals INT DEFAULT 0, -- 直推认种用户数
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(account_sequence, stats_date)
);
-- ============================================
-- CDC 同步状态追踪
-- ============================================
-- CDC 同步进度表
CREATE TABLE cdc_sync_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source_topic VARCHAR(100) NOT NULL UNIQUE,
last_sequence_num BIGINT DEFAULT 0,
last_synced_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 已处理事件表(幂等性)
CREATE TABLE processed_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id VARCHAR(100) NOT NULL UNIQUE,
event_type VARCHAR(50) NOT NULL,
source_service VARCHAR(50),
processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ============================================
-- 配置表
-- ============================================
-- 贡献值递增配置
CREATE TABLE contribution_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
base_contribution DECIMAL(20,10) DEFAULT 22617,
increment_percentage DECIMAL(10,6) DEFAULT 0.003, -- 0.3%
unit_size INT DEFAULT 100,
start_tree_number INT DEFAULT 1000,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 分配比例配置
CREATE TABLE distribution_rate_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rate_type VARCHAR(30) NOT NULL UNIQUE, -- PERSONAL/OPERATION/PROVINCE/CITY/LEVEL_PER/BONUS_PER
rate_value DECIMAL(10,6) NOT NULL,
description VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
4. 核心业务逻辑
4.1 算力计算公式
/**
* 计算用户 X 的总算力
*/
function calculateUserContribution(accountSequence: string): ContributionResult {
const user = getUserWithTeamStats(accountSequence);
// 1. 来自自己认种 (70%)
const personalContribution = user.ownAdoptions.reduce((sum, adoption) => {
return sum + adoption.treeCount * adoption.contributionPerTree * 0.70;
}, 0);
// 2. 来自团队层级 (每级0.5%)
const unlockedLevels = getUnlockedLevelDepth(user.directReferralAdoptedCount);
let teamLevelContribution = 0;
for (let level = 1; level <= unlockedLevels; level++) {
const levelTrees = user.teamStats[`level_${level}_trees`];
const levelContribution = levelTrees * baseContribution * 0.005; // 0.5%
teamLevelContribution += levelContribution;
}
// 3. 来自团队额外奖励 (只看第1级,最多3个2.5%)
const unlockedBonusTiers = getUnlockedBonusTiers(user);
const level1Trees = user.teamStats.level_1_trees;
const teamBonusContribution = level1Trees * baseContribution * 0.025 * unlockedBonusTiers;
return {
personalContribution,
teamLevelContribution,
teamBonusContribution,
totalContribution: personalContribution + teamLevelContribution + teamBonusContribution
};
}
/**
* 根据直推认种用户数确定解锁层级
*/
function getUnlockedLevelDepth(directReferralAdoptedCount: number): number {
if (directReferralAdoptedCount >= 5) return 15;
if (directReferralAdoptedCount >= 3) return 10;
if (directReferralAdoptedCount >= 1) return 5;
return 0;
}
/**
* 根据用户条件确定解锁的额外奖励档位数
*/
function getUnlockedBonusTiers(user: User): number {
let tiers = 0;
if (user.hasAdopted) tiers++; // 自己认种过 → +1档
if (user.directReferralAdoptedCount >= 2) tiers++; // 直推≥2 → +1档
if (user.directReferralAdoptedCount >= 4) tiers++; // 直推≥4 → +1档
return tiers; // 0/1/2/3
}
4.2 认种事件处理流程
/**
* 当新认种发生时的处理流程
*/
async function processAdoptionContribution(adoption: SyncedAdoption): Promise<void> {
const totalContribution = adoption.treeCount * adoption.contributionPerTree;
await unitOfWork.runInTransaction(async (tx) => {
// 1. 分配给认种人 (70%)
await createContributionRecord(tx, {
accountSequence: adoption.accountSequence,
sourceType: 'PERSONAL',
sourceAdoptionId: adoption.id,
amount: totalContribution * 0.70,
distributionRate: 0.70,
});
// 2. 分配给系统账户 (15%)
await createSystemContribution(tx, 'OPERATION', adoption, 0.12);
await createSystemContribution(tx, 'PROVINCE', adoption, 0.01);
await createSystemContribution(tx, 'CITY', adoption, 0.02);
// 3. 分配给上线团队 (15%)
await distributeTeamContribution(tx, adoption, totalContribution * 0.15);
// 4. 标记已分配
await markAdoptionDistributed(tx, adoption.id);
});
}
/**
* 分配团队贡献值给上线链条
*/
async function distributeTeamContribution(
tx: Transaction,
adoption: Adoption,
teamTotal: number
): Promise<void> {
const ancestors = await getAncestorChain(adoption.accountSequence, 15);
let distributedLevel = 0;
let distributedBonus = 0;
for (let i = 0; i < ancestors.length && i < 15; i++) {
const ancestor = ancestors[i];
const level = i + 1;
// 层级部分 (0.5% 每级)
const levelAmount = teamTotal * 0.5 / 15; // 7.5% / 15 = 0.5%
if (ancestor.unlockedLevelDepth >= level) {
await createContributionRecord(tx, {
accountSequence: ancestor.accountSequence,
sourceType: 'TEAM_LEVEL',
levelDepth: level,
amount: levelAmount,
});
distributedLevel += levelAmount;
} else {
// 未解锁,归总部
await createUnallocatedContribution(tx, {
type: 'LEVEL_OVERFLOW',
wouldBeAccount: ancestor.accountSequence,
levelDepth: level,
amount: levelAmount,
});
}
}
// 额外奖励部分 (只给直接上线)
if (ancestors.length > 0) {
const directReferrer = ancestors[0];
const bonusPerTier = teamTotal * 0.5 / 3; // 7.5% / 3 = 2.5%
for (let tier = 1; tier <= 3; tier++) {
if (directReferrer.unlockedBonusTiers >= tier) {
await createContributionRecord(tx, {
accountSequence: directReferrer.accountSequence,
sourceType: 'TEAM_BONUS',
bonusTier: tier,
amount: bonusPerTier,
});
distributedBonus += bonusPerTier;
} else {
await createUnallocatedContribution(tx, {
type: `BONUS_TIER_${tier}`,
wouldBeAccount: directReferrer.accountSequence,
amount: bonusPerTier,
});
}
}
}
}
4.3 CDC 数据同步
/**
* Debezium CDC Consumer - 用户数据同步
*/
@Consumer({ topic: 'dbserver1.rwa_identity.users' })
async handleUserCdc(message: DebeziumMessage): Promise<void> {
const { op, after, source } = message;
const sequenceNum = source.sequence;
// 幂等性检查
if (await isEventProcessed(`user-cdc-${sequenceNum}`)) {
return;
}
switch (op) {
case 'c': // CREATE
case 'u': // UPDATE
await syncedUserRepository.upsert({
accountSequence: after.account_sequence,
originalUserId: after.id,
phone: after.phone,
status: after.status,
sourceSequenceNum: sequenceNum,
});
break;
case 'd': // DELETE
// 通常不处理删除,或标记为 inactive
break;
}
await markEventProcessed(`user-cdc-${sequenceNum}`);
}
5. 服务间通信
5.1 事件发布(Outbox Pattern)
// 发布算力计算完成事件
interface ContributionCalculatedEvent {
eventId: string;
eventType: 'ContributionCalculated';
accountSequence: string;
totalContribution: string;
effectiveContribution: string;
calculatedAt: string;
}
// Mining Service 订阅此事件用于挖矿分配
5.2 订阅的 CDC Topics
| Topic | 来源服务 | 数据内容 |
|---|---|---|
dbserver1.rwa_identity.users |
identity-service | 用户基本信息 |
dbserver1.rwa_planting.adoptions |
planting-service | 认种记录 |
dbserver1.rwa_referral.referral_relations |
referral-service | 推荐关系 |
6. 关键注意事项
6.1 数据一致性
- 所有算力变更必须在事务中完成
- 使用乐观锁 (version 字段) 处理并发更新
- 明细账与汇总账必须保持一致
6.2 幂等性
- CDC 消息可能重复,使用 sequence_num 去重
- 认种分配使用 contribution_distributed 标记防止重复
6.3 性能优化
- 团队统计表 (user_team_stats) 定期预计算
- 使用 Redis 缓存热点用户算力
- 批量处理历史数据计算
6.4 跨服务关联
- 始终使用 account_sequence,不使用 userId
- account_sequence 是唯一的跨服务关联标识
7. 开发检查清单
- 实现 CDC Consumer 同步用户/认种/推荐数据
- 实现算力计算核心逻辑
- 实现算力明细账记录
- 实现未分配算力归总部逻辑
- 实现算力过期处理定时任务
- 实现每日快照生成
- 实现查询 API
- 编写单元测试
- 编写集成测试
- 配置 Debezium Connector
8. 启动命令
# 开发环境
npm run start:dev
# 生成 Prisma Client
npx prisma generate
# 运行迁移
npx prisma migrate dev
# 生产环境
npm run build && npm run start:prod