# Presence Service 开发指南 ## 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 推荐工具 | 工具 | 用途 | |-----|------| | VS Code | IDE | | Prisma Extension | Prisma 语法高亮 | | ESLint Extension | 代码检查 | | Prettier Extension | 代码格式化 | | REST Client | API 测试 | --- ## 2. 项目设置 ### 2.1 克隆项目 ```bash git clone cd backend/services/presence-service ``` ### 2.2 安装依赖 ```bash npm install ``` ### 2.3 环境配置 复制环境变量模板: ```bash cp .env.example .env.development ``` 编辑 `.env.development`: ```env # 应用配置 NODE_ENV=development PORT=3000 # 数据库配置 DATABASE_URL=postgresql://postgres:postgres@localhost:5432/presence_dev?schema=public # Redis 配置 REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 # Kafka 配置 KAFKA_BROKERS=localhost:9092 KAFKA_CLIENT_ID=presence-service-dev # JWT 配置 (从 identity-service 获取) JWT_SECRET=your-jwt-secret-key ``` ### 2.4 启动基础设施 使用 Docker Compose 启动 PostgreSQL 和 Redis: ```bash # 启动开发环境依赖 docker compose -f docker-compose.dev.yml up -d # 查看服务状态 docker compose -f docker-compose.dev.yml ps ``` `docker-compose.dev.yml` 示例: ```yaml services: postgres: image: postgres:15-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: presence_dev ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - "6379:6379" volumes: postgres-data: ``` ### 2.5 数据库迁移 ```bash # 生成 Prisma Client npx prisma generate # 同步数据库 Schema (开发环境) npx prisma db push # 或使用迁移 (生产环境) npx prisma migrate dev --name init ``` ### 2.6 启动服务 ```bash # 开发模式 (热重载) npm run start:dev # 调试模式 npm run start:debug # 生产模式 npm run start:prod ``` --- ## 3. 项目结构约定 ### 3.1 命名规范 | 类型 | 命名规范 | 示例 | |-----|---------|------| | 文件名 | kebab-case | `event-log.entity.ts` | | 类名 | PascalCase | `EventLogEntity` | | 接口名 | PascalCase + I 前缀 | `IEventLogRepository` | | 方法名 | camelCase | `findByTimeRange` | | 常量 | UPPER_SNAKE_CASE | `EVENT_LOG_REPOSITORY` | | 枚举值 | UPPER_SNAKE_CASE | `EventType.APP_SESSION_START` | ### 3.2 目录约定 ``` src/ ├── domain/ # 领域层 - 纯业务逻辑,无框架依赖 ├── application/ # 应用层 - 用例编排,CQRS 处理器 ├── infrastructure/ # 基础设施层 - 技术实现 ├── api/ # API 层 - HTTP 控制器 └── shared/ # 共享模块 - 工具、过滤器、守卫 ``` ### 3.3 模块组织 每个功能模块遵循以下结构: ``` feature/ ├── feature.module.ts # 模块定义 ├── feature.controller.ts # 控制器 (可选) ├── feature.service.ts # 服务 (可选) ├── dto/ # 数据传输对象 │ ├── request/ │ └── response/ └── __tests__/ # 单元测试 (可选) ``` --- ## 4. 编码规范 ### 4.1 DDD 原则 #### 领域层规则 1. **不依赖任何框架** - 领域层只使用纯 TypeScript 2. **聚合根保护内部状态** - 通过方法修改状态,不直接暴露属性 3. **值对象不可变** - 创建后不能修改 4. **领域服务无状态** - 不持有任何状态 ```typescript // ✅ 好的做法 - 值对象不可变 export class InstallId { private constructor(private readonly _value: string) {} static fromString(value: string): InstallId { // 校验逻辑 return new InstallId(value); } get value(): string { return this._value; } } // ❌ 不好的做法 - 值对象可变 export class InstallId { public value: string; // 可以被外部修改 } ``` #### 聚合根设计 ```typescript // ✅ 好的做法 - 聚合根保护内部状态 export class DailyActiveStats { private _dauCount: number; updateStats(count: number): void { if (count < 0) { throw new InvalidDauCountException(); } this._dauCount = count; this.incrementVersion(); } get dauCount(): number { return this._dauCount; } } // ❌ 不好的做法 - 直接暴露内部状态 export class DailyActiveStats { public dauCount: number; // 外部可以直接修改 } ``` ### 4.2 CQRS 规范 #### Command 设计 ```typescript // Command 只包含执行操作所需的数据 export class RecordHeartbeatCommand { constructor( public readonly userId: bigint, public readonly installId: string, public readonly appVersion: string, public readonly clientTs: number, ) {} } // Handler 负责业务编排 @CommandHandler(RecordHeartbeatCommand) export class RecordHeartbeatHandler implements ICommandHandler { async execute(command: RecordHeartbeatCommand): Promise { // 1. 验证 // 2. 执行业务逻辑 // 3. 持久化 // 4. 发布事件 } } ``` #### Query 设计 ```typescript // Query 只包含查询条件 export class GetOnlineCountQuery { constructor( public readonly windowSeconds: number = 180, ) {} } // Handler 返回查询结果 @QueryHandler(GetOnlineCountQuery) export class GetOnlineCountHandler implements IQueryHandler { async execute(query: GetOnlineCountQuery): Promise { // 直接读取数据,不修改状态 } } ``` ### 4.3 依赖注入 ```typescript // 1. 定义接口和 Token (domain/repositories/) export const EVENT_LOG_REPOSITORY = Symbol('EVENT_LOG_REPOSITORY'); export interface IEventLogRepository { insert(log: EventLog): Promise; } // 2. 实现接口 (infrastructure/persistence/repositories/) @Injectable() export class EventLogRepositoryImpl implements IEventLogRepository { async insert(log: EventLog): Promise { // 具体实现 } } // 3. 配置绑定 (infrastructure/infrastructure.module.ts) @Module({ providers: [ { provide: EVENT_LOG_REPOSITORY, useClass: EventLogRepositoryImpl, }, ], }) // 4. 使用 (application/commands/) @CommandHandler(SomeCommand) export class SomeHandler { constructor( @Inject(EVENT_LOG_REPOSITORY) private readonly repo: IEventLogRepository, ) {} } ``` ### 4.4 错误处理 ```typescript // 1. 定义领域异常 (domain/exceptions/) export class InvalidInstallIdException extends DomainException { constructor(value: string) { super(`Invalid install ID: ${value}`); } get code(): string { return 'INVALID_INSTALL_ID'; } } // 2. 在值对象中使用 export class InstallId { static fromString(value: string): InstallId { if (!this.isValid(value)) { throw new InvalidInstallIdException(value); } return new InstallId(value); } } // 3. GlobalExceptionFilter 自动处理 // DomainException -> 400 Bad Request ``` --- ## 5. 常用命令 ### 5.1 开发命令 ```bash # 启动开发服务器 npm run start:dev # 代码检查 npm run lint # 代码格式化 npm run format # 类型检查 npm run type-check ``` ### 5.2 数据库命令 ```bash # 生成 Prisma Client npx prisma generate # 同步 Schema (开发) npx prisma db push # 创建迁移 npx prisma migrate dev --name # 应用迁移 (生产) npx prisma migrate deploy # 打开 Prisma Studio npx prisma studio # 重置数据库 npx prisma migrate reset ``` ### 5.3 测试命令 ```bash # 运行所有测试 npm test # 运行单元测试 npm run test:unit # 运行集成测试 npm run test:integration # 运行 E2E 测试 npm run test:e2e # 生成覆盖率报告 npm run test:cov # 监视模式 npm run test:watch ``` ### 5.4 构建命令 ```bash # 构建生产版本 npm run build # 清理构建产物 rm -rf dist/ ``` --- ## 6. 开发流程 ### 6.1 新增功能流程 1. **领域建模** - 识别聚合、实体、值对象 - 定义领域服务 - 设计仓储接口 2. **编写领域层代码** - 创建值对象 (`domain/value-objects/`) - 创建实体/聚合 (`domain/entities/`, `domain/aggregates/`) - 创建领域服务 (`domain/services/`) - 定义仓储接口 (`domain/repositories/`) 3. **编写应用层代码** - 创建 Command/Query (`application/commands/`, `application/queries/`) - 创建 Handler 4. **编写基础设施层代码** - 创建 Mapper (`infrastructure/persistence/mappers/`) - 实现仓储 (`infrastructure/persistence/repositories/`) 5. **编写 API 层代码** - 创建 DTO (`api/dto/`) - 创建 Controller (`api/controllers/`) 6. **编写测试** - 单元测试 (领域层) - 集成测试 (应用层) - E2E 测试 (API 层) ### 6.2 代码审查清单 - [ ] 领域层是否无框架依赖? - [ ] 值对象是否不可变? - [ ] 聚合根是否保护了内部状态? - [ ] 仓储接口是否定义在领域层? - [ ] Command/Query 是否职责单一? - [ ] 是否有充分的测试覆盖? - [ ] 异常处理是否完善? - [ ] 是否遵循命名规范? --- ## 7. 调试技巧 ### 7.1 VS Code 调试配置 `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug NestJS", "runtimeExecutable": "npm", "runtimeArgs": ["run", "start:debug"], "console": "integratedTerminal", "restart": true, "autoAttachChildProcesses": true }, { "type": "node", "request": "launch", "name": "Debug Jest Tests", "runtimeExecutable": "npm", "runtimeArgs": ["run", "test:debug"], "console": "integratedTerminal" } ] } ``` ### 7.2 日志调试 ```typescript import { Logger } from '@nestjs/common'; const logger = new Logger('MyService'); logger.log('Info message'); logger.warn('Warning message'); logger.error('Error message', error.stack); logger.debug('Debug message'); // 需要设置 LOG_LEVEL=debug ``` ### 7.3 Prisma 调试 ```bash # 启用查询日志 DEBUG="prisma:query" npm run start:dev # 或在 schema.prisma 中配置 generator client { provider = "prisma-client-js" previewFeatures = ["tracing"] } ``` --- ## 8. Git 工作流 ### 8.1 分支命名 | 类型 | 格式 | 示例 | |-----|------|------| | 功能 | `feature/` | `feature/add-online-history-api` | | 修复 | `fix/` | `fix/heartbeat-validation` | | 重构 | `refactor/` | `refactor/redis-repository` | | 文档 | `docs/` | `docs/api-documentation` | ### 8.2 提交信息格式 ``` (): [optional body] [optional footer] ``` 类型: - `feat`: 新功能 - `fix`: 修复 bug - `refactor`: 重构 - `docs`: 文档 - `test`: 测试 - `chore`: 构建/工具 示例: ``` feat(presence): add online history query API - Add GetOnlineHistoryQuery and handler - Add OnlineHistoryResponseDto - Add endpoint GET /api/v1/presence/online-history Closes #123 ``` --- ## 9. 常见问题 ### Q: Prisma Client 未生成 ```bash npx prisma generate ``` ### Q: 数据库连接失败 1. 检查 Docker 容器是否运行 2. 检查 DATABASE_URL 是否正确 3. 检查端口是否被占用 ### Q: Redis 连接失败 1. 检查 REDIS_HOST 和 REDIS_PORT 2. 检查 Redis 容器状态 3. 如有密码,检查 REDIS_PASSWORD ### Q: 测试失败 1. 确保测试数据库已启动 2. 检查环境变量配置 3. 运行 `npx prisma db push` 同步 schema