# Reward Service 开发指南

## 环境准备

### 系统要求

- **Node.js**: >= 20.x LTS
- **npm**: >= 10.x
- **Docker**: >= 24.x (用于本地开发环境)
- **Git**: >= 2.x

### 开发环境设置

#### 1. 克隆项目

```bash
git clone <repository-url>
cd backend/services/reward-service
```

#### 2. 安装依赖

```bash
npm install
```

#### 3. 配置环境变量

创建 `.env` 文件：

```bash
cp .env.example .env
```

编辑 `.env` 文件：

```env
# 应用配置
NODE_ENV=development
PORT=3000

# 数据库配置
DATABASE_URL="postgresql://postgres:password@localhost:5432/reward_db"

# Redis配置
REDIS_HOST=localhost
REDIS_PORT=6379

# Kafka配置
KAFKA_BROKERS=localhost:9092
KAFKA_CLIENT_ID=reward-service
KAFKA_GROUP_ID=reward-service-group

# JWT配置
JWT_SECRET=your-jwt-secret-key

# 外部服务配置
REFERRAL_SERVICE_URL=http://localhost:3001
AUTHORIZATION_SERVICE_URL=http://localhost:3002
WALLET_SERVICE_URL=http://localhost:3003
```

#### 4. 启动基础设施

使用 Docker Compose 启动 PostgreSQL、Redis 和 Kafka：

```bash
docker compose -f docker-compose.test.yml up -d
```

#### 5. 数据库迁移

```bash
# 生成 Prisma Client
npx prisma generate

# 运行数据库迁移
npx prisma migrate dev
```

#### 6. 启动开发服务器

```bash
# 开发模式 (热重载)
npm run start:dev

# 调试模式
npm run start:debug
```

---

## 项目结构

```
reward-service/
├── src/
│   ├── api/                    # API层
│   │   ├── controllers/        # 控制器
│   │   ├── dto/               # 数据传输对象
│   │   │   ├── request/       # 请求DTO
│   │   │   └── response/      # 响应DTO
│   │   └── api.module.ts
│   │
│   ├── application/            # 应用层
│   │   ├── services/          # 应用服务
│   │   ├── schedulers/        # 定时任务
│   │   └── application.module.ts
│   │
│   ├── domain/                 # 领域层 (核心)
│   │   ├── aggregates/        # 聚合根
│   │   ├── value-objects/     # 值对象
│   │   ├── events/            # 领域事件
│   │   ├── services/          # 领域服务
│   │   ├── repositories/      # 仓储接口
│   │   └── domain.module.ts
│   │
│   ├── infrastructure/         # 基础设施层
│   │   ├── persistence/       # 持久化
│   │   │   ├── prisma/        # Prisma配置
│   │   │   ├── repositories/  # 仓储实现
│   │   │   └── mappers/       # 对象映射
│   │   ├── external/          # 外部服务客户端
│   │   ├── kafka/             # Kafka集成
│   │   ├── redis/             # Redis集成
│   │   └── infrastructure.module.ts
│   │
│   ├── shared/                 # 共享模块
│   │   ├── guards/            # 守卫
│   │   └── strategies/        # 认证策略
│   │
│   ├── config/                 # 配置
│   ├── app.module.ts          # 根模块
│   └── main.ts                # 入口文件
│
├── test/                       # 测试
│   ├── integration/           # 集成测试
│   └── app.e2e-spec.ts        # E2E测试
│
├── prisma/                     # Prisma配置
│   └── schema.prisma
│
├── docs/                       # 文档
├── Makefile                    # Make命令
└── docker-compose.test.yml    # Docker配置
```

---

## 开发规范

### 代码风格

项目使用 ESLint 和 Prettier 进行代码规范检查：

```bash
# 运行 ESLint 检查并自动修复
npm run lint

# 运行 Prettier 格式化
npm run format
```

### 命名规范

| 类型 | 规范 | 示例 |
|------|------|------|
| 文件名 | kebab-case | `reward-ledger-entry.aggregate.ts` |
| 类名 | PascalCase | `RewardLedgerEntry` |
| 接口名 | I + PascalCase | `IRewardLedgerEntryRepository` |
| 方法名 | camelCase | `calculateRewards` |
| 常量 | UPPER_SNAKE_CASE | `HEADQUARTERS_COMMUNITY_USER_ID` |
| 枚举值 | UPPER_SNAKE_CASE | `SHARE_RIGHT` |

### DDD 分层规范

#### 领域层 (Domain)

领域层是系统核心，**不依赖任何其他层**。

```typescript
// ✅ 正确：领域层只使用领域概念
import { Money } from '../value-objects/money.vo';
import { RewardStatus } from '../value-objects/reward-status.enum';

// ❌ 错误：领域层不应依赖基础设施
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
```

#### 应用层 (Application)

应用层协调领域层和基础设施层，实现用例。

```typescript
@Injectable()
export class RewardApplicationService {
  constructor(
    // 注入领域服务
    private readonly rewardCalculationService: RewardCalculationService,
    // 通过接口注入仓储
    @Inject(REWARD_LEDGER_ENTRY_REPOSITORY)
    private readonly repository: IRewardLedgerEntryRepository,
  ) {}
}
```

#### 基础设施层 (Infrastructure)

基础设施层实现领域层定义的接口。

```typescript
// 仓储实现
@Injectable()
export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(entry: RewardLedgerEntry): Promise<void> {
    // 实现持久化逻辑
  }
}
```

---

## 添加新功能

### 1. 添加新的值对象

```typescript
// src/domain/value-objects/new-value.vo.ts
export class NewValue {
  private readonly _value: number;

  private constructor(value: number) {
    if (value < 0) {
      throw new Error('Value must be non-negative');
    }
    this._value = value;
  }

  static create(value: number): NewValue {
    return new NewValue(value);
  }

  get value(): number {
    return this._value;
  }

  equals(other: NewValue): boolean {
    return this._value === other._value;
  }
}
```

### 2. 添加新的领域事件

```typescript
// src/domain/events/new.event.ts
import { DomainEvent } from './domain-event.base';

export class NewEvent extends DomainEvent {
  constructor(
    public readonly data: {
      id: string;
      userId: string;
      // ... 其他字段
    },
  ) {
    super('NewEvent');
  }
}
```

### 3. 添加新的聚合根方法

```typescript
// 在聚合根中添加新行为
export class RewardLedgerEntry {
  // ... 现有代码

  /**
   * 新的领域行为
   */
  newBehavior(): void {
    // 1. 检查不变式
    if (!this.canPerformNewBehavior()) {
      throw new Error('Cannot perform behavior in current state');
    }

    // 2. 修改状态
    this._someField = newValue;

    // 3. 发布领域事件
    this._domainEvents.push(new NewEvent({
      id: this._id?.toString() || '',
      userId: this._userId.toString(),
    }));
  }

  private canPerformNewBehavior(): boolean {
    // 业务规则检查
    return true;
  }
}
```

### 4. 添加新的 API 端点

```typescript
// src/api/controllers/new.controller.ts
@ApiTags('New')
@Controller('new')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class NewController {
  constructor(private readonly service: RewardApplicationService) {}

  @Get()
  @ApiOperation({ summary: '新接口描述' })
  @ApiResponse({ status: 200, description: '成功' })
  async newEndpoint(@Request() req) {
    const userId = BigInt(req.user.sub);
    return this.service.newMethod(userId);
  }
}
```

---

## 依赖注入

### 定义仓储接口

```typescript
// src/domain/repositories/new.repository.interface.ts
export interface INewRepository {
  findById(id: bigint): Promise<Entity | null>;
  save(entity: Entity): Promise<void>;
}

export const NEW_REPOSITORY = Symbol('INewRepository');
```

### 实现仓储

```typescript
// src/infrastructure/persistence/repositories/new.repository.impl.ts
@Injectable()
export class NewRepositoryImpl implements INewRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: bigint): Promise<Entity | null> {
    const data = await this.prisma.entity.findUnique({ where: { id } });
    return data ? EntityMapper.toDomain(data) : null;
  }

  async save(entity: Entity): Promise<void> {
    const data = EntityMapper.toPersistence(entity);
    await this.prisma.entity.upsert({
      where: { id: data.id },
      create: data,
      update: data,
    });
  }
}
```

### 注册依赖

```typescript
// src/infrastructure/infrastructure.module.ts
@Module({
  providers: [
    {
      provide: NEW_REPOSITORY,
      useClass: NewRepositoryImpl,
    },
  ],
  exports: [NEW_REPOSITORY],
})
export class InfrastructureModule {}
```

---

## 常用命令

### 开发命令

```bash
# 启动开发服务器 (热重载)
npm run start:dev

# 启动调试模式
npm run start:debug

# 构建生产版本
npm run build

# 启动生产服务器
npm run start:prod
```

### 数据库命令

```bash
# 生成 Prisma Client
npx prisma generate

# 创建新迁移
npx prisma migrate dev --name <migration-name>

# 应用迁移
npx prisma migrate deploy

# 重置数据库
npx prisma migrate reset --force

# 打开 Prisma Studio
npx prisma studio
```

### 代码质量

```bash
# ESLint 检查
npm run lint

# 代码格式化
npm run format
```

### 测试命令

```bash
# 运行所有测试
npm test

# 运行单元测试
make test-unit

# 运行集成测试
make test-integration

# 运行 E2E 测试
make test-e2e

# 测试覆盖率
npm run test:cov
```

---

## 调试技巧

### VS Code 调试配置

创建 `.vscode/launch.json`：

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach NestJS",
      "port": 9229,
      "restart": true,
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--watchAll=false"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}
```

### 日志调试

```typescript
import { Logger } from '@nestjs/common';

@Injectable()
export class SomeService {
  private readonly logger = new Logger(SomeService.name);

  async someMethod() {
    this.logger.log('Processing started');
    this.logger.debug('Debug information', { data });
    this.logger.warn('Warning message');
    this.logger.error('Error occurred', error.stack);
  }
}
```

---

## 常见问题

### Q: 如何处理 BigInt 序列化问题?

```typescript
// 在 JSON 序列化时转换为字符串
return {
  id: entity.id?.toString(),
  userId: entity.userId.toString(),
};
```

### Q: 如何添加新的外部服务依赖?

1. 在 `src/domain/services/` 中定义接口 (防腐层)
2. 在 `src/infrastructure/external/` 中实现客户端
3. 在模块中注册依赖注入

### Q: 如何处理数据库事务?

```typescript
await this.prisma.$transaction(async (tx) => {
  await tx.rewardLedgerEntry.create({ data: entry1 });
  await tx.rewardLedgerEntry.create({ data: entry2 });
  await tx.rewardSummary.update({ where: { userId }, data: summary });
});
```

### Q: 如何测试私有方法?

不要直接测试私有方法。通过公共接口测试私有方法的行为：

```typescript
// ❌ 错误：直接测试私有方法
expect(service['privateMethod']()).toBe(expected);

// ✅ 正确：通过公共接口测试
const result = await service.publicMethod();
expect(result).toMatchObject({ /* expected behavior */ });
```
