rwadurian/backend/services/reporting-service/docs/DEVELOPMENT.md

10 KiB
Raw Permalink Blame History

开发指南

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 克隆项目

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

2.2 安装依赖

npm install

2.3 环境配置

创建 .env.development 文件:

cp .env.example .env.development

编辑 .env.development:

# 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):

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:

npx prisma generate

同步数据库 Schema:

npx prisma db push

(可选) 填充种子数据:

npm run prisma:seed

2.5 启动 Redis

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

3. 开发工作流

3.1 启动开发服务器

# 热重载模式
npm run start:dev

# 调试模式
npm run start:debug

服务启动后访问:

3.2 常用命令

# 构建项目
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

# 查看所有可用命令
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):

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

ESLint 规则 (关键规则):

{
  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 领域驱动设计规范

聚合根规则:

// 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) { ... }
}

值对象规则:

// 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:

# 格式
<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 描述需包含:

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

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

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

6. 调试技巧

6.1 VS Code 调试配置

.vscode/launch.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 日志调试

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 调试

启用查询日志:

// 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 未生成

npx prisma generate

Q: 数据库连接失败

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

Q: 测试失败 - 表不存在

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

Q: TypeScript 编译错误

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

Q: 端口被占用

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