# Admin Service 开发指南

## 目录

- [1. 开发环境设置](#1-开发环境设置)
- [2. 项目初始化](#2-项目初始化)
- [3. 开发流程](#3-开发流程)
- [4. 代码规范](#4-代码规范)
- [5. 调试技巧](#5-调试技巧)
- [6. 常见开发任务](#6-常见开发任务)

---

## 1. 开发环境设置

### 1.1 系统要求

| 工具 | 版本要求 | 说明 |
|-----|---------|------|
| **Node.js** | >= 20.x | 推荐使用 LTS 版本 |
| **npm** | >= 10.x | 或使用 yarn/pnpm |
| **PostgreSQL** | >= 16.x | 本地开发或 Docker |
| **Docker** | >= 24.x | (可选) 容器化开发 |
| **Git** | >= 2.x | 版本控制 |
| **VSCode** | 最新版 | 推荐 IDE |

### 1.2 VSCode 推荐插件

```json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",           // ESLint
    "esbenp.prettier-vscode",           // Prettier
    "prisma.prisma",                    // Prisma
    "firsttris.vscode-jest-runner",     // Jest Runner
    "orta.vscode-jest",                 // Jest
    "ms-vscode.vscode-typescript-next", // TypeScript
    "usernamehw.errorlens",             // Error Lens
    "eamodio.gitlens"                   // GitLens
  ]
}
```

保存到 `.vscode/extensions.json`

### 1.3 VSCode 工作区设置

```json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "typescript.preferences.importModuleSpecifier": "relative",
  "jest.autoRun": "off",
  "[prisma]": {
    "editor.defaultFormatter": "Prisma.prisma"
  }
}
```

保存到 `.vscode/settings.json`

---

## 2. 项目初始化

### 2.1 克隆项目

```bash
git clone https://github.com/your-org/rwa-durian.git
cd rwa-durian/backend/services/admin-service
```

### 2.2 安装依赖

```bash
# 使用 npm
npm install

# 或使用 yarn
yarn install

# 或使用 pnpm
pnpm install
```

### 2.3 环境配置

创建 `.env.development` 文件：

```env
# 应用配置
NODE_ENV=development
APP_PORT=3005
API_PREFIX=api/v1

# 数据库配置
DATABASE_URL=postgresql://postgres:password@localhost:5432/admin_service_dev?schema=public

# 日志配置
LOG_LEVEL=debug

# CORS 配置
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
```

**注意**: 不要提交 `.env.*` 文件到 Git！已添加到 `.gitignore`。

### 2.4 数据库初始化

#### 方案 1: 本地 PostgreSQL

```bash
# 1. 创建数据库
psql -U postgres -c "CREATE DATABASE admin_service_dev;"

# 2. 运行迁移
npm run prisma:migrate:dev

# 3. 生成 Prisma Client
npm run prisma:generate
```

#### 方案 2: Docker PostgreSQL

```bash
# 1. 启动数据库容器
docker run -d \
  --name admin-dev-db \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=password \
  -e POSTGRES_DB=admin_service_dev \
  -p 5432:5432 \
  postgres:16-alpine

# 2. 运行迁移
npm run prisma:migrate:dev

# 3. 生成 Prisma Client
npm run prisma:generate
```

### 2.5 验证环境

```bash
# 检查数据库连接
npm run prisma:studio

# 运行测试
npm run test:unit

# 启动开发服务器
npm run start:dev
```

访问 `http://localhost:3005/api/v1/health` 应返回:
```json
{"status": "ok"}
```

---

## 3. 开发流程

### 3.1 Git 工作流

#### 分支策略

```
main (生产)
  ↑
develop (开发)
  ↑
feature/xxx (功能分支)
hotfix/xxx (紧急修复)
```

#### 创建功能分支

```bash
# 从 develop 创建功能分支
git checkout develop
git pull origin develop
git checkout -b feature/add-version-delete

# 开发...

# 提交
git add .
git commit -m "feat(version): add delete version functionality"

# 推送
git push origin feature/add-version-delete

# 创建 Pull Request
```

#### Commit Message 规范

遵循 [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>(<scope>): <subject>

<body>

<footer>
```

**Type 类型**:
- `feat`: 新功能
- `fix`: Bug 修复
- `docs`: 文档更新
- `style`: 代码格式 (不影响功能)
- `refactor`: 重构
- `test`: 测试相关
- `chore`: 构建/工具配置

**示例**:
```bash
git commit -m "feat(version): add soft delete for versions"
git commit -m "fix(repository): handle duplicate version code error"
git commit -m "docs(api): update version check endpoint documentation"
git commit -m "test(handler): add unit tests for EnableVersionHandler"
```

### 3.2 开发迭代流程

#### 1. 需求分析

**示例**: 添加版本删除功能

- 功能需求: 支持软删除版本记录
- 业务规则:
  - 只能删除未启用的版本
  - 删除后不可恢复
  - 记录删除人和删除时间

#### 2. 设计方案

**领域层修改**:
```typescript
// domain/entities/app-version.entity.ts
class AppVersion {
  delete(deletedBy: string): void {
    if (this._isEnabled) {
      throw new DomainException('Cannot delete an enabled version');
    }
    this._deletedBy = deletedBy;
    this._deletedAt = new Date();
  }

  get isDeleted(): boolean {
    return this._deletedAt !== null;
  }
}
```

**Prisma Schema 修改**:
```prisma
model AppVersion {
  // ... 现有字段
  deletedBy  String?
  deletedAt  DateTime?
}
```

#### 3. 实现功能

**步骤 1**: 更新 Prisma Schema
```bash
# prisma/schema.prisma
# 添加 deletedBy 和 deletedAt 字段

# 创建迁移
npm run prisma:migrate:dev --name add_soft_delete
```

**步骤 2**: 更新领域实体
```typescript
// src/domain/entities/app-version.entity.ts
export class AppVersion {
  private _deletedBy: string | null = null;
  private _deletedAt: Date | null = null;

  delete(deletedBy: string): void {
    if (this._isEnabled) {
      throw new DomainException('Cannot delete an enabled version');
    }
    this._deletedBy = deletedBy;
    this._deletedAt = new Date();
  }

  get isDeleted(): boolean {
    return this._deletedAt !== null;
  }
}
```

**步骤 3**: 创建命令和处理器
```typescript
// src/application/commands/delete-version.command.ts
export class DeleteVersionCommand {
  constructor(
    public readonly versionId: string,
    public readonly deletedBy: string,
  ) {}
}

// src/application/handlers/delete-version.handler.ts
@Injectable()
export class DeleteVersionHandler {
  constructor(
    @Inject(APP_VERSION_REPOSITORY)
    private readonly repository: AppVersionRepository,
  ) {}

  async execute(command: DeleteVersionCommand): Promise<void> {
    const version = await this.repository.findById(command.versionId);
    if (!version) {
      throw new ApplicationException('Version not found');
    }

    version.delete(command.deletedBy);
    await this.repository.save(version);
  }
}
```

**步骤 4**: 添加控制器端点
```typescript
// src/api/controllers/version.controller.ts
@Delete(':id')
async deleteVersion(
  @Param('id') id: string,
  @Body() dto: DeleteVersionDto,
): Promise<void> {
  const command = new DeleteVersionCommand(id, dto.deletedBy);
  await this.deleteVersionHandler.execute(command);
}
```

**步骤 5**: 添加 DTO
```typescript
// src/api/dtos/delete-version.dto.ts
export class DeleteVersionDto {
  @IsNotEmpty()
  @IsString()
  deletedBy: string;
}
```

#### 4. 编写测试

**单元测试**:
```typescript
// test/unit/domain/entities/app-version.entity.spec.ts
describe('delete', () => {
  it('should delete version when disabled', () => {
    const version = AppVersion.create({...});
    version.disable('admin');

    version.delete('admin');

    expect(version.isDeleted).toBe(true);
  });

  it('should throw error when deleting enabled version', () => {
    const version = AppVersion.create({...});

    expect(() => version.delete('admin')).toThrow(DomainException);
  });
});
```

**集成测试**:
```typescript
// test/integration/handlers/delete-version.handler.spec.ts
describe('DeleteVersionHandler', () => {
  it('should delete version successfully', async () => {
    const version = await createTestVersion({ isEnabled: false });
    const command = new DeleteVersionCommand(version.id, 'admin');

    await handler.execute(command);

    const deleted = await repository.findById(version.id);
    expect(deleted.isDeleted).toBe(true);
  });
});
```

**E2E 测试**:
```typescript
// test/e2e/version.controller.spec.ts
describe('/version/:id (DELETE)', () => {
  it('should delete version successfully', () => {
    return request(app.getHttpServer())
      .delete(`/api/v1/version/${versionId}`)
      .send({ deletedBy: 'admin' })
      .expect(204);
  });
});
```

#### 5. 运行测试

```bash
# 单元测试
npm run test:unit

# 集成测试 (需要数据库)
DATABASE_URL="postgresql://postgres:password@localhost:5432/admin_service_test" \
  npm run test:integration

# E2E 测试 (需要数据库)
DATABASE_URL="postgresql://postgres:password@localhost:5432/admin_service_test" \
  npm run test:e2e

# 全部测试 + 覆盖率
npm run test:cov
```

#### 6. 本地验证

```bash
# 启动开发服务器
npm run start:dev

# 测试 API
curl -X DELETE http://localhost:3005/api/v1/version/550e8400-e29b-41d4-a716-446655440000 \
  -H "Content-Type: application/json" \
  -d '{"deletedBy": "admin"}'
```

#### 7. 代码审查

```bash
# 格式化代码
npm run format

# 检查代码规范
npm run lint

# 修复 lint 问题
npm run lint:fix
```

#### 8. 提交代码

```bash
git add .
git commit -m "feat(version): add soft delete functionality

- Add deletedBy and deletedAt fields to AppVersion entity
- Implement delete business logic with validation
- Add DELETE /api/v1/version/:id endpoint
- Add comprehensive unit, integration, and E2E tests
- Update Prisma schema with migration

Closes #123"

git push origin feature/add-version-delete
```

---

## 4. 代码规范

### 4.1 TypeScript 规范

#### 类型定义

```typescript
// ✅ 推荐: 显式类型注解
function calculateFileSize(bytes: bigint): string {
  return `${bytes} bytes`;
}

// ❌ 避免: 使用 any
function process(data: any) { // 不推荐
  return data.value;
}

// ✅ 推荐: 使用具体类型
interface ProcessData {
  value: string;
}
function process(data: ProcessData) {
  return data.value;
}
```

#### 命名规范

```typescript
// ✅ 类名: PascalCase
class AppVersion {}
class VersionCheckService {}

// ✅ 接口名: PascalCase, 以 I 开头 (可选)
interface AppVersionRepository {}
interface IAppVersionRepository {} // 也可以

// ✅ 方法/变量: camelCase
const versionCode = 100;
function findLatestVersion() {}

// ✅ 常量: SCREAMING_SNAKE_CASE
const MAX_FILE_SIZE = 1024 * 1024 * 100; // 100MB
const DEFAULT_PAGE_SIZE = 10;

// ✅ 私有属性: 下划线前缀
class AppVersion {
  private _versionCode: VersionCode;
  private _isEnabled: boolean;
}
```

#### 导入顺序

```typescript
// 1. Node.js 内置模块
import * as path from 'path';
import * as fs from 'fs';

// 2. 外部依赖
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/prisma/prisma.service';

// 3. 内部模块 (绝对路径)
import { AppVersion } from '@/domain/entities/app-version.entity';
import { VersionCode } from '@/domain/value-objects/version-code.vo';

// 4. 相对路径导入
import { CreateVersionDto } from './dtos/create-version.dto';
import { VersionController } from './version.controller';
```

### 4.2 DDD 代码规范

#### 值对象 (Value Object)

```typescript
// ✅ 推荐: 不可变、验证逻辑封装
export class VersionCode {
  private readonly value: number;

  constructor(value: number) {
    if (!Number.isInteger(value) || value < 1) {
      throw new DomainException('Version code must be a positive integer');
    }
    this.value = value;
  }

  static create(value: number): VersionCode {
    return new VersionCode(value);
  }

  getValue(): number {
    return this.value;
  }

  equals(other: VersionCode): boolean {
    return this.value === other.value;
  }
}
```

#### 实体 (Entity)

```typescript
// ✅ 推荐: 封装业务逻辑、私有属性、工厂方法
export class AppVersion {
  private readonly _id: string;
  private _isEnabled: boolean;

  private constructor(props: AppVersionProps) {
    this._id = props.id;
    this._isEnabled = props.isEnabled;
  }

  // 工厂方法
  static create(props: CreateAppVersionProps): AppVersion {
    // 验证逻辑
    return new AppVersion({...});
  }

  // 业务方法
  enable(updatedBy: string): void {
    this._isEnabled = true;
    this._updatedBy = updatedBy;
    this._updatedAt = new Date();
  }

  // 查询方法
  isEnabledVersion(): boolean {
    return this._isEnabled;
  }
}
```

#### Repository 接口

```typescript
// ✅ 推荐: 领域层定义接口，基础设施层实现
// domain/repositories/app-version.repository.ts
export interface AppVersionRepository {
  save(version: AppVersion): Promise<AppVersion>;
  findById(id: string): Promise<AppVersion | null>;
  findLatestEnabledVersion(platform: Platform): Promise<AppVersion | null>;
}

// infrastructure/persistence/repositories/app-version.repository.impl.ts
@Injectable()
export class AppVersionRepositoryImpl implements AppVersionRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(version: AppVersion): Promise<AppVersion> {
    // 实现逻辑
  }
}
```

### 4.3 NestJS 规范

#### 依赖注入

```typescript
// ✅ 推荐: 构造函数注入
@Injectable()
export class VersionController {
  constructor(
    private readonly createVersionHandler: CreateVersionHandler,
    private readonly enableVersionHandler: EnableVersionHandler,
  ) {}
}

// ✅ 推荐: 使用 @Inject 注入接口
@Injectable()
export class CreateVersionHandler {
  constructor(
    @Inject(APP_VERSION_REPOSITORY)
    private readonly repository: AppVersionRepository,
  ) {}
}
```

#### 模块组织

```typescript
// ✅ 推荐: 清晰的 Provider 定义
@Module({
  imports: [PrismaModule],
  controllers: [VersionController],
  providers: [
    // Handlers
    CreateVersionHandler,
    EnableVersionHandler,
    DisableVersionHandler,

    // Services
    VersionCheckService,

    // Repositories
    {
      provide: APP_VERSION_REPOSITORY,
      useClass: AppVersionRepositoryImpl,
    },
  ],
  exports: [APP_VERSION_REPOSITORY],
})
export class VersionModule {}
```

### 4.4 Prisma 规范

#### Schema 定义

```prisma
// ✅ 推荐: 清晰的模型定义和注释
/// 应用版本表
model AppVersion {
  /// 主键 (UUID)
  id            String   @id @default(uuid())

  /// 平台 (android/ios)
  platform      String

  /// 版本号 (整数, 递增)
  versionCode   Int

  /// 版本名称 (语义化版本)
  versionName   String

  /// 创建时间
  createdAt     DateTime @default(now())

  /// 更新时间
  updatedAt     DateTime @updatedAt

  /// 平台 + 版本号唯一索引
  @@unique([platform, versionCode], name: "platform_versionCode")

  /// 表名
  @@map("AppVersion")
}
```

#### 查询优化

```typescript
// ✅ 推荐: 使用索引字段查询
await prisma.appVersion.findFirst({
  where: {
    platform: 'android',
    isEnabled: true,
  },
  orderBy: {
    versionCode: 'desc',
  },
});

// ✅ 推荐: 选择必要字段
await prisma.appVersion.findMany({
  select: {
    id: true,
    versionCode: true,
    versionName: true,
  },
});

// ❌ 避免: N+1 查询问题
// 如果需要关联查询，使用 include
```

---

## 5. 调试技巧

### 5.1 VSCode 调试配置

创建 `.vscode/launch.json`:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug NestJS",
      "runtimeArgs": [
        "-r",
        "ts-node/register",
        "-r",
        "tsconfig-paths/register"
      ],
      "args": ["${workspaceFolder}/src/main.ts"],
      "envFile": "${workspaceFolder}/.env.development",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Current File",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "${fileBasenameNoExtension}",
        "--runInBand",
        "--no-cache",
        "--watchAll=false"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "windows": {
        "program": "${workspaceFolder}/node_modules/jest/bin/jest"
      }
    }
  ]
}
```

### 5.2 日志调试

```typescript
// 使用 NestJS Logger
import { Logger } from '@nestjs/common';

@Injectable()
export class CreateVersionHandler {
  private readonly logger = new Logger(CreateVersionHandler.name);

  async execute(command: CreateVersionCommand): Promise<AppVersion> {
    this.logger.log(`Creating version: ${command.platform} v${command.versionName}`);
    this.logger.debug(`Command details: ${JSON.stringify(command)}`);

    try {
      const version = await this.repository.save(appVersion);
      this.logger.log(`Version created successfully: ${version.id}`);
      return version;
    } catch (error) {
      this.logger.error(`Failed to create version: ${error.message}`, error.stack);
      throw error;
    }
  }
}
```

### 5.3 数据库调试

```bash
# Prisma Studio - 可视化数据库工具
npm run prisma:studio

# 查看生成的 SQL
DATABASE_URL="..." npx prisma migrate dev --create-only

# 直接连接数据库
psql -U postgres -d admin_service_dev

# 查看迁移状态
npx prisma migrate status
```

### 5.4 HTTP 请求调试

#### 使用 REST Client (VSCode 插件)

创建 `test.http`:

```http
### 创建版本
POST http://localhost:3005/api/v1/version
Content-Type: application/json

{
  "platform": "android",
  "versionCode": 100,
  "versionName": "1.0.0",
  "buildNumber": "1",
  "downloadUrl": "https://cdn.example.com/app-v1.0.0.apk",
  "fileSize": 52428800,
  "fileSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "changelog": "Initial release",
  "isEnabled": true,
  "isForceUpdate": false,
  "createdBy": "admin"
}

### 检查更新
GET http://localhost:3005/api/v1/version/check?platform=android&versionCode=99
```

---

## 6. 常见开发任务

### 6.1 添加新的值对象

```bash
# 1. 创建值对象文件
touch src/domain/value-objects/download-url.vo.ts

# 2. 实现值对象
# 3. 添加单元测试
touch test/unit/domain/value-objects/download-url.vo.spec.ts

# 4. 运行测试
npm run test:unit -- download-url.vo.spec
```

### 6.2 添加新的 API 端点

```bash
# 1. 创建 DTO
# src/api/dtos/update-version.dto.ts

# 2. 创建命令
# src/application/commands/update-version.command.ts

# 3. 创建处理器
# src/application/handlers/update-version.handler.ts

# 4. 添加控制器方法
# src/api/controllers/version.controller.ts

# 5. 添加测试
# test/e2e/version.controller.spec.ts

# 6. 验证
npm run start:dev
curl -X PATCH http://localhost:3005/api/v1/version/{id} -d '{...}'
```

### 6.3 修改数据库 Schema

```bash
# 1. 修改 prisma/schema.prisma
# 2. 创建迁移
npm run prisma:migrate:dev --name add_new_field

# 3. 生成 Prisma Client
npm run prisma:generate

# 4. 更新领域实体
# src/domain/entities/app-version.entity.ts

# 5. 更新 Mapper
# src/infrastructure/persistence/mappers/app-version.mapper.ts

# 6. 运行测试验证
npm run test
```

### 6.4 性能优化

```bash
# 1. 分析慢查询
# 启用 Prisma 查询日志
# prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
  log      = ["query", "info", "warn", "error"]
}

# 2. 添加数据库索引
# prisma/schema.prisma
model AppVersion {
  // ...
  @@index([platform, isEnabled, versionCode(sort: Desc)])
}

# 3. 创建迁移
npm run prisma:migrate:dev --name add_performance_index
```

### 6.5 添加缓存层

```typescript
// 1. 安装依赖
npm install @nestjs/cache-manager cache-manager

// 2. 配置缓存模块
// src/app.module.ts
import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [
    CacheModule.register({
      ttl: 300, // 5 分钟
      max: 100, // 最多缓存 100 项
    }),
  ],
})
export class AppModule {}

// 3. 使用缓存
@Injectable()
export class VersionCheckService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}

  async checkForUpdate(platform: Platform, versionCode: number) {
    const cacheKey = `version:${platform}:${versionCode}`;

    const cached = await this.cacheManager.get(cacheKey);
    if (cached) {
      return cached;
    }

    const result = await this.performCheck(platform, versionCode);
    await this.cacheManager.set(cacheKey, result);

    return result;
  }
}
```

---

## 7. 故障排查

### 7.1 常见问题

#### 问题 1: Prisma Client 未生成

**错误**:
```
Cannot find module '@prisma/client'
```

**解决方案**:
```bash
npm run prisma:generate
```

#### 问题 2: 数据库连接失败

**错误**:
```
Error: P1001: Can't reach database server
```

**解决方案**:
```bash
# 检查数据库是否运行
docker ps | grep postgres

# 检查 DATABASE_URL 配置
echo $DATABASE_URL

# 测试连接
psql -U postgres -h localhost -p 5432 -d admin_service_dev
```

#### 问题 3: 迁移冲突

**错误**:
```
Error: Migration failed
```

**解决方案**:
```bash
# 重置数据库 (开发环境)
npm run prisma:migrate:reset

# 或手动解决冲突
npx prisma migrate resolve --applied <migration_name>
```

### 7.2 性能分析

```bash
# 使用 Prisma Studio 查看数据
npm run prisma:studio

# 分析查询性能
DATABASE_URL="..." npx prisma db execute \
  --file analyze_queries.sql

# 使用 Node.js profiler
node --inspect-brk dist/main.js
# 访问 chrome://inspect
```

---

**最后更新**: 2025-12-03
**版本**: 1.0.0
**维护者**: RWA Durian Team
