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

494 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 开发指南
## 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
```