# Planting Service 开发指南 ## 目录 - [环境要求](#环境要求) - [项目设置](#项目设置) - [开发流程](#开发流程) - [代码规范](#代码规范) - [领域开发指南](#领域开发指南) - [常用命令](#常用命令) - [调试技巧](#调试技巧) - [常见问题](#常见问题) --- ## 环境要求 ### 必需工具 | 工具 | 版本 | 用途 | |-----|------|------| | Node.js | ≥ 20.x | 运行时 | | npm | ≥ 10.x | 包管理 | | PostgreSQL | ≥ 16.x | 数据库 | | Docker | ≥ 24.x | 容器化 (可选) | | Git | ≥ 2.x | 版本控制 | ### 推荐 IDE - **VS Code** + 推荐插件: - ESLint - Prettier - Prisma - GitLens - REST Client --- ## 项目设置 ### 1. 克隆项目 ```bash git clone cd backend/services/planting-service ``` ### 2. 安装依赖 ```bash npm install # 或使用 make make install ``` ### 3. 环境配置 复制环境配置文件: ```bash cp .env.example .env ``` 编辑 `.env` 文件: ```env # 数据库 DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_planting?schema=public" # JWT JWT_SECRET="your-secret-key" # 服务端口 PORT=3003 # 外部服务 WALLET_SERVICE_URL=http://localhost:3002 IDENTITY_SERVICE_URL=http://localhost:3001 REFERRAL_SERVICE_URL=http://localhost:3004 ``` ### 4. 数据库设置 ```bash # 生成 Prisma Client npx prisma generate # 运行数据库迁移 npx prisma migrate dev # (可选) 打开 Prisma Studio npx prisma studio ``` ### 5. 启动开发服务器 ```bash npm run start:dev # 或使用 make make dev ``` 服务将在 `http://localhost:3003` 启动。 --- ## 开发流程 ### Git 分支策略 ``` main # 生产分支 ├── develop # 开发分支 ├── feature/* # 功能分支 ├── bugfix/* # 修复分支 └── hotfix/* # 紧急修复分支 ``` ### 开发流程 1. **创建功能分支** ```bash git checkout develop git pull origin develop git checkout -b feature/new-feature ``` 2. **开发功能** - 编写代码 - 编写测试 - 运行测试确保通过 3. **提交代码** ```bash git add . git commit -m "feat: add new feature" ``` 4. **推送并创建 PR** ```bash git push origin feature/new-feature # 在 GitHub 创建 Pull Request ``` ### 提交规范 使用 [Conventional Commits](https://www.conventionalcommits.org/): ``` (): [optional body] [optional footer] ``` **类型 (type)**: - `feat`: 新功能 - `fix`: Bug 修复 - `docs`: 文档更新 - `style`: 代码格式 - `refactor`: 重构 - `test`: 测试 - `chore`: 构建/工具 **示例**: ```bash feat(order): add province-city selection fix(payment): fix balance check logic docs(api): update API documentation test(order): add integration tests ``` --- ## 代码规范 ### TypeScript 规范 ```typescript // 1. 使用明确的类型声明 function createOrder(userId: bigint, treeCount: number): PlantingOrder { // ... } // 2. 使用 readonly 保护不可变数据 class TreeCount { constructor(private readonly _value: number) {} } // 3. 使用接口定义契约 interface IPlantingOrderRepository { save(order: PlantingOrder): Promise; findById(id: bigint): Promise; } // 4. 使用枚举定义常量 enum PlantingOrderStatus { CREATED = 'CREATED', PAID = 'PAID', } ``` ### 命名规范 | 类型 | 规范 | 示例 | |-----|------|------| | 类名 | PascalCase | `PlantingOrder` | | 接口 | I + PascalCase | `IPlantingOrderRepository` | | 方法 | camelCase | `createOrder` | | 常量 | UPPER_SNAKE_CASE | `MAX_TREE_COUNT` | | 文件 | kebab-case | `planting-order.aggregate.ts` | | 目录 | kebab-case | `value-objects` | ### 文件命名约定 ``` *.aggregate.ts # 聚合根 *.entity.ts # 实体 *.vo.ts # 值对象 *.service.ts # 服务 *.repository.*.ts # 仓储 *.controller.ts # 控制器 *.dto.ts # 数据传输对象 *.spec.ts # 单元测试 *.integration.spec.ts # 集成测试 *.e2e-spec.ts # E2E 测试 *.mapper.ts # 映射器 *.guard.ts # 守卫 *.filter.ts # 过滤器 ``` ### ESLint + Prettier 项目已配置 ESLint 和 Prettier: ```bash # 运行 lint npm run lint # 格式化代码 npm run format ``` --- ## 领域开发指南 ### 创建新的聚合根 ```typescript // src/domain/aggregates/new-aggregate.aggregate.ts export interface NewAggregateData { id?: bigint; name: string; // ... 其他字段 } export class NewAggregate { private _id?: bigint; private _name: string; private _domainEvents: DomainEvent[] = []; private constructor(data: NewAggregateData) { this._id = data.id; this._name = data.name; } // 工厂方法 - 创建新实例 static create(name: string): NewAggregate { const aggregate = new NewAggregate({ name }); aggregate.addDomainEvent(new NewAggregateCreatedEvent(aggregate)); return aggregate; } // 工厂方法 - 从持久化重建 static reconstitute(data: NewAggregateData): NewAggregate { return new NewAggregate(data); } // 业务方法 doSomething(): void { // 业务逻辑 this.addDomainEvent(new SomethingHappenedEvent(this)); } // Getters get id(): bigint | undefined { return this._id; } get name(): string { return this._name; } // 领域事件 private addDomainEvent(event: DomainEvent): void { this._domainEvents.push(event); } getDomainEvents(): DomainEvent[] { return [...this._domainEvents]; } clearDomainEvents(): void { this._domainEvents = []; } } ``` ### 创建值对象 ```typescript // src/domain/value-objects/email.vo.ts export class Email { private readonly _value: string; private constructor(value: string) { this._value = value; } static create(value: string): Email { if (!this.isValid(value)) { throw new Error('Invalid email format'); } return new Email(value); } private static isValid(value: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(value); } get value(): string { return this._value; } equals(other: Email): boolean { return this._value === other._value; } toString(): string { return this._value; } } ``` ### 创建领域服务 ```typescript // src/domain/services/pricing.service.ts import { Injectable } from '@nestjs/common'; @Injectable() export class PricingDomainService { private readonly PRICE_PER_TREE = 2199; calculateTotalPrice(treeCount: number): number { return treeCount * this.PRICE_PER_TREE; } calculateDiscount(treeCount: number): number { if (treeCount >= 100) return 0.05; if (treeCount >= 50) return 0.03; if (treeCount >= 10) return 0.01; return 0; } } ``` ### 创建仓储 **1. 定义接口 (领域层)** ```typescript // src/domain/repositories/new-aggregate.repository.interface.ts export const NEW_AGGREGATE_REPOSITORY = Symbol('INewAggregateRepository'); export interface INewAggregateRepository { save(aggregate: NewAggregate): Promise; findById(id: bigint): Promise; findByName(name: string): Promise; } ``` **2. 实现仓储 (基础设施层)** ```typescript // src/infrastructure/persistence/repositories/new-aggregate.repository.impl.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { INewAggregateRepository } from '../../../domain/repositories/new-aggregate.repository.interface'; @Injectable() export class NewAggregateRepositoryImpl implements INewAggregateRepository { constructor(private readonly prisma: PrismaService) {} async save(aggregate: NewAggregate): Promise { const data = NewAggregateMapper.toPersistence(aggregate); if (aggregate.id) { await this.prisma.newAggregate.update({ where: { id: aggregate.id }, data, }); } else { const created = await this.prisma.newAggregate.create({ data }); aggregate.setId(created.id); } } async findById(id: bigint): Promise { const record = await this.prisma.newAggregate.findUnique({ where: { id }, }); return record ? NewAggregateMapper.toDomain(record) : null; } async findByName(name: string): Promise { const records = await this.prisma.newAggregate.findMany({ where: { name: { contains: name } }, }); return records.map(NewAggregateMapper.toDomain); } } ``` **3. 注册到模块** ```typescript // src/infrastructure/infrastructure.module.ts @Module({ providers: [ { provide: NEW_AGGREGATE_REPOSITORY, useClass: NewAggregateRepositoryImpl, }, ], exports: [NEW_AGGREGATE_REPOSITORY], }) export class InfrastructureModule {} ``` --- ## 常用命令 ### Makefile 命令 ```bash # 开发 make install # 安装依赖 make dev # 启动开发服务器 make build # 构建项目 # 数据库 make prisma-generate # 生成 Prisma Client make prisma-migrate # 运行迁移 make prisma-studio # 打开 Prisma Studio make prisma-reset # 重置数据库 # 测试 make test-unit # 单元测试 make test-integration # 集成测试 make test-e2e # E2E 测试 make test-cov # 测试覆盖率 make test-all # 运行所有测试 # Docker make docker-build # 构建 Docker 镜像 make docker-up # 启动容器 make docker-down # 停止容器 make test-docker-all # Docker 中运行测试 # 代码质量 make lint # 运行 ESLint make format # 格式化代码 ``` ### npm 命令 ```bash npm run start # 启动服务 npm run start:dev # 开发模式 npm run start:debug # 调试模式 npm run build # 构建 npm run test # 运行测试 npm run test:watch # 监听模式测试 npm run test:cov # 覆盖率测试 npm run test:e2e # E2E 测试 npm run lint # 代码检查 npm run format # 代码格式化 ``` --- ## 调试技巧 ### VS Code 调试配置 创建 `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug NestJS", "type": "node", "request": "launch", "runtimeArgs": [ "--inspect-brk", "-r", "tsconfig-paths/register", "-r", "ts-node/register" ], "args": ["${workspaceFolder}/src/main.ts"], "cwd": "${workspaceFolder}", "console": "integratedTerminal", "protocol": "inspector" }, { "name": "Debug Jest Tests", "type": "node", "request": "launch", "runtimeArgs": [ "--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand" ], "console": "integratedTerminal" } ] } ``` ### Prisma 查询日志 在 `.env` 中启用: ```env DATABASE_URL="postgresql://...?connection_limit=5" ``` 在 `prisma.service.ts` 中配置: ```typescript super({ log: ['query', 'info', 'warn', 'error'], }); ``` ### 请求日志 使用 NestJS Logger: ```typescript import { Logger } from '@nestjs/common'; @Injectable() export class PlantingApplicationService { private readonly logger = new Logger(PlantingApplicationService.name); async createOrder(userId: bigint, treeCount: number) { this.logger.log(`Creating order for user ${userId}, trees: ${treeCount}`); // ... this.logger.debug('Order created successfully', { orderNo }); } } ``` --- ## 常见问题 ### Q: Prisma Client 未生成 ```bash # 解决方案 npx prisma generate ``` ### Q: 数据库连接失败 检查: 1. PostgreSQL 服务是否启动 2. `.env` 中 DATABASE_URL 是否正确 3. 数据库是否存在 ```bash # 创建数据库 createdb rwadurian_planting ``` ### Q: BigInt 序列化错误 在返回 JSON 时,BigInt 需要转换为字符串: ```typescript // 在响应 DTO 中 class OrderResponse { @Transform(({ value }) => value.toString()) userId: string; } ``` ### Q: 测试时数据库冲突 使用独立的测试数据库: ```env # .env.test DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_planting_test" ``` ### Q: 循环依赖错误 使用 `forwardRef`: ```typescript @Module({ imports: [forwardRef(() => OtherModule)], }) export class MyModule {} ``` ### Q: 热重载不工作 检查 `nest-cli.json`: ```json { "compilerOptions": { "deleteOutDir": true, "webpack": true, "watchAssets": true } } ``` --- ## 参考资料 - [NestJS 官方文档](https://docs.nestjs.com/) - [Prisma 官方文档](https://www.prisma.io/docs/) - [TypeScript 手册](https://www.typescriptlang.org/docs/) - [领域驱动设计精粹](https://www.domainlanguage.com/ddd/)