10 KiB
10 KiB
开发指南
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
服务启动后访问:
- API: http://localhost:3000/api/v1
- Swagger: http://localhost:3000/api (如已启用)
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: 数据库连接失败
- 检查 PostgreSQL 是否运行:
docker ps - 检查 DATABASE_URL 环境变量
- 检查网络连接和端口
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