# 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` - 文档 ### 提交信息格式 ``` ():