# MPC Party Service 开发指南

## 环境准备

### 系统要求

- **Node.js**: >= 18.x
- **npm**: >= 9.x
- **MySQL**: >= 8.0
- **Redis**: >= 6.x
- **Docker**: >= 20.x (可选，用于容器化开发)

### 安装步骤

#### 1. 克隆项目

```bash
cd backend/services/mpc-service
```

#### 2. 安装依赖

```bash
npm install
```

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

复制环境变量模板并修改：

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

编辑 `.env` 文件：

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

# 数据库配置
DATABASE_URL="mysql://user:password@localhost:3306/mpc_service"

# Redis 配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=

# JWT 配置
JWT_SECRET=your-super-secret-jwt-key-at-least-32-characters
JWT_ACCESS_EXPIRES_IN=2h
JWT_REFRESH_EXPIRES_IN=7d

# MPC 配置
MPC_PARTY_ID=party-server-1
MPC_COORDINATOR_URL=http://localhost:50051
MPC_MESSAGE_ROUTER_WS_URL=ws://localhost:50052

# 加密配置
SHARE_MASTER_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

# Kafka 配置 (可选)
KAFKA_ENABLED=false
KAFKA_BROKERS=localhost:9092
KAFKA_CLIENT_ID=mpc-party-service
```

#### 4. 初始化数据库

```bash
# 生成 Prisma Client
npx prisma generate

# 运行数据库迁移
npx prisma migrate dev

# (可选) 查看数据库
npx prisma studio
```

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

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

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

---

## 开发工作流

### 项目脚本

```json
{
  "scripts": {
    // 开发
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",

    // 构建
    "build": "nest build",
    "prebuild": "rimraf dist",

    // 测试
    "test": "jest --config ./tests/jest.config.js",
    "test:unit": "jest --config ./tests/jest-unit.config.js",
    "test:integration": "jest --config ./tests/jest-integration.config.js",
    "test:e2e": "jest --config ./tests/jest-e2e.config.js",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",

    // 代码质量
    "lint": "eslint \"{src,tests}/**/*.ts\" --fix",
    "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",

    // 数据库
    "prisma:generate": "prisma generate",
    "prisma:migrate": "prisma migrate dev",
    "prisma:studio": "prisma studio"
  }
}
```

### 开发流程

1. **创建功能分支**
   ```bash
   git checkout -b feature/your-feature-name
   ```

2. **编写代码**
   - 遵循项目代码规范
   - 编写相应的测试

3. **运行测试**
   ```bash
   npm run test
   npm run lint
   ```

4. **提交代码**
   ```bash
   git add .
   git commit -m "feat: add your feature description"
   ```

5. **创建 Pull Request**

---

## 代码规范

### 目录命名

- 使用 kebab-case：`party-share`, `mpc-party`
- 模块目录包含 `index.ts` 导出

### 文件命名

- 实体：`party-share.entity.ts`
- 服务：`share-encryption.domain-service.ts`
- 控制器：`mpc-party.controller.ts`
- DTO：`participate-keygen.dto.ts`
- 测试：`party-share.entity.spec.ts`

### TypeScript 规范

```typescript
// 使用接口定义数据结构
interface CreatePartyShareProps {
  partyId: PartyId;
  sessionId: SessionId;
  shareType: PartyShareType;
  shareData: ShareData;
  publicKey: PublicKey;
  threshold: Threshold;
}

// 使用枚举定义常量
enum PartyShareStatus {
  ACTIVE = 'active',
  ROTATED = 'rotated',
  REVOKED = 'revoked',
}

// 使用类型别名简化复杂类型
type ShareFilters = {
  partyId?: string;
  status?: PartyShareStatus;
  shareType?: PartyShareType;
};

// 使用 readonly 保护不可变属性
class PartyShare {
  private readonly _id: ShareId;
  private readonly _createdAt: Date;
}

// 使用 private 前缀
private readonly _domainEvents: DomainEvent[] = [];

// 使用 getter 暴露属性
get id(): ShareId {
  return this._id;
}
```

### 错误处理

```typescript
// 领域层错误
export class DomainError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'DomainError';
  }
}

// 应用层错误
export class ApplicationError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly details?: any,
  ) {
    super(message);
    this.name = 'ApplicationError';
  }
}

// 使用错误
if (!share) {
  throw new ApplicationError('Share not found', 'SHARE_NOT_FOUND');
}
```

### 日志规范

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

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

  async doSomething() {
    this.logger.log('Starting operation');
    this.logger.debug(`Processing with params: ${JSON.stringify(params)}`);
    this.logger.warn('Potential issue detected');
    this.logger.error('Operation failed', error.stack);
  }
}
```

---

## 添加新功能指南

### 1. 添加新的 API 端点

#### Step 1: 创建 DTO

```typescript
// src/api/dto/request/new-feature.dto.ts
import { IsString, IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class NewFeatureDto {
  @ApiProperty({ description: 'Feature parameter' })
  @IsString()
  @IsNotEmpty()
  param: string;
}
```

#### Step 2: 创建 Command/Query

```typescript
// src/application/commands/new-feature/new-feature.command.ts
export class NewFeatureCommand {
  constructor(
    public readonly param: string,
  ) {}
}
```

#### Step 3: 创建 Handler

```typescript
// src/application/commands/new-feature/new-feature.handler.ts
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Injectable } from '@nestjs/common';
import { NewFeatureCommand } from './new-feature.command';

@Injectable()
@CommandHandler(NewFeatureCommand)
export class NewFeatureHandler implements ICommandHandler<NewFeatureCommand> {
  async execute(command: NewFeatureCommand): Promise<any> {
    // 实现业务逻辑
  }
}
```

#### Step 4: 更新 Application Service

```typescript
// src/application/services/mpc-party-application.service.ts
async newFeature(params: NewFeatureParams): Promise<ResultDto> {
  const command = new NewFeatureCommand(params.param);
  return this.commandBus.execute(command);
}
```

#### Step 5: 添加 Controller 端点

```typescript
// src/api/controllers/mpc-party.controller.ts
@Post('new-feature')
@ApiOperation({ summary: '新功能' })
@ApiResponse({ status: 200, description: 'Success' })
async newFeature(@Body() dto: NewFeatureDto) {
  return this.mpcPartyService.newFeature(dto);
}
```

#### Step 6: 编写测试

```typescript
// tests/unit/application/new-feature.handler.spec.ts
describe('NewFeatureHandler', () => {
  // ... 单元测试
});

// tests/integration/new-feature.spec.ts
describe('NewFeature (Integration)', () => {
  // ... 集成测试
});
```

### 2. 添加新的领域实体

#### Step 1: 定义实体

```typescript
// src/domain/entities/new-entity.entity.ts
import { DomainEvent } from '../events';

export class NewEntity {
  private readonly _id: EntityId;
  private readonly _domainEvents: DomainEvent[] = [];

  private constructor(props: NewEntityProps) {
    this._id = props.id;
  }

  static create(props: CreateNewEntityProps): NewEntity {
    const entity = new NewEntity({
      id: EntityId.generate(),
      ...props,
    });

    entity.addDomainEvent(new NewEntityCreatedEvent(entity.id.value));
    return entity;
  }

  static reconstruct(props: NewEntityProps): NewEntity {
    return new NewEntity(props);
  }

  // Getters
  get id(): EntityId {
    return this._id;
  }

  get domainEvents(): DomainEvent[] {
    return [...this._domainEvents];
  }

  // Business methods
  doSomething(): void {
    // 业务逻辑
    this.addDomainEvent(new SomethingDoneEvent(this._id.value));
  }

  // Private methods
  private addDomainEvent(event: DomainEvent): void {
    this._domainEvents.push(event);
  }

  clearDomainEvents(): void {
    this._domainEvents.length = 0;
  }
}
```

#### Step 2: 定义值对象

```typescript
// src/domain/value-objects/entity-id.ts
export class EntityId {
  private constructor(private readonly _value: string) {}

  static create(value: string): EntityId {
    if (!this.isValid(value)) {
      throw new DomainError('Invalid EntityId format');
    }
    return new EntityId(value);
  }

  static generate(): EntityId {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(2, 15);
    return new EntityId(`entity_${timestamp}_${random}`);
  }

  private static isValid(value: string): boolean {
    return /^entity_\d+_[a-z0-9]+$/.test(value);
  }

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

  equals(other: EntityId): boolean {
    return this._value === other._value;
  }
}
```

#### Step 3: 定义仓储接口

```typescript
// src/domain/repositories/new-entity.repository.interface.ts
import { NewEntity } from '../entities/new-entity.entity';
import { EntityId } from '../value-objects';

export const NEW_ENTITY_REPOSITORY = Symbol('NEW_ENTITY_REPOSITORY');

export interface NewEntityRepository {
  save(entity: NewEntity): Promise<void>;
  findById(id: EntityId): Promise<NewEntity | null>;
  findMany(filters?: any): Promise<NewEntity[]>;
  delete(id: EntityId): Promise<void>;
}
```

#### Step 4: 实现仓储

```typescript
// src/infrastructure/persistence/repositories/new-entity.repository.impl.ts
@Injectable()
export class NewEntityRepositoryImpl implements NewEntityRepository {
  constructor(
    private readonly prisma: PrismaService,
    private readonly mapper: NewEntityMapper,
  ) {}

  async save(entity: NewEntity): Promise<void> {
    const data = this.mapper.toPersistence(entity);
    await this.prisma.newEntity.create({ data });
  }

  async findById(id: EntityId): Promise<NewEntity | null> {
    const record = await this.prisma.newEntity.findUnique({
      where: { id: id.value },
    });
    return record ? this.mapper.toDomain(record) : null;
  }
}
```

### 3. 添加新的外部服务集成

```typescript
// src/infrastructure/external/new-service/new-service.client.ts
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class NewServiceClient {
  private readonly logger = new Logger(NewServiceClient.name);
  private readonly baseUrl: string;

  constructor(private readonly configService: ConfigService) {
    this.baseUrl = this.configService.get<string>('NEW_SERVICE_URL');
  }

  async callExternalService(params: any): Promise<any> {
    this.logger.log(`Calling external service: ${JSON.stringify(params)}`);

    try {
      // 实现外部服务调用
      const response = await fetch(`${this.baseUrl}/endpoint`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(params),
      });

      if (!response.ok) {
        throw new Error(`External service error: ${response.status}`);
      }

      return response.json();
    } catch (error) {
      this.logger.error(`External service failed: ${error.message}`, error.stack);
      throw error;
    }
  }
}
```

---

## 调试技巧

### 1. VSCode 调试配置

`.vscode/launch.json`:

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug MPC Service",
      "runtimeArgs": [
        "-r",
        "ts-node/register",
        "-r",
        "tsconfig-paths/register"
      ],
      "args": ["${workspaceFolder}/src/main.ts"],
      "cwd": "${workspaceFolder}",
      "env": {
        "NODE_ENV": "development"
      },
      "sourceMaps": true
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Tests",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": [
        "--config",
        "${workspaceFolder}/tests/jest.config.js",
        "--runInBand",
        "--testPathPattern",
        "${file}"
      ],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}
```

### 2. 日志调试

```typescript
// 启用详细日志
const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn', 'log', 'debug', 'verbose'],
});

// 在代码中添加调试日志
this.logger.debug(`Debug info: ${JSON.stringify(data)}`);
this.logger.verbose(`Verbose details: ${details}`);
```

### 3. 数据库调试

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

# 使用 Prisma Studio 查看数据
npx prisma studio
```

### 4. API 调试

- 使用 Swagger UI: `http://localhost:3006/api/docs`
- 使用 Postman 或 Insomnia
- 查看请求/响应日志

---

## 性能优化

### 1. 数据库优化

```typescript
// 使用索引
@@index([partyId, status])
@@index([publicKey])

// 使用 select 限制字段
const share = await this.prisma.partyShare.findUnique({
  where: { id },
  select: {
    id: true,
    status: true,
    publicKey: true,
    // 不选择大字段如 shareData
  },
});

// 使用分页
const shares = await this.prisma.partyShare.findMany({
  skip: (page - 1) * limit,
  take: limit,
  orderBy: { createdAt: 'desc' },
});
```

### 2. 缓存优化

```typescript
// 使用 Redis 缓存
@Injectable()
export class CachedShareService {
  constructor(private readonly redis: Redis) {}

  async getShareInfo(shareId: string): Promise<ShareInfo> {
    const cacheKey = `share:${shareId}`;

    // 尝试从缓存获取
    const cached = await this.redis.get(cacheKey);
    if (cached) {
      return JSON.parse(cached);
    }

    // 从数据库获取
    const share = await this.repository.findById(shareId);

    // 存入缓存
    await this.redis.setex(cacheKey, 3600, JSON.stringify(share));

    return share;
  }
}
```

### 3. 异步处理

```typescript
// 使用异步端点处理长时间操作
@Post('keygen/participate')
@HttpCode(HttpStatus.ACCEPTED)
async participateInKeygen(@Body() dto: ParticipateKeygenDto) {
  // 立即返回，后台异步处理
  this.mpcPartyService.participateInKeygen(dto).catch(error => {
    this.logger.error(`Keygen failed: ${error.message}`);
  });

  return {
    message: 'Keygen participation started',
    sessionId: dto.sessionId,
  };
}
```

---

## 常见问题解决

### 1. Prisma 相关

**问题**: Prisma Client 未生成

```bash
npx prisma generate
```

**问题**: 数据库连接失败

检查 `DATABASE_URL` 环境变量格式：
```
mysql://user:password@host:port/database
```

### 2. 测试相关

**问题**: 测试超时

```javascript
// 增加超时时间
jest.setTimeout(60000);
```

**问题**: 模拟不生效

确保模拟在正确的位置：
```typescript
beforeEach(() => {
  jest.clearAllMocks();
  mockService.method.mockResolvedValue(expectedValue);
});
```

### 3. TypeScript 相关

**问题**: 路径别名不工作

检查 `tsconfig.json`:
```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
```

**问题**: 类型错误

```bash
# 重新生成类型
npm run build
npx prisma generate
```

### 4. 运行时相关

**问题**: 端口已被占用

```bash
# 查找占用端口的进程
lsof -i :3006
# 或在 Windows 上
netstat -ano | findstr :3006

# 终止进程
kill <PID>
```

**问题**: 内存不足

```bash
# 增加 Node.js 内存限制
NODE_OPTIONS=--max-old-space-size=4096 npm run start:dev
```

---

## 代码审查清单

在提交 PR 前，请检查：

- [ ] 代码遵循项目规范
- [ ] 所有测试通过
- [ ] 新功能有相应的测试
- [ ] 更新了相关文档
- [ ] 没有硬编码的敏感信息
- [ ] 日志级别适当
- [ ] 错误处理完善
- [ ] 没有引入新的安全漏洞
