diff --git a/backend/services/wallet-service/.env.example b/backend/services/wallet-service/.env.example new file mode 100644 index 00000000..7fd479d9 --- /dev/null +++ b/backend/services/wallet-service/.env.example @@ -0,0 +1,4 @@ +DATABASE_URL="postgresql://user:password@host:5432/database?schema=public" +NODE_ENV=development +APP_PORT=3002 +JWT_SECRET=your-jwt-secret-key diff --git a/backend/services/wallet-service/.gitignore b/backend/services/wallet-service/.gitignore new file mode 100644 index 00000000..a179970d --- /dev/null +++ b/backend/services/wallet-service/.gitignore @@ -0,0 +1,45 @@ +# Dependencies +node_modules/ +package-lock.json + +# Build output +dist/ +build/ + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Test coverage +coverage/ + +# Prisma +prisma/*.db +prisma/*.db-journal + +# Claude Code +.claude/ + +# Misc +nul +*.tmp +*.temp diff --git a/backend/services/wallet-service/DEVELOPMENT_GUIDE.md b/backend/services/wallet-service/DEVELOPMENT_GUIDE.md deleted file mode 100644 index a6f5bb44..00000000 --- a/backend/services/wallet-service/DEVELOPMENT_GUIDE.md +++ /dev/null @@ -1,757 +0,0 @@ -# Wallet Service 开发指导 - -## 项目概述 - -Wallet Service 是 RWA 榴莲女皇平台的钱包账本微服务,负责管理用户的平台内部余额、充值入账、提现、资金流水记账等功能。 - -## 技术栈 - -- **框架**: NestJS -- **数据库**: PostgreSQL + Prisma ORM -- **架构**: DDD + Hexagonal Architecture (六边形架构) -- **语言**: TypeScript - -## 架构参考 - -请参考 `identity-service` 的架构模式,保持一致性: - -``` -wallet-service/ -├── prisma/ -│ └── schema.prisma # 数据库模型 -├── src/ -│ ├── api/ # Presentation Layer (API层) -│ │ ├── controllers/ # HTTP 控制器 -│ │ │ ├── wallet.controller.ts -│ │ │ ├── ledger.controller.ts -│ │ │ ├── deposit.controller.ts -│ │ │ └── settlement.controller.ts -│ │ ├── dto/ # 数据传输对象 -│ │ │ ├── wallet.dto.ts -│ │ │ ├── ledger.dto.ts -│ │ │ ├── deposit.dto.ts -│ │ │ └── settlement.dto.ts -│ │ └── api.module.ts -│ │ -│ ├── application/ # Application Layer (应用层) -│ │ ├── commands/ # 命令对象 -│ │ │ ├── handle-deposit.command.ts -│ │ │ ├── deduct-for-planting.command.ts -│ │ │ ├── allocate-funds.command.ts -│ │ │ ├── add-rewards.command.ts -│ │ │ ├── settle-rewards.command.ts -│ │ │ └── withdraw.command.ts -│ │ ├── queries/ # 查询对象 -│ │ │ ├── get-my-wallet.query.ts -│ │ │ └── get-my-ledger.query.ts -│ │ └── services/ -│ │ └── wallet-application.service.ts -│ │ -│ ├── domain/ # Domain Layer (领域层) -│ │ ├── aggregates/ # 聚合根 -│ │ │ ├── wallet-account.aggregate.ts -│ │ │ ├── ledger-entry.aggregate.ts -│ │ │ ├── deposit-order.aggregate.ts -│ │ │ └── settlement-order.aggregate.ts -│ │ ├── value-objects/ # 值对象 -│ │ │ ├── wallet-id.vo.ts -│ │ │ ├── money.vo.ts -│ │ │ ├── balance.vo.ts -│ │ │ ├── wallet-balances.vo.ts -│ │ │ ├── wallet-rewards.vo.ts -│ │ │ ├── hashpower.vo.ts -│ │ │ ├── asset-type.enum.ts -│ │ │ ├── chain-type.enum.ts -│ │ │ ├── wallet-status.enum.ts -│ │ │ ├── ledger-entry-type.enum.ts -│ │ │ ├── deposit-status.enum.ts -│ │ │ └── settlement-status.enum.ts -│ │ ├── events/ # 领域事件 -│ │ │ ├── deposit-completed.event.ts -│ │ │ ├── withdrawal-requested.event.ts -│ │ │ ├── reward-moved-to-settleable.event.ts -│ │ │ ├── reward-expired.event.ts -│ │ │ └── settlement-completed.event.ts -│ │ ├── repositories/ # 仓储接口 (Port) -│ │ │ ├── wallet-account.repository.interface.ts -│ │ │ ├── ledger-entry.repository.interface.ts -│ │ │ ├── deposit-order.repository.interface.ts -│ │ │ └── settlement-order.repository.interface.ts -│ │ └── services/ # 领域服务 -│ │ └── wallet-ledger.service.ts -│ │ -│ ├── infrastructure/ # Infrastructure Layer (基础设施层) -│ │ ├── persistence/ # 持久化实现 (Adapter) -│ │ │ ├── entities/ # Prisma 实体映射 -│ │ │ ├── mappers/ # 领域模型与实体的映射 -│ │ │ └── repositories/ # 仓储实现 -│ │ └── infrastructure.module.ts -│ │ -│ ├── app.module.ts -│ └── main.ts -├── .env.development -├── .env.example -├── package.json -└── tsconfig.json -``` - ---- - -## 第一阶段:项目初始化 - -### 1.1 创建 NestJS 项目 - -```bash -cd backend/services -npx @nestjs/cli new wallet-service --skip-git --package-manager npm -cd wallet-service -``` - -### 1.2 安装依赖 - -```bash -npm install @nestjs/config @prisma/client class-validator class-transformer uuid -npm install -D prisma @types/uuid -``` - -### 1.3 配置环境变量 - -创建 `.env.development`: -```env -DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_wallet?schema=public" -NODE_ENV=development -PORT=3002 -``` - -创建 `.env.example`: -```env -DATABASE_URL="postgresql://user:password@host:5432/database?schema=public" -NODE_ENV=development -PORT=3002 -``` - ---- - -## 第二阶段:数据库设计 (Prisma Schema) - -### 2.1 创建 prisma/schema.prisma - -```prisma -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -// ============================================ -// 钱包账户表 (状态表) -// ============================================ -model WalletAccount { - id BigInt @id @default(autoincrement()) @map("wallet_id") - userId BigInt @unique @map("user_id") - - // USDT 余额 - usdtAvailable Decimal @default(0) @map("usdt_available") @db.Decimal(20, 8) - usdtFrozen Decimal @default(0) @map("usdt_frozen") @db.Decimal(20, 8) - - // DST 余额 - dstAvailable Decimal @default(0) @map("dst_available") @db.Decimal(20, 8) - dstFrozen Decimal @default(0) @map("dst_frozen") @db.Decimal(20, 8) - - // BNB 余额 - bnbAvailable Decimal @default(0) @map("bnb_available") @db.Decimal(20, 8) - bnbFrozen Decimal @default(0) @map("bnb_frozen") @db.Decimal(20, 8) - - // OG 余额 - ogAvailable Decimal @default(0) @map("og_available") @db.Decimal(20, 8) - ogFrozen Decimal @default(0) @map("og_frozen") @db.Decimal(20, 8) - - // RWAD 余额 - rwadAvailable Decimal @default(0) @map("rwad_available") @db.Decimal(20, 8) - rwadFrozen Decimal @default(0) @map("rwad_frozen") @db.Decimal(20, 8) - - // 算力 - hashpower Decimal @default(0) @map("hashpower") @db.Decimal(20, 8) - - // 待领取收益 - pendingUsdt Decimal @default(0) @map("pending_usdt") @db.Decimal(20, 8) - pendingHashpower Decimal @default(0) @map("pending_hashpower") @db.Decimal(20, 8) - pendingExpireAt DateTime? @map("pending_expire_at") - - // 可结算收益 - settleableUsdt Decimal @default(0) @map("settleable_usdt") @db.Decimal(20, 8) - settleableHashpower Decimal @default(0) @map("settleable_hashpower") @db.Decimal(20, 8) - - // 已结算总额 - settledTotalUsdt Decimal @default(0) @map("settled_total_usdt") @db.Decimal(20, 8) - settledTotalHashpower Decimal @default(0) @map("settled_total_hashpower") @db.Decimal(20, 8) - - // 已过期总额 - expiredTotalUsdt Decimal @default(0) @map("expired_total_usdt") @db.Decimal(20, 8) - expiredTotalHashpower Decimal @default(0) @map("expired_total_hashpower") @db.Decimal(20, 8) - - // 状态 - status String @default("ACTIVE") @map("status") @db.VarChar(20) - - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - @@map("wallet_accounts") - @@index([userId]) - @@index([usdtAvailable(sort: Desc)]) - @@index([hashpower(sort: Desc)]) - @@index([status]) -} - -// ============================================ -// 账本流水表 (行为表, append-only) -// ============================================ -model LedgerEntry { - id BigInt @id @default(autoincrement()) @map("entry_id") - userId BigInt @map("user_id") - - // 流水类型 - entryType String @map("entry_type") @db.VarChar(50) - - // 金额变动 (正数入账, 负数支出) - amount Decimal @map("amount") @db.Decimal(20, 8) - assetType String @map("asset_type") @db.VarChar(20) - - // 余额快照 (操作后余额) - balanceAfter Decimal? @map("balance_after") @db.Decimal(20, 8) - - // 关联引用 - refOrderId String? @map("ref_order_id") @db.VarChar(100) - refTxHash String? @map("ref_tx_hash") @db.VarChar(100) - - // 备注 - memo String? @map("memo") @db.VarChar(500) - - // 扩展数据 - payloadJson Json? @map("payload_json") - - createdAt DateTime @default(now()) @map("created_at") - - @@map("wallet_ledger_entries") - @@index([userId, createdAt(sort: Desc)]) - @@index([entryType]) - @@index([assetType]) - @@index([refOrderId]) - @@index([refTxHash]) - @@index([createdAt]) -} - -// ============================================ -// 充值订单表 -// ============================================ -model DepositOrder { - id BigInt @id @default(autoincrement()) @map("order_id") - userId BigInt @map("user_id") - - // 充值信息 - chainType String @map("chain_type") @db.VarChar(20) - amount Decimal @map("amount") @db.Decimal(20, 8) - txHash String @unique @map("tx_hash") @db.VarChar(100) - - // 状态 - status String @default("PENDING") @map("status") @db.VarChar(20) - confirmedAt DateTime? @map("confirmed_at") - - createdAt DateTime @default(now()) @map("created_at") - - @@map("deposit_orders") - @@index([userId]) - @@index([txHash]) - @@index([status]) - @@index([chainType]) -} - -// ============================================ -// 结算订单表 -// ============================================ -model SettlementOrder { - id BigInt @id @default(autoincrement()) @map("order_id") - userId BigInt @map("user_id") - - // 结算信息 - usdtAmount Decimal @map("usdt_amount") @db.Decimal(20, 8) - settleCurrency String @map("settle_currency") @db.VarChar(10) - - // SWAP 信息 - swapTxHash String? @map("swap_tx_hash") @db.VarChar(100) - receivedAmount Decimal? @map("received_amount") @db.Decimal(20, 8) - - // 状态 - status String @default("PENDING") @map("status") @db.VarChar(20) - settledAt DateTime? @map("settled_at") - - createdAt DateTime @default(now()) @map("created_at") - - @@map("settlement_orders") - @@index([userId]) - @@index([status]) - @@index([settleCurrency]) - @@index([createdAt]) -} -``` - -### 2.2 初始化数据库 - -```bash -npx prisma migrate dev --name init -npx prisma generate -``` - ---- - -## 第三阶段:领域层实现 - -### 3.1 值对象 (Value Objects) - -#### 3.1.1 asset-type.enum.ts -```typescript -export enum AssetType { - USDT = 'USDT', - DST = 'DST', - BNB = 'BNB', - OG = 'OG', - RWAD = 'RWAD', - HASHPOWER = 'HASHPOWER', -} -``` - -#### 3.1.2 chain-type.enum.ts -```typescript -export enum ChainType { - KAVA = 'KAVA', - DST = 'DST', - BSC = 'BSC', -} -``` - -#### 3.1.3 wallet-status.enum.ts -```typescript -export enum WalletStatus { - ACTIVE = 'ACTIVE', - FROZEN = 'FROZEN', - CLOSED = 'CLOSED', -} -``` - -#### 3.1.4 ledger-entry-type.enum.ts -```typescript -export enum LedgerEntryType { - DEPOSIT_KAVA = 'DEPOSIT_KAVA', - DEPOSIT_BSC = 'DEPOSIT_BSC', - PLANT_PAYMENT = 'PLANT_PAYMENT', - REWARD_PENDING = 'REWARD_PENDING', - REWARD_TO_SETTLEABLE = 'REWARD_TO_SETTLEABLE', - REWARD_EXPIRED = 'REWARD_EXPIRED', - REWARD_SETTLED = 'REWARD_SETTLED', - TRANSFER_TO_POOL = 'TRANSFER_TO_POOL', - SWAP_EXECUTED = 'SWAP_EXECUTED', - WITHDRAWAL = 'WITHDRAWAL', - TRANSFER_IN = 'TRANSFER_IN', - TRANSFER_OUT = 'TRANSFER_OUT', - FREEZE = 'FREEZE', - UNFREEZE = 'UNFREEZE', -} -``` - -#### 3.1.5 deposit-status.enum.ts -```typescript -export enum DepositStatus { - PENDING = 'PENDING', - CONFIRMED = 'CONFIRMED', - FAILED = 'FAILED', -} -``` - -#### 3.1.6 settlement-status.enum.ts -```typescript -export enum SettlementStatus { - PENDING = 'PENDING', - SWAPPING = 'SWAPPING', - COMPLETED = 'COMPLETED', - FAILED = 'FAILED', -} - -export enum SettleCurrency { - BNB = 'BNB', - OG = 'OG', - USDT = 'USDT', - DST = 'DST', -} -``` - -#### 3.1.7 money.vo.ts -```typescript -export class Money { - private constructor( - public readonly amount: number, - public readonly currency: string, - ) { - if (amount < 0) { - throw new Error('Money amount cannot be negative'); - } - } - - static create(amount: number, currency: string): Money { - return new Money(amount, currency); - } - - static USDT(amount: number): Money { - return new Money(amount, 'USDT'); - } - - static zero(currency: string = 'USDT'): Money { - return new Money(0, currency); - } - - add(other: Money): Money { - this.ensureSameCurrency(other); - return new Money(this.amount + other.amount, this.currency); - } - - subtract(other: Money): Money { - this.ensureSameCurrency(other); - if (this.amount < other.amount) { - throw new Error('Insufficient balance'); - } - return new Money(this.amount - other.amount, this.currency); - } - - lessThan(other: Money): boolean { - this.ensureSameCurrency(other); - return this.amount < other.amount; - } - - greaterThan(other: Money): boolean { - this.ensureSameCurrency(other); - return this.amount > other.amount; - } - - isZero(): boolean { - return this.amount === 0; - } - - private ensureSameCurrency(other: Money): void { - if (this.currency !== other.currency) { - throw new Error('Currency mismatch'); - } - } -} -``` - -#### 3.1.8 balance.vo.ts -```typescript -import { Money } from './money.vo'; - -export class Balance { - constructor( - public readonly available: Money, - public readonly frozen: Money, - ) {} - - static zero(): Balance { - return new Balance(Money.zero(), Money.zero()); - } - - add(amount: Money): Balance { - return new Balance(this.available.add(amount), this.frozen); - } - - deduct(amount: Money): Balance { - if (this.available.lessThan(amount)) { - throw new Error('Insufficient available balance'); - } - return new Balance(this.available.subtract(amount), this.frozen); - } - - freeze(amount: Money): Balance { - if (this.available.lessThan(amount)) { - throw new Error('Insufficient available balance to freeze'); - } - return new Balance( - this.available.subtract(amount), - this.frozen.add(amount), - ); - } - - unfreeze(amount: Money): Balance { - if (this.frozen.lessThan(amount)) { - throw new Error('Insufficient frozen balance to unfreeze'); - } - return new Balance( - this.available.add(amount), - this.frozen.subtract(amount), - ); - } - - get total(): Money { - return this.available.add(this.frozen); - } -} -``` - -#### 3.1.9 hashpower.vo.ts -```typescript -export class Hashpower { - private constructor(public readonly value: number) { - if (value < 0) { - throw new Error('Hashpower cannot be negative'); - } - } - - static create(value: number): Hashpower { - return new Hashpower(value); - } - - static zero(): Hashpower { - return new Hashpower(0); - } - - add(other: Hashpower): Hashpower { - return new Hashpower(this.value + other.value); - } - - subtract(other: Hashpower): Hashpower { - if (this.value < other.value) { - throw new Error('Insufficient hashpower'); - } - return new Hashpower(this.value - other.value); - } - - isZero(): boolean { - return this.value === 0; - } -} -``` - -### 3.2 聚合根 (Aggregates) - -#### 3.2.1 wallet-account.aggregate.ts - -实现 `WalletAccount` 聚合根,包含: -- 余额管理 (USDT/DST/BNB/OG/RWAD/算力) -- 收益汇总 (待领取/可结算/已结算/过期) -- 核心领域行为:deposit, deduct, freeze, unfreeze, addPendingReward, movePendingToSettleable, settleReward, withdraw - -#### 3.2.2 ledger-entry.aggregate.ts - -实现 `LedgerEntry` 聚合根 (append-only),包含: -- 流水类型 -- 金额变动 -- 余额快照 -- 关联引用 - -### 3.3 仓储接口 (Repository Interfaces) - -#### 3.3.1 wallet-account.repository.interface.ts -```typescript -export interface IWalletAccountRepository { - save(wallet: WalletAccount): Promise; - findById(walletId: bigint): Promise; - findByUserId(userId: bigint): Promise; - getOrCreate(userId: bigint): Promise; - findByUserIds(userIds: bigint[]): Promise>; -} - -export const WALLET_ACCOUNT_REPOSITORY = Symbol('IWalletAccountRepository'); -``` - -#### 3.3.2 ledger-entry.repository.interface.ts -```typescript -export interface ILedgerEntryRepository { - save(entry: LedgerEntry): Promise; - saveAll(entries: LedgerEntry[]): Promise; - findByUserId(userId: bigint, filters?: LedgerFilters, pagination?: Pagination): Promise; - findByRefOrderId(refOrderId: string): Promise; -} - -export const LEDGER_ENTRY_REPOSITORY = Symbol('ILedgerEntryRepository'); -``` - ---- - -## 第四阶段:基础设施层实现 - -### 4.1 实体映射 (Entities) - -创建 Prisma 实体到领域模型的映射类。 - -### 4.2 仓储实现 (Repository Implementations) - -实现所有仓储接口,使用 Prisma Client 进行数据库操作。 - ---- - -## 第五阶段:应用层实现 - -### 5.1 命令对象 (Commands) - -```typescript -// handle-deposit.command.ts -export class HandleDepositCommand { - constructor( - public readonly userId: string, - public readonly amount: number, - public readonly chainType: ChainType, - public readonly txHash: string, - ) {} -} - -// deduct-for-planting.command.ts -export class DeductForPlantingCommand { - constructor( - public readonly userId: string, - public readonly amount: number, - public readonly orderId: string, - ) {} -} -``` - -### 5.2 应用服务 (Application Service) - -实现 `WalletApplicationService`,包含所有用例: -- handleDeposit - 处理充值 -- deductForPlanting - 认种扣款 -- allocateFunds - 资金分配 -- addRewards - 增加奖励 -- movePendingToSettleable - 待领取→可结算 -- settleRewards - 结算收益 -- getMyWallet - 查询我的钱包 -- getMyLedger - 查询我的流水 - ---- - -## 第六阶段:API层实现 - -### 6.1 DTO 定义 - -```typescript -// wallet.dto.ts -export class WalletDTO { - walletId: string; - userId: string; - balances: BalancesDTO; - rewards: RewardsDTO; - status: string; -} - -export class BalancesDTO { - usdt: BalanceDTO; - dst: BalanceDTO; - bnb: BalanceDTO; - og: BalanceDTO; - rwad: BalanceDTO; - hashpower: number; -} - -export class BalanceDTO { - available: number; - frozen: number; -} -``` - -### 6.2 控制器实现 - -```typescript -// wallet.controller.ts -@Controller('wallet') -export class WalletController { - constructor(private readonly walletService: WalletApplicationService) {} - - @Get('my-wallet') - @UseGuards(JwtAuthGuard) - async getMyWallet(@CurrentUser() user: User): Promise { - return this.walletService.getMyWallet({ userId: user.id }); - } -} - -// ledger.controller.ts -@Controller('wallet/ledger') -export class LedgerController { - constructor(private readonly walletService: WalletApplicationService) {} - - @Get('my-ledger') - @UseGuards(JwtAuthGuard) - async getMyLedger( - @CurrentUser() user: User, - @Query() query: GetMyLedgerQueryDTO, - ): Promise { - return this.walletService.getMyLedger({ - userId: user.id, - ...query, - }); - } -} -``` - ---- - -## 第七阶段:模块配置 - -### 7.1 app.module.ts - -```typescript -@Module({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - envFilePath: `.env.${process.env.NODE_ENV || 'development'}`, - }), - ApiModule, - InfrastructureModule, - ], -}) -export class AppModule {} -``` - ---- - -## 关键业务规则 (不变式) - -1. **余额不能为负**: 任何币种的可用余额不能为负数 -2. **流水表只能追加**: 账本流水表只能 INSERT,不能 UPDATE/DELETE -3. **每笔流水必须有余额快照**: 每笔流水必须记录操作后的余额快照 -4. **结算金额不能超过可结算余额**: 结算金额必须 ≤ 可结算余额 - ---- - -## API 端点汇总 - -| 方法 | 路径 | 描述 | 认证 | -|------|------|------|------| -| GET | /wallet/my-wallet | 查询我的钱包 | 需要 | -| GET | /wallet/ledger/my-ledger | 查询我的流水 | 需要 | -| POST | /wallet/deposit | 充值入账 (内部) | 服务间 | -| POST | /wallet/withdraw | 提现申请 | 需要 | -| POST | /wallet/settle | 结算收益 | 需要 | - ---- - -## 开发顺序建议 - -1. 项目初始化和 Prisma Schema -2. 值对象实现 -3. 聚合根实现 -4. 仓储接口定义 -5. 仓储实现 -6. 领域服务实现 -7. 应用服务实现 -8. DTO 和控制器实现 -9. 模块配置和测试 - ---- - -## 注意事项 - -1. 所有金额使用 `Decimal(20, 8)` 存储,避免浮点数精度问题 -2. 流水表是 append-only,不允许更新或删除 -3. 每次余额变动都要创建对应的流水记录 -4. 使用事务确保余额和流水的一致性 -5. 参考 identity-service 的代码风格和命名规范 diff --git a/backend/services/wallet-service/docs/API.md b/backend/services/wallet-service/docs/API.md new file mode 100644 index 00000000..10f2de20 --- /dev/null +++ b/backend/services/wallet-service/docs/API.md @@ -0,0 +1,409 @@ +# Wallet Service API 文档 + +## 概述 + +本文档描述 Wallet Service 的所有 HTTP API 接口。服务基础路径: `/api/v1` + +## 认证 + +除标注为 `公开` 的接口外,所有接口都需要 JWT Bearer Token 认证: + +```http +Authorization: Bearer +``` + +JWT Payload 结构: +```json +{ + "sub": "用户ID", + "seq": 1001, + "exp": 1700000000 +} +``` + +## 响应格式 + +### 成功响应 + +```json +{ + "success": true, + "data": { ... }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +### 错误响应 + +```json +{ + "success": false, + "code": "ERROR_CODE", + "message": "错误描述", + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +--- + +## 1. 健康检查 + +### GET /api/v1/health + +检查服务健康状态。 + +**认证**: 公开 + +**响应示例**: +```json +{ + "success": true, + "data": { + "status": "ok", + "service": "wallet-service", + "timestamp": "2024-01-01T00:00:00.000Z" + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +--- + +## 2. 钱包接口 + +### GET /api/v1/wallet/my-wallet + +查询当前用户的钱包信息。 + +**认证**: 需要 + +**响应示例**: +```json +{ + "success": true, + "data": { + "walletId": "1", + "userId": "12345", + "balances": { + "usdt": { "available": 1000.5, "frozen": 0 }, + "dst": { "available": 0, "frozen": 0 }, + "bnb": { "available": 0.1, "frozen": 0 }, + "og": { "available": 0, "frozen": 0 }, + "rwad": { "available": 100, "frozen": 0 } + }, + "hashpower": 500, + "rewards": { + "pendingUsdt": 50, + "pendingHashpower": 100, + "pendingExpireAt": "2024-01-02T00:00:00.000Z", + "settleableUsdt": 200, + "settleableHashpower": 400, + "settledTotalUsdt": 1000, + "settledTotalHashpower": 2000, + "expiredTotalUsdt": 50, + "expiredTotalHashpower": 100 + }, + "status": "ACTIVE" + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +**字段说明**: + +| 字段 | 类型 | 描述 | +|-----|------|-----| +| walletId | string | 钱包ID | +| userId | string | 用户ID | +| balances.{coin}.available | number | 可用余额 | +| balances.{coin}.frozen | number | 冻结余额 | +| hashpower | number | 当前算力 | +| rewards.pendingUsdt | number | 待领取USDT奖励 | +| rewards.pendingHashpower | number | 待领取算力奖励 | +| rewards.pendingExpireAt | string | 待领取奖励过期时间 | +| rewards.settleableUsdt | number | 可结算USDT | +| rewards.settleableHashpower | number | 可结算算力 | +| rewards.settledTotalUsdt | number | 已结算USDT累计 | +| rewards.settledTotalHashpower | number | 已结算算力累计 | +| rewards.expiredTotalUsdt | number | 已过期USDT累计 | +| rewards.expiredTotalHashpower | number | 已过期算力累计 | +| status | string | 钱包状态: ACTIVE, FROZEN | + +--- + +### POST /api/v1/wallet/claim-rewards + +领取待领取的奖励,将其转为可结算状态。 + +**认证**: 需要 + +**请求体**: 无 + +**响应示例**: +```json +{ + "success": true, + "data": { + "message": "Rewards claimed successfully" + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +**错误码**: + +| 错误码 | HTTP状态 | 描述 | +|-------|---------|------| +| NO_PENDING_REWARDS | 400 | 没有待领取的奖励 | +| WALLET_FROZEN | 403 | 钱包已冻结 | + +--- + +### POST /api/v1/wallet/settle + +结算可结算的USDT奖励为指定币种。 + +**认证**: 需要 + +**请求体**: +```json +{ + "usdtAmount": 100, + "settleCurrency": "BNB" +} +``` + +| 字段 | 类型 | 必填 | 描述 | +|-----|------|-----|------| +| usdtAmount | number | 是 | 结算USDT金额 (>0) | +| settleCurrency | string | 是 | 结算目标币种: BNB, USDT, DST, OG, RWAD | + +**响应示例**: +```json +{ + "success": true, + "data": { + "settlementOrderId": "12345" + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +**错误码**: + +| 错误码 | HTTP状态 | 描述 | +|-------|---------|------| +| INSUFFICIENT_BALANCE | 400 | 可结算余额不足 | +| WALLET_FROZEN | 403 | 钱包已冻结 | +| VALIDATION_ERROR | 400 | 参数验证失败 | + +--- + +## 3. 流水接口 + +### GET /api/v1/wallet/ledger/my-ledger + +查询当前用户的账本流水记录。 + +**认证**: 需要 + +**查询参数**: + +| 参数 | 类型 | 必填 | 默认值 | 描述 | +|-----|------|-----|-------|------| +| page | number | 否 | 1 | 页码 (>=1) | +| pageSize | number | 否 | 20 | 每页数量 (1-100) | +| entryType | string | 否 | - | 流水类型过滤 | +| assetType | string | 否 | - | 资产类型过滤 | +| startDate | string | 否 | - | 开始日期 (ISO8601) | +| endDate | string | 否 | - | 结束日期 (ISO8601) | + +**entryType 可选值**: +- `DEPOSIT_KAVA` - KAVA链充值 +- `DEPOSIT_BSC` - BSC链充值 +- `PLANT_PAYMENT` - 认种支付 +- `REWARD_PENDING` - 奖励待领取 +- `REWARD_TO_SETTLEABLE` - 奖励转可结算 +- `REWARD_SETTLED` - 奖励已结算 +- `REWARD_EXPIRED` - 奖励已过期 +- `WITHDRAWAL` - 提现 +- `ADMIN_ADJUST` - 管理员调整 + +**assetType 可选值**: +- `USDT`, `DST`, `BNB`, `OG`, `RWAD`, `HASHPOWER` + +**响应示例**: +```json +{ + "success": true, + "data": { + "data": [ + { + "id": "1001", + "entryType": "DEPOSIT_KAVA", + "amount": 100, + "assetType": "USDT", + "balanceAfter": 1100, + "refOrderId": null, + "refTxHash": "0x1234...5678", + "memo": "Deposit from KAVA", + "createdAt": "2024-01-01T10:00:00.000Z" + }, + { + "id": "1002", + "entryType": "PLANT_PAYMENT", + "amount": -50, + "assetType": "USDT", + "balanceAfter": 1050, + "refOrderId": "order_123", + "refTxHash": null, + "memo": "Plant payment", + "createdAt": "2024-01-01T11:00:00.000Z" + } + ], + "total": 100, + "page": 1, + "pageSize": 20, + "totalPages": 5 + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +**字段说明**: + +| 字段 | 类型 | 描述 | +|-----|------|-----| +| id | string | 流水ID | +| entryType | string | 流水类型 | +| amount | number | 金额 (正入账/负支出) | +| assetType | string | 资产类型 | +| balanceAfter | number | 操作后余额 | +| refOrderId | string | 关联订单号 | +| refTxHash | string | 关联交易哈希 | +| memo | string | 备注 | +| createdAt | string | 创建时间 | + +--- + +## 4. 充值接口 (内部服务) + +### POST /api/v1/wallet/deposit + +处理链上充值确认后的入账。 + +**认证**: 公开 (仅限内部服务调用) + +**请求体**: +```json +{ + "userId": "12345", + "amount": 100, + "chainType": "KAVA", + "txHash": "0x1234567890abcdef..." +} +``` + +| 字段 | 类型 | 必填 | 描述 | +|-----|------|-----|------| +| userId | string | 是 | 用户ID | +| amount | number | 是 | 充值金额 (>0) | +| chainType | string | 是 | 链类型: KAVA, BSC | +| txHash | string | 是 | 交易哈希 (唯一) | + +**响应示例**: +```json +{ + "success": true, + "data": { + "message": "Deposit processed successfully" + }, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +**错误码**: + +| 错误码 | HTTP状态 | 描述 | +|-------|---------|------| +| DUPLICATE_TRANSACTION | 409 | 重复交易 (txHash已存在) | +| VALIDATION_ERROR | 400 | 参数验证失败 | + +--- + +## 错误码汇总 + +| 错误码 | HTTP状态 | 描述 | +|-------|---------|------| +| VALIDATION_ERROR | 400 | 参数验证失败 | +| UNAUTHORIZED | 401 | 未认证 | +| FORBIDDEN | 403 | 无权限 | +| NOT_FOUND | 404 | 资源不存在 | +| DUPLICATE_TRANSACTION | 409 | 重复交易 | +| INSUFFICIENT_BALANCE | 400 | 余额不足 | +| WALLET_FROZEN | 403 | 钱包已冻结 | +| WALLET_NOT_FOUND | 404 | 钱包不存在 | +| NO_PENDING_REWARDS | 400 | 没有待领取的奖励 | +| INTERNAL_ERROR | 500 | 内部错误 | + +--- + +## Swagger 文档 + +启动服务后访问: `http://localhost:3000/api-docs` + +--- + +## 调用示例 + +### cURL + +```bash +# 获取钱包信息 +curl -X GET "http://localhost:3000/api/v1/wallet/my-wallet" \ + -H "Authorization: Bearer " + +# 领取奖励 +curl -X POST "http://localhost:3000/api/v1/wallet/claim-rewards" \ + -H "Authorization: Bearer " + +# 结算奖励 +curl -X POST "http://localhost:3000/api/v1/wallet/settle" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"usdtAmount": 100, "settleCurrency": "BNB"}' + +# 查询流水 +curl -X GET "http://localhost:3000/api/v1/wallet/ledger/my-ledger?page=1&pageSize=10" \ + -H "Authorization: Bearer " + +# 充值入账 (内部服务) +curl -X POST "http://localhost:3000/api/v1/wallet/deposit" \ + -H "Content-Type: application/json" \ + -d '{"userId": "12345", "amount": 100, "chainType": "KAVA", "txHash": "0x..."}' +``` + +### JavaScript (Fetch) + +```javascript +// 获取钱包信息 +const response = await fetch('/api/v1/wallet/my-wallet', { + headers: { + 'Authorization': `Bearer ${token}` + } +}); +const data = await response.json(); + +// 结算奖励 +const settleResponse = await fetch('/api/v1/wallet/settle', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + usdtAmount: 100, + settleCurrency: 'BNB' + }) +}); +``` diff --git a/backend/services/wallet-service/docs/ARCHITECTURE.md b/backend/services/wallet-service/docs/ARCHITECTURE.md new file mode 100644 index 00000000..bf0c2578 --- /dev/null +++ b/backend/services/wallet-service/docs/ARCHITECTURE.md @@ -0,0 +1,383 @@ +# Wallet Service 架构设计文档 + +## 概述 + +Wallet Service 是 RWA (Real World Assets) 榴莲认种平台的核心钱包与账本服务,负责管理用户资产、处理充值/提现、记录交易流水、管理奖励结算等功能。 + +## 技术栈 + +| 组件 | 技术选型 | 版本 | +|------|---------|------| +| 运行时 | Node.js | 20.x | +| 框架 | NestJS | 10.x | +| 语言 | TypeScript | 5.x | +| ORM | Prisma | 5.x | +| 数据库 | PostgreSQL | 15.x | +| 认证 | JWT (passport-jwt) | - | +| 精度计算 | Decimal.js | 10.x | +| API文档 | Swagger | 7.x | + +## 架构模式 + +本服务采用 **领域驱动设计 (DDD)** + **CQRS** 架构模式: + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Presentation Layer │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ +│ │ WalletController │ │ LedgerController │ │ DepositController │ │ +│ │ /wallet/* │ │ /wallet/ledger │ │ /wallet/deposit │ │ +│ └────────┬─────────┘ └────────┬─────────┘ └──────────┬───────────┘ │ +└───────────┼─────────────────────┼──────────────────────┼────────────────┘ + │ │ │ +┌───────────┼─────────────────────┼──────────────────────┼────────────────┐ +│ │ Application Layer (CQRS) │ │ +│ ▼ ▼ ▼ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ WalletApplicationService │ │ +│ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ +│ │ │ Commands │ │ Queries │ │ │ +│ │ │ - HandleDeposit │ │ - GetMyWallet │ │ │ +│ │ │ - DeductForPlanting │ │ - GetMyLedger │ │ │ +│ │ │ - AddRewards │ │ │ │ │ +│ │ │ - ClaimRewards │ │ │ │ │ +│ │ │ - SettleRewards │ │ │ │ │ +│ │ └─────────────────────┘ └─────────────────────────────┘ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────────────┼───────────────────────────────────────┐ +│ Domain Layer │ │ +│ ┌──────────────────────────────┼──────────────────────────────────┐ │ +│ │ Aggregates │ │ +│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ +│ │ │ WalletAccount │ │ DepositOrder │ │ SettlementOrder │ │ │ +│ │ │ (聚合根) │ │ │ │ │ │ │ +│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ +│ │ │ │ │ +│ │ ┌──────┴──────────────────────────────────────────────────┐ │ │ +│ │ │ LedgerEntry │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────┐ │ +│ │ Value Objects │ │ +│ │ Money │ Balance │ Hashpower │ UserId │ WalletId │ Enums │ │ +│ └───────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────┐ │ +│ │ Domain Events │ │ +│ │ DepositCompleted │ BalanceDeducted │ RewardAdded │ etc. │ │ +│ └───────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────────────┼───────────────────────────────────────┐ +│ Infrastructure Layer │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ Repository Implementations │ │ +│ │ WalletAccountRepository │ LedgerEntryRepository │ etc. │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ Prisma Service │ │ +│ │ (Database Connection Pool) │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ PostgreSQL │ + │ Database │ + └──────────────────┘ +``` + +## 目录结构 + +``` +src/ +├── api/ # 表示层 (Presentation Layer) +│ ├── controllers/ # HTTP 控制器 +│ │ ├── wallet.controller.ts # 钱包相关接口 +│ │ ├── ledger.controller.ts # 流水查询接口 +│ │ ├── deposit.controller.ts # 充值入账接口 (内部) +│ │ └── health.controller.ts # 健康检查 +│ ├── dto/ +│ │ ├── request/ # 请求 DTO +│ │ └── response/ # 响应 DTO +│ └── api.module.ts +│ +├── application/ # 应用层 (Application Layer) +│ ├── commands/ # 命令 (写操作) +│ │ ├── handle-deposit.command.ts +│ │ ├── deduct-for-planting.command.ts +│ │ ├── add-rewards.command.ts +│ │ ├── claim-rewards.command.ts +│ │ └── settle-rewards.command.ts +│ ├── queries/ # 查询 (读操作) +│ │ ├── get-my-wallet.query.ts +│ │ └── get-my-ledger.query.ts +│ └── services/ +│ └── wallet-application.service.ts +│ +├── domain/ # 领域层 (Domain Layer) +│ ├── aggregates/ # 聚合 +│ │ ├── wallet-account.aggregate.ts +│ │ ├── ledger-entry.aggregate.ts +│ │ ├── deposit-order.aggregate.ts +│ │ └── settlement-order.aggregate.ts +│ ├── value-objects/ # 值对象 +│ │ ├── money.vo.ts +│ │ ├── balance.vo.ts +│ │ ├── hashpower.vo.ts +│ │ ├── user-id.vo.ts +│ │ ├── wallet-id.vo.ts +│ │ └── [各种枚举].enum.ts +│ ├── events/ # 领域事件 +│ │ ├── deposit-completed.event.ts +│ │ ├── balance-deducted.event.ts +│ │ └── [其他事件].ts +│ └── repositories/ # 仓储接口 +│ ├── wallet-account.repository.interface.ts +│ └── [其他接口].ts +│ +├── infrastructure/ # 基础设施层 +│ ├── persistence/ +│ │ ├── prisma/ +│ │ │ └── prisma.service.ts +│ │ └── repositories/ # 仓储实现 +│ │ ├── wallet-account.repository.impl.ts +│ │ └── [其他实现].ts +│ └── infrastructure.module.ts +│ +├── shared/ # 共享模块 +│ ├── decorators/ # 装饰器 +│ ├── filters/ # 异常过滤器 +│ ├── guards/ # 守卫 +│ ├── interceptors/ # 拦截器 +│ ├── strategies/ # 认证策略 +│ └── exceptions/ # 自定义异常 +│ +├── app.module.ts # 根模块 +└── main.ts # 入口文件 +``` + +## 核心领域模型 + +### 1. WalletAccount (钱包账户聚合) + +钱包账户是核心聚合根,管理用户的所有资产状态: + +```typescript +WalletAccount { + // 标识 + walletId: WalletId + userId: UserId + + // 多币种余额 + balances: { + usdt: Balance { available, frozen } + dst: Balance { available, frozen } + bnb: Balance { available, frozen } + og: Balance { available, frozen } + rwad: Balance { available, frozen } + } + + // 算力 + hashpower: Hashpower + + // 奖励状态机 + rewards: { + pending -> 待领取 (24小时内必须领取) + settleable -> 可结算 (可兑换为其他币种) + settled -> 已结算累计 + expired -> 已过期累计 + } + + // 状态 + status: ACTIVE | FROZEN +} +``` + +### 2. 奖励状态机 + +``` + ┌──────────────────┐ + │ addReward() │ + └────────┬─────────┘ + ▼ + ┌──────────────────┐ + ┌─────────│ PENDING │─────────┐ + │ │ (待领取, 有过期) │ │ + │ └──────────────────┘ │ + │ │ │ + expire() │ claim() │ + │ │ │ + ▼ ▼ │ +┌──────────────────┐ ┌──────────────────┐ │ +│ EXPIRED │ │ SETTLEABLE │ │ +│ (已过期累计) │ │ (可结算) │ │ +└──────────────────┘ └────────┬─────────┘ │ + │ │ + settle() │ + │ │ + ▼ │ + ┌──────────────────┐ │ + │ SETTLED │ │ + │ (已结算累计) │ │ + └──────────────────┘ │ +``` + +### 3. LedgerEntry (账本流水) + +采用 **Append-Only** 模式,不可修改,确保审计追溯: + +```typescript +LedgerEntry { + entryType: LedgerEntryType // 流水类型 + amount: Money // 金额 (正入账/负支出) + assetType: AssetType // 资产类型 + balanceAfter: Money? // 操作后余额快照 + refOrderId?: string // 关联订单号 + refTxHash?: string // 关联交易哈希 + memo?: string // 备注 +} +``` + +### 4. 流水类型枚举 + +```typescript +enum LedgerEntryType { + // 充值相关 + DEPOSIT_KAVA = 'DEPOSIT_KAVA' // KAVA链充值 + DEPOSIT_BSC = 'DEPOSIT_BSC' // BSC链充值 + + // 认种相关 + PLANT_PAYMENT = 'PLANT_PAYMENT' // 认种支付 + + // 奖励相关 + REWARD_PENDING = 'REWARD_PENDING' // 奖励待领取 + REWARD_TO_SETTLEABLE = 'REWARD_TO_SETTLEABLE' // 奖励转可结算 + REWARD_SETTLED = 'REWARD_SETTLED' // 奖励已结算 + REWARD_EXPIRED = 'REWARD_EXPIRED' // 奖励已过期 + + // 其他 + WITHDRAWAL = 'WITHDRAWAL' // 提现 + ADMIN_ADJUST = 'ADMIN_ADJUST' // 管理员调整 +} +``` + +## 数据库设计 + +### ER 图 + +``` +┌─────────────────────┐ ┌─────────────────────┐ +│ wallet_accounts │ │ wallet_ledger_ │ +│ │ │ entries │ +├─────────────────────┤ ├─────────────────────┤ +│ wallet_id (PK) │───┐ │ entry_id (PK) │ +│ user_id (UK) │ │ │ user_id (FK) │ +│ usdt_available │ └──▶│ entry_type │ +│ usdt_frozen │ │ amount │ +│ dst_available │ │ asset_type │ +│ dst_frozen │ │ balance_after │ +│ bnb_available │ │ ref_order_id │ +│ bnb_frozen │ │ ref_tx_hash │ +│ og_available │ │ memo │ +│ og_frozen │ │ payload_json │ +│ rwad_available │ │ created_at │ +│ rwad_frozen │ └─────────────────────┘ +│ hashpower │ +│ pending_usdt │ ┌─────────────────────┐ +│ pending_hashpower │ │ deposit_orders │ +│ pending_expire_at │ ├─────────────────────┤ +│ settleable_usdt │ │ order_id (PK) │ +│ settleable_hashpower│ │ user_id (FK) │ +│ settled_total_usdt │ │ chain_type │ +│ settled_total_hp │ │ amount │ +│ expired_total_usdt │ │ tx_hash (UK) │ +│ expired_total_hp │ │ status │ +│ status │ │ confirmed_at │ +│ created_at │ │ created_at │ +│ updated_at │ └─────────────────────┘ +└─────────────────────┘ + ┌─────────────────────┐ + │ settlement_orders │ + ├─────────────────────┤ + │ order_id (PK) │ + │ user_id (FK) │ + │ usdt_amount │ + │ settle_currency │ + │ swap_tx_hash │ + │ received_amount │ + │ status │ + │ settled_at │ + │ created_at │ + └─────────────────────┘ +``` + +### 精度处理 + +所有金额字段使用 `Decimal(20, 8)` 存储: +- 20位总精度,8位小数 +- 支持最大 999,999,999,999.99999999 +- 使用 `decimal.js` 库进行计算,避免浮点数精度问题 + +## 安全设计 + +### 认证与授权 + +``` + ┌────────────────┐ + │ JWT Token │ + │ (Bearer) │ + └───────┬────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ JwtAuthGuard │ +│ - 验证 Token 签名 │ +│ - 检查 Token 过期 │ +│ - 提取 userId 和 seq │ +└───────────────────────────┬─────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ @Public() 装饰器 │ +│ - 标记为公开接口,跳过认证 │ +│ - 用于内部服务调用 (如 /deposit) │ +└─────────────────────────────────────────────────────┘ +``` + +### 内部服务调用 + +充值入账接口 (`POST /wallet/deposit`) 使用 `@Public()` 装饰器,跳过 JWT 认证,仅供内部链监控服务调用。生产环境应通过网络隔离保护。 + +## 扩展点 + +### 1. 领域事件 + +所有聚合操作都会产生领域事件,可用于: +- 异步通知 +- 事件溯源 +- 跨服务通信 + +```typescript +wallet.deposit(amount, chainType, txHash); +// 产生 DepositCompletedEvent + +wallet.deduct(amount, reason); +// 产生 BalanceDeductedEvent +``` + +### 2. 仓储接口 + +基础设施层实现可替换: +- 当前:Prisma + PostgreSQL +- 可扩展:Redis 缓存、MongoDB 等 + +### 3. 消息队列集成 + +预留事件发布接口,可集成: +- RabbitMQ +- Kafka +- Redis Pub/Sub diff --git a/backend/services/wallet-service/docs/DEPLOYMENT.md b/backend/services/wallet-service/docs/DEPLOYMENT.md new file mode 100644 index 00000000..3382192d --- /dev/null +++ b/backend/services/wallet-service/docs/DEPLOYMENT.md @@ -0,0 +1,787 @@ +# Wallet Service 部署文档 + +## 概述 + +本文档描述 Wallet Service 的部署架构、配置方式、Docker 容器化以及生产环境部署流程。 + +--- + +## 部署架构 + +``` + ┌─────────────────┐ + │ Load Balancer │ + │ (Nginx/ALB) │ + └────────┬────────┘ + │ + ┌────────────────────────┼────────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Wallet Service │ │ Wallet Service │ │ Wallet Service │ +│ Instance 1 │ │ Instance 2 │ │ Instance N │ +└────────┬────────┘ └────────┬────────┘ └────────┬────────┘ + │ │ │ + └──────────────────────┼──────────────────────┘ + │ + ┌────────┴────────┐ + │ PostgreSQL │ + │ (Primary) │ + └────────┬────────┘ + │ + ┌────────┴────────┐ + │ PostgreSQL │ + │ (Replica) │ + └─────────────────┘ +``` + +--- + +## 环境要求 + +### 系统要求 + +| 组件 | 最低要求 | 推荐配置 | +|-----|---------|---------| +| CPU | 2 核 | 4+ 核 | +| 内存 | 2 GB | 4+ GB | +| 磁盘 | 20 GB SSD | 50+ GB SSD | +| Node.js | 20.x | 20.x LTS | +| PostgreSQL | 15.x | 15.x | + +### 依赖服务 + +- PostgreSQL 15.x 数据库 +- Docker (可选,用于容器化部署) +- Kubernetes (可选,用于编排) + +--- + +## 环境变量配置 + +### 必需变量 + +| 变量 | 描述 | 示例 | +|-----|------|-----| +| `DATABASE_URL` | PostgreSQL 连接字符串 | `postgresql://user:pass@host:5432/db` | +| `JWT_SECRET` | JWT 签名密钥 | `your-secret-key-here` | +| `NODE_ENV` | 环境标识 | `production` | +| `PORT` | 服务端口 | `3000` | + +### 可选变量 + +| 变量 | 描述 | 默认值 | +|-----|------|-------| +| `JWT_EXPIRES_IN` | JWT 过期时间 | `24h` | +| `LOG_LEVEL` | 日志级别 | `info` | +| `CORS_ORIGIN` | CORS 允许源 | `*` | + +### 环境配置文件 + +```bash +# .env.production +DATABASE_URL="postgresql://wallet:strong_password@db.example.com:5432/wallet_prod?schema=public&connection_limit=10" +JWT_SECRET="your-production-secret-key-min-32-chars" +NODE_ENV=production +PORT=3000 +LOG_LEVEL=info +``` + +--- + +## Docker 部署 + +### Dockerfile + +```dockerfile +# Dockerfile +# 构建阶段 +FROM node:20-alpine AS builder + +WORKDIR /app + +# 复制依赖文件 +COPY package*.json ./ +COPY prisma ./prisma/ + +# 安装依赖 +RUN npm ci --only=production=false + +# 复制源代码 +COPY . . + +# 生成 Prisma Client +RUN npx prisma generate + +# 构建应用 +RUN npm run build + +# 清理开发依赖 +RUN npm prune --production + +# 生产阶段 +FROM node:20-alpine AS production + +WORKDIR /app + +# 安装 dumb-init 用于信号处理 +RUN apk add --no-cache dumb-init + +# 创建非 root 用户 +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nestjs -u 1001 + +# 复制构建产物 +COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist +COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=nestjs:nodejs /app/prisma ./prisma +COPY --from=builder --chown=nestjs:nodejs /app/package.json ./ + +# 切换到非 root 用户 +USER nestjs + +# 暴露端口 +EXPOSE 3000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1 + +# 启动命令 +ENTRYPOINT ["dumb-init", "--"] +CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"] +``` + +### .dockerignore + +``` +# .dockerignore +node_modules +dist +coverage +.git +.gitignore +*.md +*.log +.env* +!.env.example +test +.vscode +.idea +``` + +### 构建和运行 + +```bash +# 构建镜像 +docker build -t wallet-service:latest . + +# 运行容器 +docker run -d \ + --name wallet-service \ + -p 3000:3000 \ + -e DATABASE_URL="postgresql://wallet:password@host:5432/wallet" \ + -e JWT_SECRET="your-secret-key" \ + -e NODE_ENV=production \ + wallet-service:latest + +# 查看日志 +docker logs -f wallet-service +``` + +--- + +## Docker Compose 部署 + +### docker-compose.yml + +```yaml +# docker-compose.yml +version: '3.8' + +services: + wallet-service: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + DATABASE_URL: postgresql://wallet:wallet123@postgres:5432/wallet?schema=public + JWT_SECRET: ${JWT_SECRET:-development-secret-change-in-production} + NODE_ENV: ${NODE_ENV:-production} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + postgres: + image: postgres:15-alpine + environment: + POSTGRES_USER: wallet + POSTGRES_PASSWORD: wallet123 + POSTGRES_DB: wallet + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U wallet -d wallet"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres_data: +``` + +### docker-compose.prod.yml + +```yaml +# docker-compose.prod.yml +version: '3.8' + +services: + wallet-service: + image: wallet-service:${VERSION:-latest} + ports: + - "3000:3000" + environment: + DATABASE_URL: ${DATABASE_URL} + JWT_SECRET: ${JWT_SECRET} + NODE_ENV: production + LOG_LEVEL: info + deploy: + replicas: 3 + update_config: + parallelism: 1 + delay: 10s + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +### 运行命令 + +```bash +# 开发环境 +docker-compose up -d + +# 生产环境 +docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d + +# 扩展副本 +docker-compose -f docker-compose.prod.yml up -d --scale wallet-service=3 +``` + +--- + +## Kubernetes 部署 + +### Deployment + +```yaml +# k8s/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wallet-service + labels: + app: wallet-service +spec: + replicas: 3 + selector: + matchLabels: + app: wallet-service + template: + metadata: + labels: + app: wallet-service + spec: + containers: + - name: wallet-service + image: wallet-service:latest + ports: + - containerPort: 3000 + env: + - name: NODE_ENV + value: "production" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: wallet-secrets + key: database-url + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: wallet-secrets + key: jwt-secret + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /api/v1/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/v1/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +### Service + +```yaml +# k8s/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: wallet-service +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 3000 + selector: + app: wallet-service +``` + +### Secret + +```yaml +# k8s/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: wallet-secrets +type: Opaque +stringData: + database-url: "postgresql://wallet:password@postgres:5432/wallet" + jwt-secret: "your-production-secret-key" +``` + +### Ingress + +```yaml +# k8s/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wallet-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: wallet.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: wallet-service + port: + number: 80 +``` + +### 部署命令 + +```bash +# 创建 Secret +kubectl apply -f k8s/secret.yaml + +# 部署服务 +kubectl apply -f k8s/deployment.yaml +kubectl apply -f k8s/service.yaml +kubectl apply -f k8s/ingress.yaml + +# 查看状态 +kubectl get pods -l app=wallet-service +kubectl get svc wallet-service + +# 滚动更新 +kubectl set image deployment/wallet-service wallet-service=wallet-service:v2.0.0 + +# 回滚 +kubectl rollout undo deployment/wallet-service +``` + +--- + +## 数据库迁移 + +### Prisma 迁移 + +```bash +# 开发环境 - 创建迁移 +npx prisma migrate dev --name add_new_field + +# 生产环境 - 应用迁移 +npx prisma migrate deploy + +# 查看迁移状态 +npx prisma migrate status +``` + +### 迁移脚本 + +```bash +#!/bin/bash +# scripts/migrate.sh + +set -e + +echo "Running database migrations..." + +# 等待数据库就绪 +until pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER; do + echo "Waiting for database..." + sleep 2 +done + +# 运行迁移 +npx prisma migrate deploy + +echo "Migrations completed successfully!" +``` + +--- + +## CI/CD 流水线 + +### GitHub Actions + +```yaml +# .github/workflows/deploy.yml +name: Deploy + +on: + push: + branches: [main] + release: + types: [published] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15-alpine + env: + POSTGRES_USER: wallet + POSTGRES_PASSWORD: wallet123 + POSTGRES_DB: wallet_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Generate Prisma Client + run: npx prisma generate + + - name: Run migrations + run: npx prisma db push + env: + DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test + + - name: Run tests + run: npm test + env: + DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test + JWT_SECRET: test-secret + + - name: Run E2E tests + run: npm run test:e2e + env: + DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test + JWT_SECRET: test-secret + + build: + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=sha + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + deploy: + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to production + run: | + # 触发部署 (例如 ArgoCD, Kubernetes, 或 SSH) + echo "Deploying to production..." +``` + +--- + +## 监控和日志 + +### 健康检查端点 + +```bash +# 健康检查 +curl http://localhost:3000/api/v1/health + +# 响应示例 +{ + "success": true, + "data": { + "status": "ok", + "service": "wallet-service", + "timestamp": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 日志配置 + +```typescript +// src/main.ts +import { Logger } from '@nestjs/common'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule, { + logger: process.env.NODE_ENV === 'production' + ? ['error', 'warn', 'log'] + : ['error', 'warn', 'log', 'debug', 'verbose'], + }); + // ... +} +``` + +### 日志格式 (生产环境) + +```json +{ + "timestamp": "2024-01-01T00:00:00.000Z", + "level": "info", + "context": "WalletController", + "message": "Deposit processed", + "userId": "12345", + "amount": 100, + "requestId": "abc-123" +} +``` + +--- + +## 安全配置 + +### Nginx 反向代理 + +```nginx +# nginx.conf +upstream wallet_service { + server wallet-service-1:3000; + server wallet-service-2:3000; + server wallet-service-3:3000; +} + +server { + listen 80; + server_name wallet.example.com; + + # 重定向到 HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name wallet.example.com; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Strict-Transport-Security "max-age=31536000" always; + + # 速率限制 + limit_req zone=api burst=20 nodelay; + + location /api/ { + proxy_pass http://wallet_service; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +### 环境变量安全 + +```bash +# 不要在版本控制中提交敏感信息 +# 使用环境变量或密钥管理服务 + +# AWS Secrets Manager +aws secretsmanager get-secret-value --secret-id wallet/production + +# HashiCorp Vault +vault kv get secret/wallet/production +``` + +--- + +## 故障排除 + +### 常见问题 + +#### 1. 数据库连接失败 + +```bash +# 检查连接 +psql $DATABASE_URL -c "SELECT 1" + +# 检查网络 +nc -zv db.example.com 5432 +``` + +#### 2. 容器无法启动 + +```bash +# 查看日志 +docker logs wallet-service + +# 进入容器调试 +docker exec -it wallet-service sh +``` + +#### 3. 迁移失败 + +```bash +# 查看迁移状态 +npx prisma migrate status + +# 重置数据库 (开发环境) +npx prisma migrate reset +``` + +### 回滚流程 + +```bash +# Docker 回滚 +docker stop wallet-service +docker run -d --name wallet-service wallet-service:previous-tag + +# Kubernetes 回滚 +kubectl rollout undo deployment/wallet-service + +# 数据库回滚 (手动) +# 需要提前备份,Prisma 不支持自动回滚 +pg_restore -d wallet wallet_backup.dump +``` + +--- + +## 部署检查清单 + +### 部署前 + +- [ ] 所有测试通过 +- [ ] 代码审查完成 +- [ ] 环境变量配置正确 +- [ ] 数据库备份完成 +- [ ] 迁移脚本测试通过 + +### 部署中 + +- [ ] 监控指标正常 +- [ ] 健康检查通过 +- [ ] 日志无错误 +- [ ] API 响应正常 + +### 部署后 + +- [ ] 功能验证通过 +- [ ] 性能测试通过 +- [ ] 安全扫描通过 +- [ ] 文档更新 + +--- + +## 联系方式 + +如遇部署问题,请联系: + +- 运维团队: devops@example.com +- 开发团队: dev@example.com +- 紧急联系: oncall@example.com + diff --git a/backend/services/wallet-service/docs/DEVELOPMENT.md b/backend/services/wallet-service/docs/DEVELOPMENT.md new file mode 100644 index 00000000..8e0f9336 --- /dev/null +++ b/backend/services/wallet-service/docs/DEVELOPMENT.md @@ -0,0 +1,478 @@ +# Wallet Service 开发指南 + +## 环境要求 + +- Node.js 20.x +- npm 10.x +- PostgreSQL 15.x +- Docker (用于本地数据库) +- WSL2 (Windows 开发者) + +## 快速开始 + +### 1. 克隆项目 + +```bash +git clone +cd wallet-service +``` + +### 2. 安装依赖 + +```bash +npm install +``` + +### 3. 配置环境变量 + +创建 `.env.development` 文件: + +```bash +# 数据库连接 +DATABASE_URL="postgresql://wallet:wallet123@localhost:5432/wallet_dev?schema=public" + +# JWT 配置 +JWT_SECRET="your-development-jwt-secret" + +# 应用配置 +NODE_ENV=development +PORT=3000 +``` + +### 4. 启动数据库 + +使用 Docker 启动 PostgreSQL: + +```bash +docker run -d \ + --name wallet-postgres-dev \ + -e POSTGRES_USER=wallet \ + -e POSTGRES_PASSWORD=wallet123 \ + -e POSTGRES_DB=wallet_dev \ + -p 5432:5432 \ + postgres:15-alpine +``` + +### 5. 初始化数据库 + +```bash +# 生成 Prisma Client +npx prisma generate + +# 推送数据库结构 +npx prisma db push + +# (可选) 打开 Prisma Studio 查看数据 +npx prisma studio +``` + +### 6. 启动开发服务器 + +```bash +npm run start:dev +``` + +服务将在 `http://localhost:3000` 启动。 + +Swagger 文档: `http://localhost:3000/api-docs` + +--- + +## 项目脚本 + +| 命令 | 描述 | +|-----|------| +| `npm run start` | 启动生产模式 | +| `npm run start:dev` | 启动开发模式 (热重载) | +| `npm run start:debug` | 启动调试模式 | +| `npm run build` | 构建项目 | +| `npm test` | 运行单元测试 | +| `npm run test:watch` | 监听模式运行测试 | +| `npm run test:cov` | 运行测试并生成覆盖率报告 | +| `npm run test:e2e` | 运行 E2E 测试 | +| `npm run lint` | 代码检查 | +| `npm run format` | 代码格式化 | +| `npm run prisma:generate` | 生成 Prisma Client | +| `npm run prisma:migrate` | 运行数据库迁移 | +| `npm run prisma:studio` | 启动 Prisma Studio | + +--- + +## 代码结构 + +### 添加新功能的标准流程 + +#### 1. 定义值对象 (如需要) + +```typescript +// src/domain/value-objects/new-value.vo.ts +export class NewValue { + private readonly _value: number; + + private constructor(value: number) { + this._value = value; + } + + static create(value: number): NewValue { + // 验证逻辑 + if (value < 0) { + throw new DomainError('Value cannot be negative'); + } + return new NewValue(value); + } + + get value(): number { + return this._value; + } +} +``` + +#### 2. 定义领域事件 (如需要) + +```typescript +// src/domain/events/new-action.event.ts +export class NewActionEvent extends DomainEvent { + constructor(public readonly payload: { + userId: string; + amount: string; + }) { + super('NewActionEvent'); + } +} +``` + +#### 3. 在聚合中添加业务方法 + +```typescript +// src/domain/aggregates/wallet-account.aggregate.ts +newAction(amount: Money): void { + this.ensureActive(); + // 业务逻辑 + this._updatedAt = new Date(); + this.addDomainEvent(new NewActionEvent({ + userId: this._userId.toString(), + amount: amount.value.toString(), + })); +} +``` + +#### 4. 定义命令/查询 + +```typescript +// src/application/commands/new-action.command.ts +export class NewActionCommand { + constructor( + public readonly userId: string, + public readonly amount: number, + ) {} +} +``` + +#### 5. 在应用服务中实现 + +```typescript +// src/application/services/wallet-application.service.ts +async newAction(command: NewActionCommand): Promise { + const wallet = await this.walletRepo.findByUserId(BigInt(command.userId)); + if (!wallet) { + throw new WalletNotFoundError(`userId: ${command.userId}`); + } + wallet.newAction(Money.USDT(command.amount)); + await this.walletRepo.save(wallet); + // 记录流水等... +} +``` + +#### 6. 添加 DTO + +```typescript +// src/api/dto/request/new-action.dto.ts +export class NewActionDTO { + @ApiProperty({ description: '金额' }) + @IsNumber() + @Min(0) + amount: number; +} +``` + +#### 7. 添加控制器端点 + +```typescript +// src/api/controllers/wallet.controller.ts +@Post('new-action') +@ApiOperation({ summary: '新操作' }) +async newAction( + @CurrentUser() user: CurrentUserPayload, + @Body() dto: NewActionDTO, +): Promise<{ message: string }> { + await this.walletService.newAction( + new NewActionCommand(user.userId, dto.amount) + ); + return { message: 'Success' }; +} +``` + +--- + +## 值对象规范 + +### Money (金额) + +```typescript +// 创建 +const usdt = Money.USDT(100); // 100 USDT +const bnb = Money.BNB(0.5); // 0.5 BNB +const custom = Money.create(50, 'DST'); // 50 DST + +// 运算 +const sum = usdt.add(Money.USDT(50)); // 150 USDT +const diff = usdt.subtract(Money.USDT(30)); // 70 USDT + +// 比较 +usdt.equals(Money.USDT(100)); // true +usdt.lessThan(Money.USDT(200)); // true +usdt.isZero(); // false + +// 获取值 +usdt.value; // 100 (number) +usdt.currency; // 'USDT' +``` + +### Balance (余额) + +```typescript +// 创建 +const balance = Balance.create( + Money.USDT(1000), // available + Money.USDT(100) // frozen +); + +// 操作 +const afterDeposit = balance.add(Money.USDT(200)); // available + 200 +const afterDeduct = balance.deduct(Money.USDT(50)); // available - 50 +const afterFreeze = balance.freeze(Money.USDT(100)); // available -> frozen +const afterUnfreeze = balance.unfreeze(Money.USDT(50)); // frozen -> available +``` + +### Hashpower (算力) + +```typescript +const hp = Hashpower.create(500); +const sum = hp.add(Hashpower.create(100)); // 600 +const value = hp.value; // 500 +``` + +--- + +## 仓储模式 + +### 接口定义 + +```typescript +// src/domain/repositories/wallet-account.repository.interface.ts +export interface IWalletAccountRepository { + findByUserId(userId: bigint): Promise; + getOrCreate(userId: bigint): Promise; + save(wallet: WalletAccount): Promise; +} +``` + +### 实现 + +```typescript +// src/infrastructure/persistence/repositories/wallet-account.repository.impl.ts +@Injectable() +export class WalletAccountRepositoryImpl implements IWalletAccountRepository { + constructor(private readonly prisma: PrismaService) {} + + async findByUserId(userId: bigint): Promise { + const record = await this.prisma.walletAccount.findUnique({ + where: { userId }, + }); + if (!record) return null; + return WalletAccount.reconstruct(/* ... */); + } +} +``` + +### 依赖注入 + +```typescript +// src/infrastructure/infrastructure.module.ts +@Module({ + providers: [ + { + provide: WALLET_ACCOUNT_REPOSITORY, + useClass: WalletAccountRepositoryImpl, + }, + ], + exports: [WALLET_ACCOUNT_REPOSITORY], +}) +export class InfrastructureModule {} +``` + +--- + +## 异常处理 + +### 领域异常 + +```typescript +// src/shared/exceptions/domain.exception.ts +export class DomainError extends Error { + constructor(message: string) { + super(message); + this.name = 'DomainError'; + } +} + +export class InsufficientBalanceError extends DomainError { + public readonly code = 'INSUFFICIENT_BALANCE'; + constructor(assetType: string, required: string, available: string) { + super(`Insufficient ${assetType} balance: required ${required}, available ${available}`); + } +} +``` + +### 异常过滤器 + +```typescript +// src/shared/filters/domain-exception.filter.ts +@Catch(DomainError) +export class DomainExceptionFilter implements ExceptionFilter { + catch(exception: DomainError, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const status = this.getHttpStatus(exception); + + response.status(status).json({ + success: false, + code: (exception as any).code || 'DOMAIN_ERROR', + message: exception.message, + timestamp: new Date().toISOString(), + }); + } +} +``` + +--- + +## 调试技巧 + +### VS Code 调试配置 + +`.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug NestJS", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "start:debug"], + "console": "integratedTerminal", + "restart": true + } + ] +} +``` + +### 日志调试 + +```typescript +// 在代码中添加 +console.log('DEBUG:', JSON.stringify(data, null, 2)); + +// 使用 NestJS Logger +import { Logger } from '@nestjs/common'; +const logger = new Logger('WalletService'); +logger.log('Processing deposit...'); +logger.debug('Wallet state:', wallet); +``` + +### 数据库调试 + +```bash +# 查看数据库 +npx prisma studio + +# 查看 SQL 日志 (在 prisma.service.ts 中) +this.$on('query', (e) => { + console.log('Query:', e.query); + console.log('Params:', e.params); +}); +``` + +--- + +## Git 工作流 + +### 分支命名 + +- `feature/xxx` - 新功能 +- `fix/xxx` - Bug 修复 +- `refactor/xxx` - 重构 +- `docs/xxx` - 文档 + +### 提交信息格式 + +``` +(): + + + +