# Authorization Service 开发指南

## 目录

1. [环境准备](#环境准备)
2. [项目初始化](#项目初始化)
3. [开发规范](#开发规范)
4. [代码结构约定](#代码结构约定)
5. [DDD 实践指南](#ddd-实践指南)
6. [常见开发任务](#常见开发任务)
7. [调试技巧](#调试技巧)

---

## 环境准备

### 系统要求

| 软件 | 版本 | 说明 |
|------|------|------|
| Node.js | 20.x LTS | 推荐使用 nvm 管理 |
| npm | 10.x | 随 Node.js 安装 |
| PostgreSQL | 15.x | 关系型数据库 |
| Redis | 7.x | 缓存服务 |
| Kafka | 3.7.x | 消息队列 |
| Docker | 24.x | 容器运行时 |

### 开发工具推荐

- **IDE**: VSCode
- **VSCode 扩展**:
  - ESLint
  - Prettier
  - Prisma
  - REST Client
  - Docker

### 本地环境配置

#### 1. 安装 Node.js

```bash
# 使用 nvm 安装 Node.js
nvm install 20
nvm use 20
```

#### 2. 启动基础设施

使用 Docker Compose 启动开发环境：

```bash
# 启动 PostgreSQL、Redis、Kafka
docker compose up -d
```

#### 3. 配置环境变量

复制环境变量模板：

```bash
cp .env.example .env.development
```

编辑 `.env.development`：

```env
# 数据库
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/authorization_dev"

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379

# Kafka
KAFKA_BROKERS=localhost:9092

# JWT
JWT_SECRET=your-development-secret-key
JWT_EXPIRES_IN=1h

# 应用
NODE_ENV=development
PORT=3002
```

---

## 项目初始化

### 1. 安装依赖

```bash
npm install
```

### 2. 生成 Prisma 客户端

```bash
npx prisma generate
```

### 3. 运行数据库迁移

```bash
npx prisma migrate dev
```

### 4. 启动开发服务器

```bash
npm run start:dev
```

服务将在 `http://localhost:3002` 启动。

### 5. 访问 API 文档

```
http://localhost:3002/api/docs
```

---

## 开发规范

### 代码风格

项目使用 ESLint + Prettier 进行代码规范检查：

```bash
# 检查代码风格
npm run lint

# 自动修复
npm run lint:fix

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

### 命名规范

#### 文件命名

| 类型 | 格式 | 示例 |
|------|------|------|
| 聚合根 | `{name}.aggregate.ts` | `authorization-role.aggregate.ts` |
| 实体 | `{name}.entity.ts` | `ladder-target-rule.entity.ts` |
| 值对象 | `{name}.vo.ts` | `month.vo.ts` |
| 服务 | `{name}.service.ts` | `authorization-command.service.ts` |
| 控制器 | `{name}.controller.ts` | `authorization.controller.ts` |
| 仓储接口 | `{name}.repository.ts` | `authorization-role.repository.ts` |
| 仓储实现 | `{name}.repository.impl.ts` | `authorization-role.repository.impl.ts` |
| DTO | `{name}.dto.ts` | `apply-authorization.dto.ts` |
| 测试 | `{name}.spec.ts` | `authorization-role.aggregate.spec.ts` |

#### 类命名

| 类型 | 格式 | 示例 |
|------|------|------|
| 聚合根 | `{Name}` | `AuthorizationRole` |
| 值对象 | `{Name}` | `Month`, `RegionCode` |
| 服务 | `{Name}Service` | `AuthorizationCommandService` |
| 控制器 | `{Name}Controller` | `AuthorizationController` |
| 仓储接口 | `I{Name}Repository` | `IAuthorizationRoleRepository` |
| DTO | `{Name}Dto` | `ApplyAuthorizationDto` |

### Git 提交规范

使用 Conventional Commits 规范：

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

<body>

<footer>
```

#### Type 类型

| 类型 | 说明 |
|------|------|
| feat | 新功能 |
| fix | Bug 修复 |
| docs | 文档变更 |
| style | 代码格式（不影响功能） |
| refactor | 重构 |
| test | 测试相关 |
| chore | 构建/工具变更 |

#### 示例

```bash
git commit -m "feat(authorization): add province company authorization"
git commit -m "fix(assessment): correct ranking calculation"
git commit -m "docs(api): update API documentation"
```

---

## 代码结构约定

### 领域层（Domain Layer）

#### 聚合根结构

```typescript
// src/domain/aggregates/authorization-role.aggregate.ts

import { AggregateRoot } from './aggregate-root.base'
import { AuthorizationId, UserId, RegionCode } from '@/domain/value-objects'
import { RoleType, AuthorizationStatus } from '@/domain/enums'
import { AuthorizationAppliedEvent } from '@/domain/events'

export interface AuthorizationRoleProps {
  // 定义聚合根属性
}

export class AuthorizationRole extends AggregateRoot {
  // 私有属性
  private _id: AuthorizationId
  private _userId: UserId
  private _roleType: RoleType
  private _status: AuthorizationStatus

  // Getters（只读访问）
  get id(): AuthorizationId { return this._id }
  get userId(): UserId { return this._userId }

  // 私有构造函数
  private constructor(props: AuthorizationRoleProps) {
    super()
    // 初始化属性
  }

  // 工厂方法 - 创建新实例
  static createAuthProvinceCompany(params: {
    userId: UserId
    provinceCode: string
    provinceName: string
  }): AuthorizationRole {
    const role = new AuthorizationRole({
      // 初始化
    })

    // 添加领域事件
    role.addDomainEvent(new AuthorizationAppliedEvent({
      // 事件数据
    }))

    return role
  }

  // 工厂方法 - 从持久化恢复
  static fromPersistence(props: AuthorizationRoleProps): AuthorizationRole {
    return new AuthorizationRole(props)
  }

  // 业务方法
  authorize(adminId: UserId): void {
    // 状态验证
    if (this._status !== AuthorizationStatus.PENDING) {
      throw new DomainError('只有待审核状态才能审核')
    }

    // 状态变更
    this._status = AuthorizationStatus.APPROVED
    this._authorizedBy = adminId
    this._authorizedAt = new Date()

    // 发布领域事件
    this.addDomainEvent(new AuthorizationApprovedEvent({
      // 事件数据
    }))
  }

  // 持久化转换
  toPersistence(): Record<string, any> {
    return {
      id: this._id.value,
      userId: this._userId.value,
      // ...
    }
  }
}
```

#### 值对象结构

```typescript
// src/domain/value-objects/month.vo.ts

export class Month {
  private constructor(private readonly _value: string) {}

  get value(): string {
    return this._value
  }

  // 工厂方法
  static create(value: string): Month {
    // 验证格式
    if (!/^\d{4}-\d{2}$/.test(value)) {
      throw new DomainError('月份格式无效，应为 YYYY-MM')
    }
    return new Month(value)
  }

  static current(): Month {
    const now = new Date()
    const year = now.getFullYear()
    const month = String(now.getMonth() + 1).padStart(2, '0')
    return new Month(`${year}-${month}`)
  }

  // 业务方法
  next(): Month {
    const [year, month] = this._value.split('-').map(Number)
    if (month === 12) {
      return new Month(`${year + 1}-01`)
    }
    return new Month(`${year}-${String(month + 1).padStart(2, '0')}`)
  }

  // 比较方法
  equals(other: Month): boolean {
    return this._value === other._value
  }

  isBefore(other: Month): boolean {
    return this._value < other._value
  }
}
```

#### 领域事件结构

```typescript
// src/domain/events/authorization-applied.event.ts

import { DomainEvent } from './domain-event.base'

export interface AuthorizationAppliedEventPayload {
  authorizationId: string
  userId: string
  roleType: string
  regionCode: string
}

export class AuthorizationAppliedEvent extends DomainEvent {
  static readonly EVENT_NAME = 'authorization.applied'

  constructor(public readonly payload: AuthorizationAppliedEventPayload) {
    super(AuthorizationAppliedEvent.EVENT_NAME)
  }
}
```

### 应用层（Application Layer）

#### 命令服务结构

```typescript
// src/application/services/authorization-command.service.ts

import { Injectable, Inject } from '@nestjs/common'
import { IAuthorizationRoleRepository } from '@/domain/repositories'
import { AUTHORIZATION_ROLE_REPOSITORY } from '@/infrastructure/persistence/repositories'
import { AuthorizationValidatorService } from '@/domain/services'
import { EventPublisherService } from '@/infrastructure/messaging/kafka'

@Injectable()
export class AuthorizationCommandService {
  constructor(
    @Inject(AUTHORIZATION_ROLE_REPOSITORY)
    private readonly authorizationRepository: IAuthorizationRoleRepository,
    private readonly validatorService: AuthorizationValidatorService,
    private readonly eventPublisher: EventPublisherService,
  ) {}

  async applyProvincialAuthorization(
    userId: string,
    provinceCode: string,
    provinceName: string,
  ): Promise<AuthorizationRole> {
    // 1. 创建值对象
    const userIdVo = UserId.create(userId)
    const regionCodeVo = RegionCode.create(provinceCode)

    // 2. 业务验证
    const validationResult = await this.validatorService.validateAuthorizationRequest(
      userIdVo,
      RoleType.AUTH_PROVINCE_COMPANY,
      regionCodeVo,
    )

    if (!validationResult.isValid) {
      throw new BusinessException(validationResult.errorMessage)
    }

    // 3. 创建聚合根
    const authorization = AuthorizationRole.createAuthProvinceCompany({
      userId: userIdVo,
      provinceCode,
      provinceName,
    })

    // 4. 持久化
    await this.authorizationRepository.save(authorization)

    // 5. 发布领域事件
    await this.eventPublisher.publishAll(authorization.domainEvents)

    return authorization
  }
}
```

### 基础设施层（Infrastructure Layer）

#### 仓储实现结构

```typescript
// src/infrastructure/persistence/repositories/authorization-role.repository.impl.ts

import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
import { IAuthorizationRoleRepository } from '@/domain/repositories'
import { AuthorizationRole } from '@/domain/aggregates'
import { AuthorizationId, UserId, RegionCode } from '@/domain/value-objects'

export const AUTHORIZATION_ROLE_REPOSITORY = Symbol('AUTHORIZATION_ROLE_REPOSITORY')

@Injectable()
export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleRepository {
  constructor(private readonly prisma: PrismaService) {}

  async save(authorization: AuthorizationRole): Promise<void> {
    const data = authorization.toPersistence()

    await this.prisma.authorizationRole.upsert({
      where: { id: data.id },
      create: data,
      update: data,
    })
  }

  async findById(id: AuthorizationId): Promise<AuthorizationRole | null> {
    const record = await this.prisma.authorizationRole.findUnique({
      where: { id: id.value },
    })

    if (!record) return null

    return this.toDomain(record)
  }

  private toDomain(record: any): AuthorizationRole {
    return AuthorizationRole.fromPersistence({
      id: AuthorizationId.create(record.id),
      userId: UserId.create(record.userId),
      // ... 映射其他属性
    })
  }
}
```

---

## DDD 实践指南

### 1. 聚合根设计原则

- **单一职责**：每个聚合根只负责一个业务概念
- **事务边界**：聚合根是事务的边界，一次事务只修改一个聚合根
- **不变量保护**：聚合根负责保护业务不变量
- **最小化聚合**：聚合应该尽可能小

### 2. 值对象使用场景

适合使用值对象的场景：

- ID 类型（AuthorizationId, UserId）
- 度量和数量（Money, Percentage）
- 时间相关（Month, DateRange）
- 描述性数据（Address, Email）

### 3. 领域事件设计

```typescript
// 事件命名：{聚合根}.{动作}
authorization.applied     // 授权申请
authorization.approved    // 授权通过
authorization.rejected    // 授权拒绝
authorization.activated   // 授权激活
authorization.revoked     // 授权撤销

assessment.passed         // 考核通过
assessment.failed         // 考核失败
assessment.bypassed       // 考核豁免
```

### 4. 仓储模式最佳实践

```typescript
// 仓储接口只定义业务需要的方法
interface IAuthorizationRoleRepository {
  save(authorization: AuthorizationRole): Promise<void>
  findById(id: AuthorizationId): Promise<AuthorizationRole | null>
  findByUserId(userId: UserId): Promise<AuthorizationRole[]>
  findActiveByRoleTypeAndRegion(
    roleType: RoleType,
    regionCode: RegionCode,
  ): Promise<AuthorizationRole[]>
}

// 避免：
// - 暴露底层数据库细节
// - 返回原始数据库记录
// - 提供过于通用的查询方法
```

---

## 常见开发任务

### 添加新的授权类型

1. 在 `RoleType` 枚举中添加新类型
2. 在 `AuthorizationRole` 聚合根中添加工厂方法
3. 在 `LadderTargetRule` 中添加对应的考核目标
4. 添加相应的 DTO 和控制器端点
5. 编写单元测试和集成测试

### 添加新的领域事件

1. 在 `src/domain/events` 中创建事件类
2. 在聚合根的相应方法中发布事件
3. 在 Kafka 配置中注册事件主题
4. （可选）创建事件处理器

### 修改数据库模型

1. 修改 `prisma/schema.prisma`
2. 生成迁移：`npx prisma migrate dev --name describe_change`
3. 更新仓储实现中的映射逻辑
4. 更新相应的 DTO

---

## 调试技巧

### 启用调试日志

```typescript
// main.ts
const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn', 'log', 'debug', 'verbose'],
})
```

### Prisma 查询日志

```typescript
// prisma.service.ts
const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error'],
})
```

### VSCode 调试配置

```json
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug NestJS",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "start:debug"],
      "console": "integratedTerminal",
      "restart": true,
      "protocol": "inspector",
      "port": 9229
    }
  ]
}
```

### 常用调试命令

```bash
# 查看 Prisma 生成的 SQL
DEBUG=prisma:query npm run start:dev

# 查看 Redis 操作
redis-cli monitor

# 查看 Kafka 消息
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic authorization-events --from-beginning
```

---

## 参考资源

- [NestJS 官方文档](https://docs.nestjs.com/)
- [Prisma 官方文档](https://www.prisma.io/docs/)
- [领域驱动设计参考](https://www.domainlanguage.com/ddd/reference/)
- [TypeScript 风格指南](https://google.github.io/styleguide/tsguide.html)
