# 开发指南

## 1. 环境要求

### 1.1 必需软件

| 软件 | 版本 | 用途 |
|------|------|------|
| Node.js | 20.x LTS | 运行时环境 |
| npm | 10.x | 包管理器 |
| Docker | 24.x+ | 容器化 |
| Docker Compose | 2.x | 容器编排 |
| PostgreSQL | 15.x | 数据库 |
| Redis | 7.x | 缓存 |

### 1.2 推荐 IDE

- **VS Code** (推荐)
  - 插件: ESLint, Prettier, Prisma, TypeScript
- **WebStorm**
- **Cursor**

## 2. 项目设置

### 2.1 克隆项目

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

### 2.2 安装依赖

```bash
npm install
```

### 2.3 环境配置

创建 `.env.development` 文件:

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

编辑 `.env.development`:

```env
# Application
NODE_ENV=development
PORT=3000

# Database
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/rwadurian_reporting?schema=public

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379

# JWT
JWT_SECRET=your-development-secret-key

# External Services (可选)
LEADERBOARD_SERVICE_URL=http://localhost:3001
PLANTING_SERVICE_URL=http://localhost:3002
```

### 2.4 数据库设置

启动 PostgreSQL (使用 Docker):

```bash
docker run -d \
  --name reporting-postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=rwadurian_reporting \
  -p 5432:5432 \
  postgres:15-alpine
```

生成 Prisma Client:

```bash
npx prisma generate
```

同步数据库 Schema:

```bash
npx prisma db push
```

(可选) 填充种子数据:

```bash
npm run prisma:seed
```

### 2.5 启动 Redis

```bash
docker run -d \
  --name reporting-redis \
  -p 6379:6379 \
  redis:7-alpine
```

## 3. 开发工作流

### 3.1 启动开发服务器

```bash
# 热重载模式
npm run start:dev

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

服务启动后访问:
- API: http://localhost:3000/api/v1
- Swagger: http://localhost:3000/api (如已启用)

### 3.2 常用命令

```bash
# 构建项目
npm run build

# 运行 Lint
npm run lint

# 格式化代码
npm run format

# 运行测试
npm test

# 运行测试 (watch 模式)
npm run test:watch

# 生成测试覆盖率
npm run test:cov

# Prisma Studio (数据库可视化)
npm run prisma:studio
```

### 3.3 使用 Makefile

```bash
# 查看所有可用命令
make help

# 安装依赖
make install

# 构建项目
make build

# 运行所有测试
make test

# 运行单元测试
make test-unit

# 运行集成测试
make test-integration

# 运行 E2E 测试
make test-e2e

# Docker 测试
make test-docker-all
```

## 4. 代码规范

### 4.1 目录结构规范

```
src/
├── api/                    # API层
│   ├── controllers/        # 每个资源一个控制器文件
│   └── dto/
│       ├── request/        # 请求 DTO
│       └── response/       # 响应 DTO
├── application/            # 应用层
│   ├── commands/           # 每个命令独立目录
│   │   └── xxx/
│   │       ├── xxx.command.ts
│   │       └── xxx.handler.ts
│   └── queries/            # 每个查询独立目录
├── domain/                 # 领域层
│   ├── aggregates/         # 每个聚合独立目录
│   │   └── xxx/
│   │       ├── index.ts
│   │       ├── xxx.aggregate.ts
│   │       └── xxx.spec.ts   # 单元测试
│   ├── value-objects/      # 值对象
│   │   ├── xxx.vo.ts
│   │   └── xxx.spec.ts
│   └── repositories/       # 仅接口定义
└── infrastructure/         # 基础设施层
```

### 4.2 命名规范

**文件命名**:
- 使用 kebab-case: `report-definition.aggregate.ts`
- 后缀约定:
  - `.aggregate.ts` - 聚合根
  - `.entity.ts` - 实体
  - `.vo.ts` - 值对象
  - `.service.ts` - 服务
  - `.controller.ts` - 控制器
  - `.dto.ts` - DTO
  - `.spec.ts` - 单元测试
  - `.e2e-spec.ts` - E2E 测试
  - `.integration.spec.ts` - 集成测试

**类命名**:
- 使用 PascalCase
- 聚合根: `ReportSnapshot`
- 值对象: `DateRange`
- 服务: `ReportingApplicationService`
- 控制器: `ReportController`

**接口命名**:
- 以 `I` 开头: `IReportSnapshotRepository`

### 4.3 代码风格

**TypeScript 配置** (`tsconfig.json`):

```json
{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}
```

**ESLint 规则** (关键规则):

```javascript
{
  rules: {
    '@typescript-eslint/explicit-function-return-type': 'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unused-vars': 'error',
    'no-console': 'warn'
  }
}
```

### 4.4 领域驱动设计规范

**聚合根规则**:

```typescript
// 1. 使用私有属性 + getter
export class ReportSnapshot {
  private readonly _id: bigint;
  private _snapshotData: SnapshotData;

  get id(): bigint { return this._id; }
  get snapshotData(): SnapshotData { return this._snapshotData; }

  // 2. 使用工厂方法创建
  public static create(props: CreateProps): ReportSnapshot {
    const snapshot = new ReportSnapshot(props);
    snapshot.addDomainEvent(new SnapshotCreatedEvent(snapshot));
    return snapshot;
  }

  // 3. 使用 reconstitute 重建 (不触发事件)
  public static reconstitute(props: ReconstitutionProps): ReportSnapshot {
    return new ReportSnapshot(props);
  }

  // 4. 业务方法封装状态变更
  public updateData(newData: SnapshotData): void {
    this.validateDataUpdate(newData);
    this._snapshotData = newData;
    this._updatedAt = new Date();
  }

  // 5. 私有构造函数
  private constructor(props: Props) { ... }
}
```

**值对象规则**:

```typescript
// 1. 不可变
export class DateRange {
  private readonly _startDate: Date;
  private readonly _endDate: Date;

  private constructor(startDate: Date, endDate: Date) {
    this._startDate = startDate;
    this._endDate = endDate;
    Object.freeze(this); // 冻结对象
  }

  // 2. 工厂方法 + 验证
  public static create(startDate: Date, endDate: Date): DateRange {
    if (startDate > endDate) {
      throw new DomainException('Invalid date range');
    }
    return new DateRange(startDate, endDate);
  }

  // 3. 实现 equals 方法
  public equals(other: DateRange): boolean {
    return this._startDate.getTime() === other._startDate.getTime() &&
           this._endDate.getTime() === other._endDate.getTime();
  }
}
```

## 5. Git 工作流

### 5.1 分支策略

```
main              # 生产分支
├── develop       # 开发分支
│   ├── feature/xxx    # 功能分支
│   ├── bugfix/xxx     # 修复分支
│   └── refactor/xxx   # 重构分支
└── release/x.x.x      # 发布分支
```

### 5.2 提交规范

使用 Conventional Commits:

```bash
# 格式
<type>(<scope>): <subject>

# 示例
feat(report): add daily report generation
fix(export): resolve Excel formatting issue
refactor(domain): extract value object for date range
test(e2e): add report generation tests
docs(api): update API documentation
```

**Type 类型**:
- `feat`: 新功能
- `fix`: Bug 修复
- `refactor`: 代码重构
- `test`: 测试相关
- `docs`: 文档更新
- `chore`: 构建/配置变更
- `perf`: 性能优化

### 5.3 PR 规范

PR 标题遵循提交规范，PR 描述需包含:

```markdown
## Summary
简要说明本次变更内容

## Changes
- 变更点 1
- 变更点 2

## Test Plan
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试场景
```

## 6. 调试技巧

### 6.1 VS Code 调试配置

`.vscode/launch.json`:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug NestJS",
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
      "args": ["${workspaceFolder}/src/main.ts"],
      "sourceMaps": true,
      "envFile": "${workspaceFolder}/.env.development"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand", "--watchAll=false"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}
```

### 6.2 日志调试

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

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

  async generateReport(dto: GenerateReportDto) {
    this.logger.debug(`Generating report: ${dto.reportCode}`);
    this.logger.log(`Report generated successfully`);
    this.logger.warn(`Performance warning: slow query`);
    this.logger.error(`Failed to generate report`, error.stack);
  }
}
```

### 6.3 Prisma 调试

启用查询日志:

```typescript
// prisma.service.ts
const prisma = new PrismaClient({
  log: [
    { level: 'query', emit: 'event' },
    { level: 'error', emit: 'stdout' },
    { level: 'warn', emit: 'stdout' },
  ],
});

prisma.$on('query', (e) => {
  console.log('Query: ' + e.query);
  console.log('Params: ' + e.params);
  console.log('Duration: ' + e.duration + 'ms');
});
```

## 7. 常见问题

### Q: Prisma Client 未生成

```bash
npx prisma generate
```

### Q: 数据库连接失败

1. 检查 PostgreSQL 是否运行: `docker ps`
2. 检查 DATABASE_URL 环境变量
3. 检查网络连接和端口

### Q: 测试失败 - 表不存在

```bash
# 推送 schema 到测试数据库
DATABASE_URL="postgresql://..." npx prisma db push
```

### Q: TypeScript 编译错误

```bash
# 清理并重新构建
rm -rf dist
npm run build
```

### Q: 端口被占用

```bash
# 查找占用端口的进程
lsof -i :3000
# 或在 Windows
netstat -ano | findstr :3000
```
