# Wallet Service 开发指南

## 环境要求

- Node.js 20.x
- npm 10.x
- PostgreSQL 15.x
- Docker (用于本地数据库)
- WSL2 (Windows 开发者)

## 快速开始

### 1. 克隆项目

```bash
git clone <repository_url>
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<void> {
  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<WalletAccount | null>;
  getOrCreate(userId: bigint): Promise<WalletAccount>;
  save(wallet: WalletAccount): Promise<WalletAccount>;
}
```

### 实现

```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<WalletAccount | null> {
    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<Response>();
    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` - 文档

### 提交信息格式

```
<type>(<scope>): <subject>

<body>

<footer>
```

类型:
- `feat` - 新功能
- `fix` - Bug 修复
- `docs` - 文档
- `style` - 格式
- `refactor` - 重构
- `test` - 测试
- `chore` - 构建/工具

示例:
```
feat(wallet): add settle rewards feature

- Add SettleRewardsCommand
- Implement settlement logic in WalletAccount
- Add POST /settle endpoint

Closes #123
```

---

## 常见问题

### 1. Prisma Client 未生成

```bash
npx prisma generate
```

### 2. 数据库连接失败

检查 `.env` 文件中的 `DATABASE_URL` 是否正确。

### 3. WSL2 性能问题

参考 [E2E-TESTING-WSL2.md](./E2E-TESTING-WSL2.md) 将项目放在 WSL2 原生文件系统中。

### 4. 端口被占用

```bash
# Windows
netstat -ano | findstr :3000
taskkill /PID <pid> /F

# Linux/Mac
lsof -i :3000
kill -9 <pid>
```
