WARNING: This commit contains code with significant issues that need immediate attention: 1. Type Safety Issues: - Unused import ZulipAccountsService causing compilation warnings - Implicit 'any' type in formatZulipAccount method parameter - Type inconsistencies in service injections 2. Service Integration Problems: - Inconsistent service interface usage - Missing proper type definitions for injected services - Potential runtime errors due to type mismatches 3. Code Quality Issues: - Violation of TypeScript strict mode requirements - Inconsistent error handling patterns - Missing proper interface implementations Files affected: - src/business/admin/database_management.service.ts (main issue) - Multiple test files and service implementations - Configuration and documentation updates Next steps required: 1. Fix TypeScript compilation errors 2. Implement proper type safety 3. Resolve service injection inconsistencies 4. Add comprehensive error handling 5. Update tests to match new implementations Impact: High - affects admin functionality and system stability Priority: Urgent - requires immediate review and fixes Author: moyin Date: 2026-01-10
1650 lines
54 KiB
Markdown
1650 lines
54 KiB
Markdown
# 开发者代码检查规范 - Whale Town 游戏服务器
|
||
|
||
## 📖 概述
|
||
|
||
本文档为Whale Town游戏服务器开发者提供全面的代码检查规范,确保代码质量、可维护性和团队协作效率。规范针对NestJS游戏服务器的双模式架构、实时通信、属性测试等特点进行了专门优化。
|
||
|
||
## 🎯 检查流程
|
||
|
||
代码检查分为6个步骤,建议按顺序执行:
|
||
|
||
1. **命名规范检查** - 文件、变量、函数、类的命名规范
|
||
2. **注释规范检查** - 文件头、类、方法注释的完整性
|
||
3. **代码质量检查** - 代码清洁度、性能优化
|
||
4. **架构分层检查** - 分层架构的合规性
|
||
5. **测试覆盖检查** - 测试文件的完整性和覆盖率
|
||
6. **功能文档生成** - README文档的生成和维护
|
||
|
||
---
|
||
|
||
## 1️⃣ 命名规范检查
|
||
|
||
### 📁 文件和文件夹命名
|
||
|
||
**核心规则:使用下划线分隔(snake_case),保持项目一致性**
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
- user_controller.ts
|
||
- admin_operation_log_service.ts
|
||
- location_broadcast_gateway.ts
|
||
- websocket_auth_guard.ts
|
||
- src/business/user_mgmt/
|
||
- src/core/location_broadcast_core/
|
||
|
||
❌ 错误示例:
|
||
- UserController.ts # 大驼峰命名
|
||
- user-service.ts # 短横线分隔
|
||
- adminOperationLog.service.ts # 小驼峰命名
|
||
- src/Business/Auth/ # 大驼峰命名
|
||
```
|
||
|
||
**⚠️ 特别注意:保持项目现有的下划线命名风格,确保代码库一致性!**
|
||
|
||
**游戏服务器特殊文件类型:**
|
||
```typescript
|
||
✅ 游戏服务器专用文件类型:
|
||
- location_broadcast.gateway.ts # WebSocket网关
|
||
- users_memory.service.ts # 内存模式服务
|
||
- file_redis.service.ts # 文件模式Redis
|
||
- admin.property.spec.ts # 属性测试
|
||
- zulip_integration.e2e.spec.ts # E2E测试
|
||
- performance_monitor.middleware.ts # 性能监控中间件
|
||
- websocket_docs.controller.ts # WebSocket文档控制器
|
||
```
|
||
|
||
### 🏗️ 文件夹结构优化
|
||
|
||
**避免过度嵌套,减少单文件文件夹**
|
||
|
||
```typescript
|
||
❌ 错误:过度嵌套
|
||
src/
|
||
guards/
|
||
auth.guard.ts # 只有一个文件,不需要单独文件夹
|
||
interceptors/
|
||
logging.interceptor.ts # 只有一个文件,不需要单独文件夹
|
||
|
||
✅ 正确:扁平化结构
|
||
src/
|
||
auth.guard.ts
|
||
logging.interceptor.ts
|
||
```
|
||
|
||
**文件夹创建判断标准:**
|
||
- 不超过3个文件:移到上级目录(扁平化)
|
||
- 4个以上文件:可以保持独立文件夹
|
||
- 完整功能模块:即使文件较少也可以保持独立(需特殊说明)
|
||
- **游戏服务器特殊考虑**:
|
||
- WebSocket相关文件可以独立成文件夹(实时通信复杂性)
|
||
- 双模式服务文件建议放在同一文件夹(便于对比)
|
||
- 属性测试文件较多的模块可以保持独立结构
|
||
|
||
**检查方法(重要):**
|
||
1. **必须使用工具详细检查**:不能凭印象判断文件夹内容
|
||
2. **逐个统计文件数量**:使用`listDirectory(path, depth=2)`获取准确数据
|
||
3. **识别单文件文件夹**:只有1个文件的文件夹必须扁平化
|
||
4. **更新引用路径**:移动文件后必须更新所有import语句
|
||
5. **考虑游戏服务器特殊性**:实时通信、双模式、测试复杂度
|
||
|
||
**常见检查错误:**
|
||
- ❌ 只看到文件夹存在就认为结构合理
|
||
- ❌ 没有统计每个文件夹的文件数量
|
||
- ❌ 凭印象判断而不使用工具验证
|
||
- ❌ 遗漏单文件文件夹的识别
|
||
|
||
**正确检查流程:**
|
||
1. 使用listDirectory工具查看详细结构
|
||
2. 逐个文件夹统计文件数量
|
||
3. 识别需要扁平化的文件夹(≤3个文件)
|
||
4. 考虑游戏服务器特殊性(WebSocket、双模式、测试复杂度)
|
||
5. 执行文件移动和路径更新操作
|
||
|
||
### 🔤 变量和函数命名
|
||
|
||
**规则:小驼峰命名(camelCase)**
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
const userName = 'Alice';
|
||
function getUserInfo() { }
|
||
async function validateUser() { }
|
||
const isGameStarted = false;
|
||
|
||
❌ 错误示例:
|
||
const UserName = 'Alice';
|
||
function GetUserInfo() { }
|
||
const is_game_started = false;
|
||
```
|
||
### 🏷️ 类和接口命名
|
||
|
||
**规则:大驼峰命名(PascalCase)**
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
class UserService { }
|
||
interface GameConfig { }
|
||
class CreateUserDto { }
|
||
enum UserStatus { }
|
||
|
||
❌ 错误示例:
|
||
class userService { }
|
||
interface gameConfig { }
|
||
class createUserDto { }
|
||
```
|
||
|
||
### 📊 常量命名
|
||
|
||
**规则:全大写 + 下划线分隔(SCREAMING_SNAKE_CASE)**
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
const PORT = 3000;
|
||
const MAX_PLAYERS = 10;
|
||
const SALT_ROUNDS = 10;
|
||
const DEFAULT_TIMEOUT = 5000;
|
||
|
||
❌ 错误示例:
|
||
const port = 3000;
|
||
const maxPlayers = 10;
|
||
const saltRounds = 10;
|
||
```
|
||
|
||
### 🛣️ 路由命名
|
||
|
||
**规则:全小写 + 短横线分隔(kebab-case)**
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
@Get('user/get-info')
|
||
@Post('room/join-room')
|
||
@Put('player/update-position')
|
||
@WebSocketGateway({ path: '/location-broadcast' }) # WebSocket路径
|
||
@MessagePattern('user-position-update') # 消息模式
|
||
|
||
❌ 错误示例:
|
||
@Get('user/getInfo')
|
||
@Post('room/joinRoom')
|
||
@Put('player/update_position')
|
||
```
|
||
|
||
---
|
||
|
||
## 2️⃣ 注释规范检查
|
||
|
||
### 📄 文件头注释
|
||
|
||
**必须包含的信息:**
|
||
|
||
```typescript
|
||
/**
|
||
* 文件功能描述
|
||
*
|
||
* 功能描述:
|
||
* - 主要功能点1
|
||
* - 主要功能点2
|
||
* - 主要功能点3
|
||
*
|
||
* 职责分离:
|
||
* - 职责描述1
|
||
* - 职责描述2
|
||
*
|
||
* 最近修改:
|
||
* - 2024-01-07: 代码规范优化 - 修复命名规范问题 (修改者: 张三)
|
||
* - 2024-01-06: 功能新增 - 添加用户验证功能 (修改者: 李四)
|
||
*
|
||
* @author 原始作者名称
|
||
* @version 1.0.1
|
||
* @since 2024-01-01
|
||
* @lastModified 2024-01-07
|
||
*/
|
||
```
|
||
|
||
### 🏛️ 类注释
|
||
|
||
**必须包含的信息:**
|
||
|
||
```typescript
|
||
/**
|
||
* 类功能描述
|
||
*
|
||
* 职责:
|
||
* - 主要职责1
|
||
* - 主要职责2
|
||
*
|
||
* 主要方法:
|
||
* - method1() - 方法1功能
|
||
* - method2() - 方法2功能
|
||
*
|
||
* 使用场景:
|
||
* - 场景描述
|
||
*/
|
||
@Injectable()
|
||
export class ExampleService {
|
||
// 类实现
|
||
}
|
||
```
|
||
|
||
### 🔧 方法注释(三级标准)
|
||
|
||
**必须包含的信息:**
|
||
|
||
```typescript
|
||
/**
|
||
* 用户登录验证
|
||
*
|
||
* 业务逻辑:
|
||
* 1. 验证用户名或邮箱格式
|
||
* 2. 查找用户记录
|
||
* 3. 验证密码哈希值
|
||
* 4. 检查用户状态是否允许登录
|
||
* 5. 记录登录日志
|
||
* 6. 返回认证结果
|
||
*
|
||
* @param loginRequest 登录请求数据
|
||
* @returns 认证结果,包含用户信息和认证状态
|
||
* @throws UnauthorizedException 用户名或密码错误时
|
||
* @throws ForbiddenException 用户状态不允许登录时
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* const result = await loginService.validateUser({
|
||
* identifier: 'user@example.com',
|
||
* password: 'password123'
|
||
* });
|
||
* ```
|
||
*/
|
||
async validateUser(loginRequest: LoginRequest): Promise<AuthResult> {
|
||
// 实现代码
|
||
}
|
||
```
|
||
|
||
### 📝 修改记录规范
|
||
|
||
**修改类型定义:**
|
||
- `代码规范优化` - 命名规范、注释规范、代码清理等
|
||
- `功能新增` - 添加新的功能或方法
|
||
- `功能修改` - 修改现有功能的实现
|
||
- `Bug修复` - 修复代码缺陷
|
||
- `性能优化` - 提升代码性能
|
||
- `重构` - 代码结构调整但功能不变
|
||
|
||
**格式要求:**
|
||
```typescript
|
||
/**
|
||
* 最近修改:
|
||
* - 2024-01-07: 代码规范优化 - 清理未使用的导入 (修改者: 张三)
|
||
* - 2024-01-06: Bug修复 - 修复邮箱验证逻辑错误 (修改者: 李四)
|
||
* - 2024-01-05: 功能新增 - 添加用户验证码登录功能 (修改者: 王五)
|
||
*
|
||
* @version 1.0.1
|
||
* @lastModified 2024-01-07
|
||
*/
|
||
```
|
||
|
||
**作者字段处理规范:**
|
||
- **保留原则**:@author字段中的人名必须保留,不得随意修改
|
||
- **AI标识替换**:只有当@author字段包含AI标识(如kiro、ChatGPT、Claude、AI等)时,才可以替换为实际的修改者名称
|
||
- **判断标准**:
|
||
- ✅ 可以替换:`@author kiro` → `@author 张三`
|
||
- ✅ 可以替换:`@author ChatGPT` → `@author 李四`
|
||
- ❌ 不可替换:`@author 王五` → 必须保留为 `@author 王五`
|
||
- ❌ 不可替换:`@author John Smith` → 必须保留为 `@author John Smith`
|
||
|
||
**修改记录更新要求:**
|
||
- **必须添加**:每次修改文件后,必须在"最近修改"部分添加新的修改记录
|
||
- **信息完整**:包含修改日期、修改类型、修改内容、修改者姓名
|
||
- **时间更新规则**:
|
||
- **仅检查不修改**:如果只是进行代码检查而没有实际修改文件内容,不更新@lastModified字段
|
||
- **实际修改才更新**:只有真正修改了文件内容(功能代码、注释内容、结构调整等)时才更新@lastModified字段
|
||
- **检查规范强调**:注释规范检查本身不是修改,除非发现需要修正的问题并进行了实际修改
|
||
- **Git变更检测**:通过git status和git diff检查文件是否有实际变更,只有git显示文件被修改时才需要添加修改记录和更新时间戳
|
||
- **版本递增**:根据修改类型适当递增版本号
|
||
|
||
**版本号递增规则:**
|
||
- 代码规范优化、Bug修复 → 修订版本 +1 (1.0.0 → 1.0.1)
|
||
- 功能新增、功能修改 → 次版本 +1 (1.0.1 → 1.1.0)
|
||
- 重构、架构变更 → 主版本 +1 (1.1.0 → 2.0.0)
|
||
|
||
---
|
||
|
||
## 3️⃣ 代码质量检查
|
||
|
||
### 🧹 导入清理
|
||
|
||
**清理未使用的导入:**
|
||
|
||
```typescript
|
||
// ✅ 正确:只导入使用的模块
|
||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||
import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
|
||
import { Server } from 'socket.io';
|
||
|
||
// ❌ 错误:导入未使用的模块
|
||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||
import { User, Admin } from './user.entity';
|
||
import * as crypto from 'crypto'; // 未使用
|
||
import { RedisService } from '../redis/redis.service'; // 未使用
|
||
```
|
||
|
||
**游戏服务器特殊导入检查:**
|
||
```typescript
|
||
// 检查双模式服务导入
|
||
import { UsersService } from './users.service';
|
||
import { UsersMemoryService } from './users-memory.service'; // 确保两个都被使用
|
||
|
||
// 检查WebSocket相关导入
|
||
import { Server, Socket } from 'socket.io'; // 确保Socket类型被使用
|
||
import { WsException } from '@nestjs/websockets'; // 确保异常处理被使用
|
||
```
|
||
|
||
### 📊 常量定义检查
|
||
|
||
```typescript
|
||
// ✅ 正确:使用全大写+下划线
|
||
const SALT_ROUNDS = 10;
|
||
const MAX_LOGIN_ATTEMPTS = 5;
|
||
const DEFAULT_PAGE_SIZE = 20;
|
||
|
||
// ❌ 错误:使用小驼峰
|
||
const saltRounds = 10;
|
||
const maxLoginAttempts = 5;
|
||
```
|
||
|
||
### 🗑️ 未使用代码清理
|
||
|
||
```typescript
|
||
// ❌ 需要删除:未使用的私有方法
|
||
private generateVerificationCode(): string {
|
||
// 如果这个方法没有被调用,应该删除
|
||
}
|
||
|
||
// ❌ 需要删除:未使用的变量
|
||
const unusedVariable = 'test';
|
||
```
|
||
|
||
### 🚫 TODO项处理
|
||
|
||
**强制要求:最终文件不能包含TODO项**
|
||
|
||
```typescript
|
||
// ❌ 错误:包含TODO项的代码
|
||
async getUserProfile(id: string): Promise<UserProfile> {
|
||
// TODO: 实现用户档案查询
|
||
throw new Error('Not implemented');
|
||
}
|
||
|
||
// ❌ 游戏服务器常见TODO(需要处理)
|
||
async sendSmsVerification(phone: string): Promise<void> {
|
||
// TODO: 集成短信服务提供商
|
||
throw new Error('SMS service not implemented');
|
||
}
|
||
|
||
async cleanupOldPositions(): Promise<void> {
|
||
// TODO: 实现位置历史数据清理
|
||
console.log('Position cleanup not implemented');
|
||
}
|
||
|
||
// ✅ 正确:真正实现功能
|
||
async getUserProfile(id: string): Promise<UserProfile> {
|
||
const profile = await this.userProfileRepository.findOne({
|
||
where: { userId: id }
|
||
});
|
||
|
||
if (!profile) {
|
||
throw new NotFoundException('用户档案不存在');
|
||
}
|
||
|
||
return profile;
|
||
}
|
||
|
||
// ✅ 正确:游戏服务器实现示例
|
||
async broadcastPositionUpdate(userId: string, position: Position): Promise<void> {
|
||
const room = await this.getRoomByUserId(userId);
|
||
this.server.to(room.id).emit('position-update', {
|
||
userId,
|
||
position,
|
||
timestamp: Date.now()
|
||
});
|
||
|
||
// 记录位置历史(如果需要)
|
||
await this.savePositionHistory(userId, position);
|
||
}
|
||
```
|
||
|
||
**游戏服务器TODO处理优先级:**
|
||
- **高优先级**:实时通信功能、用户认证、数据持久化
|
||
- **中优先级**:性能优化、监控告警、数据清理
|
||
- **低优先级**:辅助功能、统计分析、第三方集成
|
||
|
||
**TODO处理原则:**
|
||
- **真正实现**:如果功能需要,必须提供完整的实现
|
||
- **删除代码**:如果功能不需要,删除相关方法和接口
|
||
- **分阶段实现**:如果功能复杂,可以分多个版本实现,但每个版本都不能有TODO
|
||
- **文档说明**:如果某些功能暂不实现,在README中说明原因和计划
|
||
|
||
### 📏 方法长度检查
|
||
|
||
```typescript
|
||
// ✅ 正确:方法长度合理(建议不超过50行)
|
||
async createUser(userData: CreateUserDto): Promise<User> {
|
||
// 简洁的实现
|
||
}
|
||
|
||
// ❌ 错误:方法过长,需要拆分
|
||
async complexMethod() {
|
||
// 超过50行的复杂逻辑,应该拆分成多个小方法
|
||
}
|
||
```
|
||
---
|
||
|
||
## 4️⃣ 架构分层检查
|
||
|
||
### 🏗️ 架构层级识别
|
||
|
||
**项目采用分层架构:**
|
||
|
||
```
|
||
src/
|
||
├── core/ # Core层:技术实现层
|
||
│ ├── db/ # 数据访问
|
||
│ ├── redis/ # 缓存服务
|
||
│ └── utils/ # 工具服务
|
||
├── business/ # Business层:业务逻辑层
|
||
│ ├── auth/ # 认证业务
|
||
│ ├── users/ # 用户业务
|
||
│ └── admin/ # 管理业务
|
||
└── common/ # 公共层:通用组件
|
||
```
|
||
|
||
### 🔧 Core层规范
|
||
|
||
**职责:专注技术实现,不包含业务逻辑**
|
||
|
||
#### 命名规范
|
||
- **检查范围**:仅检查当前执行检查的文件夹,不考虑其他同层功能模块
|
||
- **业务支撑模块**:专门为特定业务功能提供技术支撑,使用`_core`后缀(如`location_broadcast_core`、`admin_core`)
|
||
- **通用工具模块**:提供可复用的数据访问或基础技术服务,不使用`_core`后缀(如`user_profiles`、`redis`、`logger`)
|
||
|
||
**游戏服务器Core层特殊模块:**
|
||
```typescript
|
||
✅ 正确示例:
|
||
src/core/location_broadcast_core/ # 专门为位置广播业务提供技术支撑
|
||
src/core/admin_core/ # 专门为管理员业务提供技术支撑
|
||
src/core/zulip_core/ # 专门为Zulip集成提供技术支撑
|
||
src/core/login_core/ # 专门为登录认证提供技术支撑
|
||
src/core/security_core/ # 专门为安全功能提供技术支撑
|
||
src/core/db/user_profiles/ # 通用的用户档案数据访问服务
|
||
src/core/redis/ # 通用的Redis技术封装
|
||
src/core/utils/logger/ # 通用的日志工具服务
|
||
|
||
❌ 错误示例:
|
||
src/core/location_broadcast/ # 应该是location_broadcast_core
|
||
src/core/db/user_profiles_core/ # 应该是user_profiles(通用工具)
|
||
src/core/redis_core/ # 应该是redis(通用工具)
|
||
```
|
||
|
||
**判断流程:**
|
||
```
|
||
1. 模块是否专门为某个特定业务功能服务?
|
||
├─ 是 → 检查模块名称是否体现业务领域
|
||
│ ├─ 是 → 使用 _core 后缀 (如: location_broadcast_core)
|
||
│ └─ 否 → 重新设计模块职责
|
||
└─ 否 → 模块是否提供通用的技术服务?
|
||
├─ 是 → 不使用 _core 后缀 (如: user_profiles, redis)
|
||
└─ 否 → 重新评估模块定位
|
||
|
||
2. 实际案例判断:
|
||
- user_profiles: 通用的用户档案数据访问 → 不使用后缀 ✓
|
||
- location_broadcast_core: 专门为位置广播业务服务 → 使用_core后缀 ✓
|
||
- redis: 通用的缓存技术服务 → 不使用后缀 ✓
|
||
- user_auth_core: 专门为用户认证业务服务 → 使用_core后缀 ✓
|
||
```
|
||
|
||
```typescript
|
||
✅ 正确示例:
|
||
src/core/location_broadcast_core/ # 专门为位置广播业务提供技术支撑
|
||
src/core/user_auth_core/ # 专门为用户认证业务提供技术支撑
|
||
src/core/db/user_profiles/ # 通用的用户档案数据访问服务
|
||
src/core/redis/ # 通用的Redis技术封装
|
||
src/core/utils/logger/ # 通用的日志工具服务
|
||
|
||
❌ 错误示例:
|
||
src/core/location_broadcast/ # 应该是location_broadcast_core
|
||
src/core/db/user_profiles_core/ # 应该是user_profiles(通用工具)
|
||
src/core/redis_core/ # 应该是redis(通用工具)
|
||
```
|
||
|
||
#### 技术实现示例
|
||
```typescript
|
||
// ✅ 正确:Core层专注技术实现
|
||
@Injectable()
|
||
export class LocationBroadcastCoreService {
|
||
/**
|
||
* 广播位置更新到指定房间
|
||
*
|
||
* 技术实现:
|
||
* 1. 验证WebSocket连接状态
|
||
* 2. 序列化位置数据
|
||
* 3. 通过Socket.IO广播消息
|
||
* 4. 记录广播性能指标
|
||
* 5. 处理广播异常和重试
|
||
*/
|
||
async broadcastToRoom(roomId: string, data: PositionData): Promise<void> {
|
||
// 专注WebSocket技术实现细节
|
||
const room = this.server.sockets.adapter.rooms.get(roomId);
|
||
if (!room) {
|
||
throw new NotFoundException(`Room ${roomId} not found`);
|
||
}
|
||
|
||
this.server.to(roomId).emit('position-update', data);
|
||
this.metricsService.recordBroadcast(roomId, data.userId);
|
||
}
|
||
}
|
||
|
||
// ❌ 错误:Core层包含业务逻辑
|
||
@Injectable()
|
||
export class LocationBroadcastCoreService {
|
||
async broadcastUserPosition(userId: string, position: Position): Promise<void> {
|
||
// 错误:包含了用户权限检查的业务概念
|
||
const user = await this.userService.findById(userId);
|
||
if (user.status !== UserStatus.ACTIVE) {
|
||
throw new ForbiddenException('用户状态不允许位置广播');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 依赖关系
|
||
- ✅ 允许:导入其他Core层模块
|
||
- ✅ 允许:导入第三方技术库
|
||
- ✅ 允许:导入Node.js内置模块
|
||
- ❌ 禁止:导入Business层模块
|
||
- ❌ 禁止:包含具体业务概念的命名
|
||
|
||
### 💼 Business层规范
|
||
|
||
**职责:专注业务逻辑实现,不关心底层技术细节**
|
||
|
||
#### 业务逻辑完备性
|
||
```typescript
|
||
// ✅ 正确:完整的业务逻辑
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
/**
|
||
* 用户注册业务流程
|
||
*
|
||
* 业务逻辑:
|
||
* 1. 验证用户信息完整性
|
||
* 2. 检查用户名/邮箱是否已存在
|
||
* 3. 验证邮箱格式和域名白名单
|
||
* 4. 生成用户唯一标识
|
||
* 5. 设置默认用户权限
|
||
* 6. 发送欢迎邮件
|
||
* 7. 记录注册日志
|
||
* 8. 返回注册结果
|
||
*/
|
||
async registerUser(registerData: RegisterUserDto): Promise<UserResult> {
|
||
// 完整的业务逻辑实现
|
||
}
|
||
}
|
||
|
||
// ❌ 错误:业务逻辑不完整
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
async registerUser(registerData: RegisterUserDto): Promise<User> {
|
||
// 只是简单调用数据库保存,缺少业务验证和流程
|
||
return this.userRepository.save(registerData);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 依赖关系
|
||
- ✅ 允许:导入对应的Core层业务支撑模块
|
||
- ✅ 允许:导入Core层通用工具模块
|
||
- ✅ 允许:导入其他Business层模块(谨慎使用)
|
||
- ✅ 允许:导入第三方业务库
|
||
- ❌ 禁止:直接导入底层技术实现(如数据库连接、Redis客户端等)
|
||
- ❌ 禁止:包含技术实现细节
|
||
|
||
#### 正确的分层实现
|
||
```typescript
|
||
// ✅ 正确:Business层调用Core层服务
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
constructor(
|
||
private readonly userCoreService: UserCoreService,
|
||
private readonly cacheService: CacheService,
|
||
private readonly emailService: EmailService,
|
||
) {}
|
||
|
||
async createUser(userData: CreateUserDto): Promise<User> {
|
||
// 业务验证
|
||
await this.validateUserBusinessRules(userData);
|
||
|
||
// 调用Core层服务
|
||
const user = await this.userCoreService.create(userData);
|
||
await this.cacheService.set(`user:${user.id}`, user);
|
||
await this.emailService.sendWelcomeEmail(user.email);
|
||
|
||
return user;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 🔍 常见架构违规
|
||
|
||
#### Business层违规示例
|
||
```typescript
|
||
// ❌ 错误:Business层包含技术实现细节
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
async createUser(userData: CreateUserDto): Promise<User> {
|
||
// 违规:直接操作Redis连接
|
||
const redis = new Redis({ host: 'localhost', port: 6379 });
|
||
await redis.set(`user:${userData.id}`, JSON.stringify(userData));
|
||
|
||
// 违规:直接写SQL语句
|
||
const sql = 'INSERT INTO users (name, email) VALUES (?, ?)';
|
||
await this.database.query(sql, [userData.name, userData.email]);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Core层违规示例
|
||
```typescript
|
||
// ❌ 错误:Core层包含业务逻辑
|
||
@Injectable()
|
||
export class DatabaseService {
|
||
async saveUser(userData: CreateUserDto): Promise<User> {
|
||
// 违规:包含用户注册的业务验证
|
||
if (userData.age < 18) {
|
||
throw new BadRequestException('用户年龄必须大于18岁');
|
||
}
|
||
|
||
// 违规:包含业务规则
|
||
if (userData.email.endsWith('@competitor.com')) {
|
||
throw new ForbiddenException('不允许竞争对手注册');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5️⃣ 测试覆盖检查
|
||
|
||
### 📋 测试文件存在性
|
||
|
||
**规则:每个Service、Controller、Gateway都必须有对应的测试文件**
|
||
|
||
**⚠️ 游戏服务器测试要求(重要):**
|
||
以下类型需要测试文件:
|
||
- ✅ **Service类**:文件名包含`.service.ts`的业务逻辑类
|
||
- ✅ **Controller类**:文件名包含`.controller.ts`的控制器类
|
||
- ✅ **Gateway类**:文件名包含`.gateway.ts`的WebSocket网关类
|
||
- ✅ **Guard类**:文件名包含`.guard.ts`的守卫类(游戏服务器安全重要)
|
||
- ✅ **Interceptor类**:文件名包含`.interceptor.ts`的拦截器类(日志监控重要)
|
||
- ✅ **Middleware类**:文件名包含`.middleware.ts`的中间件类(性能监控重要)
|
||
|
||
**❌ 以下类型不需要测试文件:**
|
||
- ❌ **DTO类**:数据传输对象(`.dto.ts`)不需要测试文件
|
||
- ❌ **Interface文件**:接口定义(`.interface.ts`)不需要测试文件
|
||
- ❌ **简单Utils工具类**:简单工具函数(`.utils.ts`)不需要测试文件
|
||
- ❌ **Config文件**:配置文件(`.config.ts`)不需要测试文件
|
||
- ❌ **Constants文件**:常量定义(`.constants.ts`)不需要测试文件
|
||
|
||
**🔥 测试代码检查严格要求(新增):**
|
||
|
||
#### 1. 严格一对一映射原则
|
||
- **强制要求**:每个测试文件必须严格对应一个源文件,属于严格一对一关系
|
||
- **禁止多对一**:不允许一个测试文件测试多个源文件的功能
|
||
- **禁止一对多**:不允许一个源文件的测试分散在多个测试文件中
|
||
- **命名对应**:测试文件名必须与源文件名完全对应(除.spec.ts后缀外)
|
||
|
||
```typescript
|
||
// ✅ 正确:严格一对一映射
|
||
src/business/auth/login.service.ts
|
||
src/business/auth/login.service.spec.ts
|
||
|
||
src/core/location_broadcast_core/location_broadcast_core.service.ts
|
||
src/core/location_broadcast_core/location_broadcast_core.service.spec.ts
|
||
|
||
// ❌ 错误:一个测试文件测试多个源文件
|
||
src/business/auth/auth_services.spec.ts # 测试多个service,违反一对一原则
|
||
|
||
// ❌ 错误:一个源文件的测试分散在多个文件
|
||
src/business/auth/login.service.spec.ts
|
||
src/business/auth/login_validation.spec.ts # 应该合并到login.service.spec.ts
|
||
```
|
||
|
||
#### 2. 测试范围严格限制
|
||
- **范围限制**:测试内容必须严格限于对应源文件的功能测试
|
||
- **禁止跨文件**:不允许在单元测试中测试其他文件的功能
|
||
- **依赖隔离**:使用Mock隔离外部依赖,专注测试当前文件
|
||
|
||
```typescript
|
||
// ✅ 正确:只测试LoginService的功能
|
||
// 文件:src/business/auth/login.service.spec.ts
|
||
describe('LoginService', () => {
|
||
describe('validateUser', () => {
|
||
it('should validate user credentials', () => {
|
||
// 只测试LoginService.validateUser方法
|
||
// 使用Mock隔离UserRepository等外部依赖
|
||
});
|
||
});
|
||
});
|
||
|
||
// ❌ 错误:在LoginService测试中测试其他服务
|
||
describe('LoginService', () => {
|
||
it('should integrate with UserRepository', () => {
|
||
// 错误:这是集成测试,应该移到test/integration/
|
||
});
|
||
|
||
it('should work with EmailService', () => {
|
||
// 错误:测试了EmailService的功能,违反范围限制
|
||
});
|
||
});
|
||
```
|
||
|
||
#### 3. 集成测试强制分离
|
||
- **强制分离**:所有集成测试必须从单元测试文件中移除
|
||
- **统一位置**:集成测试统一放在顶层`test/integration/`目录
|
||
- **最后执行**:集成测试在所有单元测试通过后统一执行
|
||
|
||
#### 4. 顶层test目录结构(强制要求)
|
||
```
|
||
test/
|
||
├── integration/ # 集成测试 - 测试多个模块间的交互
|
||
│ ├── auth_integration.spec.ts
|
||
│ ├── location_broadcast_integration.spec.ts
|
||
│ └── zulip_integration.spec.ts
|
||
├── e2e/ # 端到端测试 - 完整业务流程测试
|
||
│ ├── user_registration_e2e.spec.ts
|
||
│ ├── location_broadcast_e2e.spec.ts
|
||
│ └── admin_operations_e2e.spec.ts
|
||
├── performance/ # 性能测试 - WebSocket和高并发测试
|
||
│ ├── websocket_performance.spec.ts
|
||
│ ├── database_performance.spec.ts
|
||
│ └── memory_usage.spec.ts
|
||
├── property/ # 属性测试 - 基于属性的随机测试
|
||
│ ├── admin_property.spec.ts
|
||
│ ├── user_validation_property.spec.ts
|
||
│ └── position_update_property.spec.ts
|
||
└── fixtures/ # 测试数据和工具
|
||
├── test_data.ts
|
||
└── test_helpers.ts
|
||
```
|
||
|
||
**游戏服务器特殊测试要求:**
|
||
```typescript
|
||
// ✅ 必须有测试的文件类型
|
||
src/business/location-broadcast/location-broadcast.gateway.ts
|
||
src/business/location-broadcast/location-broadcast.gateway.spec.ts
|
||
|
||
src/core/security-core/websocket-auth.guard.ts
|
||
src/core/security-core/websocket-auth.guard.spec.ts
|
||
|
||
src/business/admin/performance-monitor.middleware.ts
|
||
src/business/admin/performance-monitor.middleware.spec.ts
|
||
|
||
// ❌ 不需要测试的文件类型
|
||
src/business/location-broadcast/dto/position-update.dto.ts # DTO不需要测试
|
||
src/core/location-broadcast-core/position.interface.ts # 接口不需要测试
|
||
src/business/admin/admin.constants.ts # 常量不需要测试
|
||
```
|
||
|
||
**测试文件位置规范(重要):**
|
||
- ✅ **正确位置**:测试文件必须与对应源文件放在同一目录
|
||
- ❌ **错误位置**:测试文件放在单独的tests/、test/、spec/、__tests__/等文件夹中
|
||
|
||
```typescript
|
||
// ✅ 正确:测试文件与源文件同目录
|
||
src/core/db/users/users.service.ts
|
||
src/core/db/users/users.service.spec.ts
|
||
|
||
src/business/admin/admin.service.ts
|
||
src/business/admin/admin.service.spec.ts
|
||
|
||
// ❌ 错误:测试文件在单独文件夹
|
||
src/business/admin/admin.service.ts
|
||
src/business/admin/tests/admin.service.spec.ts # 错误位置
|
||
src/business/admin/__tests__/admin.service.spec.ts # 错误位置
|
||
|
||
// ❌ 错误:缺少测试文件
|
||
src/core/login_core/login_core.service.ts
|
||
# 缺少:src/core/login_core/login_core.service.spec.ts
|
||
```
|
||
|
||
**扁平化要求:**
|
||
- **强制扁平化**:所有tests/、test/、spec/、__tests__/等测试专用文件夹必须扁平化
|
||
- **移动规则**:将测试文件移动到对应源文件的同一目录
|
||
- **更新引用**:移动后必须更新所有import路径引用
|
||
- **删除空文件夹**:移动完成后删除空的测试文件夹
|
||
|
||
### 🎯 测试用例覆盖完整性
|
||
|
||
**要求:测试文件必须覆盖Service中的所有公共方法**
|
||
|
||
```typescript
|
||
// 示例Service
|
||
@Injectable()
|
||
export class UserService {
|
||
async createUser(userData: CreateUserDto): Promise<User> { }
|
||
async findUserById(id: string): Promise<User> { }
|
||
async updateUser(id: string, updateData: UpdateUserDto): Promise<User> { }
|
||
async deleteUser(id: string): Promise<void> { }
|
||
async findUsersByStatus(status: UserStatus): Promise<User[]> { }
|
||
}
|
||
|
||
// ✅ 正确:完整的测试覆盖
|
||
describe('UserService', () => {
|
||
// 每个公共方法都有对应的测试
|
||
describe('createUser', () => {
|
||
it('should create user successfully', () => { });
|
||
it('should throw error when email already exists', () => { });
|
||
it('should throw error when required fields missing', () => { });
|
||
});
|
||
|
||
describe('findUserById', () => {
|
||
it('should return user when found', () => { });
|
||
it('should throw NotFoundException when user not found', () => { });
|
||
it('should throw error when id is invalid', () => { });
|
||
});
|
||
|
||
// ... 其他方法的测试
|
||
});
|
||
```
|
||
|
||
### 🧪 测试场景真实性
|
||
|
||
**要求:每个方法必须测试正常情况、异常情况和边界情况**
|
||
|
||
```typescript
|
||
// ✅ 正确:游戏服务器完整测试场景
|
||
describe('LocationBroadcastGateway', () => {
|
||
describe('handleConnection', () => {
|
||
// 正常情况
|
||
it('should accept valid WebSocket connection with JWT token', async () => {
|
||
const mockSocket = createMockSocket({ token: validJwtToken });
|
||
const result = await gateway.handleConnection(mockSocket);
|
||
expect(result).toBeTruthy();
|
||
expect(mockSocket.join).toHaveBeenCalledWith(expectedRoomId);
|
||
});
|
||
|
||
// 异常情况
|
||
it('should reject connection with invalid JWT token', async () => {
|
||
const mockSocket = createMockSocket({ token: 'invalid-token' });
|
||
expect(() => gateway.handleConnection(mockSocket)).toThrow(WsException);
|
||
});
|
||
|
||
// 边界情况
|
||
it('should handle connection when room is at capacity limit', async () => {
|
||
const mockSocket = createMockSocket({ token: validJwtToken });
|
||
jest.spyOn(gateway, 'getRoomMemberCount').mockResolvedValue(MAX_ROOM_CAPACITY);
|
||
|
||
expect(() => gateway.handleConnection(mockSocket))
|
||
.toThrow(new WsException('房间已满'));
|
||
});
|
||
});
|
||
|
||
describe('handlePositionUpdate', () => {
|
||
// 实时通信测试
|
||
it('should broadcast position to all room members', async () => {
|
||
const positionData = { x: 100, y: 200, timestamp: Date.now() };
|
||
await gateway.handlePositionUpdate(mockSocket, positionData);
|
||
|
||
expect(mockServer.to).toHaveBeenCalledWith(roomId);
|
||
expect(mockServer.emit).toHaveBeenCalledWith('position-update', {
|
||
userId: mockSocket.userId,
|
||
position: positionData
|
||
});
|
||
});
|
||
|
||
// 数据验证测试
|
||
it('should validate position data format', async () => {
|
||
const invalidPosition = { x: 'invalid', y: 200 };
|
||
|
||
expect(() => gateway.handlePositionUpdate(mockSocket, invalidPosition))
|
||
.toThrow(WsException);
|
||
});
|
||
});
|
||
});
|
||
|
||
// ✅ 双模式服务测试
|
||
describe('UsersService vs UsersMemoryService', () => {
|
||
it('should have identical behavior for user creation', async () => {
|
||
const userData = { name: 'Test User', email: 'test@example.com' };
|
||
|
||
const dbResult = await usersService.create(userData);
|
||
const memoryResult = await usersMemoryService.create(userData);
|
||
|
||
expect(dbResult).toMatchObject(memoryResult);
|
||
});
|
||
});
|
||
```
|
||
|
||
### 🏗️ 测试代码质量
|
||
|
||
**要求:测试代码必须清晰、可维护、真实有效**
|
||
|
||
```typescript
|
||
// ✅ 正确:游戏服务器高质量测试代码
|
||
describe('LocationBroadcastGateway', () => {
|
||
let gateway: LocationBroadcastGateway;
|
||
let mockServer: jest.Mocked<Server>;
|
||
let mockLocationService: jest.Mocked<LocationBroadcastCoreService>;
|
||
|
||
beforeEach(async () => {
|
||
const mockServer = {
|
||
to: jest.fn().mockReturnThis(),
|
||
emit: jest.fn(),
|
||
sockets: {
|
||
adapter: {
|
||
rooms: new Map()
|
||
}
|
||
}
|
||
};
|
||
|
||
const mockLocationService = {
|
||
broadcastToRoom: jest.fn(),
|
||
validatePosition: jest.fn(),
|
||
getRoomMembers: jest.fn()
|
||
};
|
||
|
||
const module: TestingModule = await Test.createTestingModule({
|
||
providers: [
|
||
LocationBroadcastGateway,
|
||
{ provide: 'SERVER', useValue: mockServer },
|
||
{ provide: LocationBroadcastCoreService, useValue: mockLocationService },
|
||
],
|
||
}).compile();
|
||
|
||
gateway = module.get<LocationBroadcastGateway>(LocationBroadcastGateway);
|
||
mockServer = module.get('SERVER');
|
||
mockLocationService = module.get(LocationBroadcastCoreService);
|
||
});
|
||
|
||
afterEach(() => {
|
||
jest.clearAllMocks();
|
||
});
|
||
|
||
describe('handlePositionUpdate', () => {
|
||
it('should broadcast valid position update to room members', async () => {
|
||
// Arrange
|
||
const mockSocket = createMockSocket({ userId: 'user123', roomId: 'room456' });
|
||
const positionData = { x: 100, y: 200, timestamp: Date.now() };
|
||
mockLocationService.validatePosition.mockResolvedValue(true);
|
||
mockLocationService.getRoomMembers.mockResolvedValue(['user123', 'user456']);
|
||
|
||
// Act
|
||
await gateway.handlePositionUpdate(mockSocket, positionData);
|
||
|
||
// Assert
|
||
expect(mockLocationService.validatePosition).toHaveBeenCalledWith(positionData);
|
||
expect(mockServer.to).toHaveBeenCalledWith('room456');
|
||
expect(mockServer.emit).toHaveBeenCalledWith('position-update', {
|
||
userId: 'user123',
|
||
position: positionData,
|
||
timestamp: expect.any(Number)
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
// ✅ 属性测试示例(管理员模块)
|
||
describe('AdminService Properties', () => {
|
||
it('should handle any valid user status update', () => {
|
||
fc.assert(fc.property(
|
||
fc.integer({ min: 1, max: 1000000 }), // userId
|
||
fc.constantFrom(...Object.values(UserStatus)), // status
|
||
async (userId, status) => {
|
||
// 属性:任何有效的用户状态更新都应该成功或抛出明确的异常
|
||
try {
|
||
const result = await adminService.updateUserStatus(userId, status);
|
||
expect(result).toBeDefined();
|
||
expect(result.status).toBe(status);
|
||
} catch (error) {
|
||
// 如果抛出异常,应该是已知的业务异常
|
||
expect(error).toBeInstanceOf(NotFoundException || BadRequestException);
|
||
}
|
||
}
|
||
));
|
||
});
|
||
});
|
||
```
|
||
|
||
### 🔗 集成测试
|
||
|
||
**要求:复杂Service需要集成测试文件(.integration.spec.ts)**
|
||
|
||
**⚠️ 重要变更:集成测试必须移动到顶层test目录**
|
||
|
||
```typescript
|
||
// ❌ 错误:集成测试放在源文件目录(旧做法)
|
||
src/core/location_broadcast_core/location_broadcast_core.service.ts
|
||
src/core/location_broadcast_core/location_broadcast_core.service.spec.ts # 单元测试
|
||
src/core/location_broadcast_core/location_broadcast_core.integration.spec.ts # 错误位置
|
||
|
||
// ✅ 正确:集成测试统一放在顶层test目录(新要求)
|
||
src/core/location_broadcast_core/location_broadcast_core.service.ts
|
||
src/core/location_broadcast_core/location_broadcast_core.service.spec.ts # 单元测试
|
||
test/integration/location_broadcast_core_integration.spec.ts # 正确位置
|
||
|
||
// ✅ 正确:其他类型测试的位置
|
||
test/e2e/zulip_integration_e2e.spec.ts # E2E测试
|
||
test/performance/websocket_performance.spec.ts # 性能测试
|
||
test/property/admin_property.spec.ts # 属性测试
|
||
```
|
||
|
||
**集成测试内容要求:**
|
||
- **模块间交互**:测试多个模块之间的协作
|
||
- **数据流验证**:验证数据在模块间的正确传递
|
||
- **依赖关系**:测试真实的依赖关系而非Mock
|
||
- **配置集成**:测试配置文件和环境变量的集成
|
||
|
||
**游戏服务器集成测试重点:**
|
||
```typescript
|
||
// test/integration/location_broadcast_integration.spec.ts
|
||
describe('LocationBroadcast Integration', () => {
|
||
it('should integrate gateway with core service and database', async () => {
|
||
// 测试Gateway -> CoreService -> Database的完整链路
|
||
});
|
||
|
||
it('should handle WebSocket connection with Redis session', async () => {
|
||
// 测试WebSocket连接与Redis会话管理的集成
|
||
});
|
||
});
|
||
|
||
// test/integration/zulip_integration.spec.ts
|
||
describe('Zulip Integration', () => {
|
||
it('should sync messages between game chat and Zulip', async () => {
|
||
// 测试游戏聊天与Zulip的消息同步集成
|
||
});
|
||
});
|
||
```
|
||
|
||
### ⚡ 测试执行
|
||
|
||
**游戏服务器推荐的测试命令:**
|
||
|
||
```bash
|
||
# 单元测试(严格限制:只执行.spec.ts文件,排除集成测试和E2E测试)
|
||
npm run test:unit
|
||
# 等价于: jest --testPathPattern=spec.ts --testPathIgnorePatterns="integration|e2e|performance|property"
|
||
|
||
# 集成测试(统一在test/integration/目录执行)
|
||
npm run test:integration
|
||
# 等价于: jest test/integration/
|
||
|
||
# E2E测试(统一在test/e2e/目录执行,需要设置环境变量)
|
||
npm run test:e2e
|
||
# 等价于: cross-env RUN_E2E_TESTS=true jest test/e2e/
|
||
|
||
# 属性测试(统一在test/property/目录执行)
|
||
npm run test:property
|
||
# 等价于: jest test/property/
|
||
|
||
# 性能测试(统一在test/performance/目录执行)
|
||
npm run test:performance
|
||
# 等价于: jest test/performance/
|
||
|
||
# 分阶段执行(推荐顺序)
|
||
npm run test:unit # 第一阶段:单元测试
|
||
npm run test:integration # 第二阶段:集成测试
|
||
npm run test:e2e # 第三阶段:E2E测试
|
||
npm run test:performance # 第四阶段:性能测试
|
||
|
||
# 全部测试(按顺序执行所有测试)
|
||
npm run test:all
|
||
|
||
# 带覆盖率的测试执行
|
||
npm run test:cov
|
||
```
|
||
|
||
**测试执行顺序说明:**
|
||
1. **单元测试优先**:确保每个模块的基础功能正确
|
||
2. **集成测试其次**:验证模块间的协作
|
||
3. **E2E测试再次**:验证完整的业务流程
|
||
4. **性能测试最后**:在功能正确的基础上验证性能
|
||
|
||
**Jest配置建议:**
|
||
```javascript
|
||
// jest.config.js
|
||
module.exports = {
|
||
// 单元测试配置
|
||
testMatch: [
|
||
'<rootDir>/src/**/*.spec.ts' // 只匹配源文件目录中的.spec.ts文件
|
||
],
|
||
testPathIgnorePatterns: [
|
||
'<rootDir>/test/', // 忽略顶层test目录
|
||
'integration', // 忽略集成测试
|
||
'e2e', // 忽略E2E测试
|
||
'performance', // 忽略性能测试
|
||
'property' // 忽略属性测试
|
||
],
|
||
|
||
// 集成测试配置(单独配置文件)
|
||
projects: [
|
||
{
|
||
displayName: 'unit',
|
||
testMatch: ['<rootDir>/src/**/*.spec.ts'],
|
||
testPathIgnorePatterns: ['<rootDir>/test/']
|
||
},
|
||
{
|
||
displayName: 'integration',
|
||
testMatch: ['<rootDir>/test/integration/**/*.spec.ts']
|
||
},
|
||
{
|
||
displayName: 'e2e',
|
||
testMatch: ['<rootDir>/test/e2e/**/*.spec.ts']
|
||
}
|
||
]
|
||
};
|
||
```
|
||
---
|
||
|
||
## 6️⃣ 功能文档生成
|
||
|
||
### 📚 README文档结构
|
||
|
||
**要求:每个功能模块文件夹都必须有README.md文档**
|
||
|
||
#### 1. 模块概述
|
||
```markdown
|
||
# [模块名称] [中文描述]
|
||
|
||
[模块名称] 是 [一段话总结文件夹的整体功能和作用,说明其在项目中的定位和价值]。
|
||
```
|
||
|
||
#### 2. 对外提供的接口
|
||
```markdown
|
||
## 用户数据操作
|
||
|
||
### create()
|
||
创建新用户记录,支持数据验证和唯一性检查。
|
||
|
||
### findByEmail()
|
||
根据邮箱地址查询用户,用于登录验证和账户找回。
|
||
|
||
### updateUserStatus()
|
||
更新用户状态,支持激活、禁用、待验证等状态切换。
|
||
```
|
||
|
||
#### 2.1 API接口列表(如适用)
|
||
**如果business模块开放了可访问的API,必须在此处列出:**
|
||
|
||
```markdown
|
||
## 对外API接口
|
||
|
||
### POST /api/auth/login
|
||
用户登录接口,支持用户名/邮箱/手机号多种方式登录。
|
||
|
||
### GET /api/users/:id
|
||
根据用户ID获取用户详细信息。
|
||
|
||
### PUT /api/users/:id/status
|
||
更新指定用户的状态(激活/禁用/待验证)。
|
||
|
||
### DELETE /api/users/:id
|
||
删除指定用户账户及相关数据。
|
||
|
||
### GET /api/users/search
|
||
根据条件搜索用户,支持邮箱、用户名、状态等筛选。
|
||
|
||
## WebSocket事件接口
|
||
|
||
### 'connection'
|
||
客户端建立WebSocket连接,需要提供JWT认证token。
|
||
|
||
### 'position_update'
|
||
接收客户端位置更新,广播给房间内其他用户。
|
||
- 输入: `{ x: number, y: number, timestamp: number }`
|
||
- 输出: 广播给房间成员
|
||
|
||
### 'join_room'
|
||
用户加入游戏房间,建立实时通信连接。
|
||
- 输入: `{ roomId: string }`
|
||
- 输出: `{ success: boolean, members: string[] }`
|
||
|
||
### 'chat_message'
|
||
处理聊天消息,支持Zulip集成和消息过滤。
|
||
- 输入: `{ message: string, roomId: string }`
|
||
- 输出: 广播给房间成员或转发到Zulip
|
||
|
||
### 'disconnect'
|
||
客户端断开连接,清理相关资源和通知其他用户。
|
||
```
|
||
|
||
#### 3. 使用的项目内部依赖
|
||
```markdown
|
||
## 使用的项目内部依赖
|
||
|
||
### UserStatus (来自 business/user-mgmt/enums/user-status.enum)
|
||
用户状态枚举,定义用户的激活、禁用、待验证等状态值。
|
||
|
||
### CreateUserDto (本模块)
|
||
用户创建数据传输对象,提供完整的数据验证规则和类型定义。
|
||
|
||
### LoggerService (来自 core/utils/logger)
|
||
日志服务,用于记录用户操作和系统事件。
|
||
```
|
||
|
||
#### 4. 核心特性
|
||
```markdown
|
||
## 核心特性
|
||
|
||
### 双存储模式支持
|
||
- 数据库模式:使用TypeORM连接MySQL,适用于生产环境
|
||
- 内存模式:使用Map存储,适用于开发测试和故障降级
|
||
- 动态模块配置:通过UsersModule.forDatabase()和forMemory()灵活切换
|
||
- 自动检测:根据环境变量自动选择存储模式
|
||
|
||
### 实时通信能力
|
||
- WebSocket支持:基于Socket.IO的实时双向通信
|
||
- 房间管理:支持用户加入/离开游戏房间
|
||
- 位置广播:实时广播用户位置更新给房间成员
|
||
- 连接管理:自动处理连接断开和重连机制
|
||
|
||
### 数据完整性保障
|
||
- 唯一性约束检查:用户名、邮箱、手机号、GitHub ID
|
||
- 数据验证:使用class-validator进行输入验证
|
||
- 事务支持:批量操作支持回滚机制
|
||
- 双模式一致性:确保内存模式和数据库模式行为一致
|
||
|
||
### 性能优化与监控
|
||
- 查询优化:使用索引和查询缓存
|
||
- 批量操作:支持批量创建和更新
|
||
- 内存缓存:热点数据缓存机制
|
||
- 性能监控:WebSocket连接数、消息处理延迟等指标
|
||
- 属性测试:使用fast-check进行随机化测试
|
||
|
||
### 第三方集成
|
||
- Zulip集成:支持与Zulip聊天系统的消息同步
|
||
- 邮件服务:用户注册验证和通知
|
||
- Redis缓存:支持Redis和文件存储双模式
|
||
- JWT认证:完整的用户认证和授权体系
|
||
```
|
||
|
||
#### 5. 潜在风险
|
||
```markdown
|
||
## 潜在风险
|
||
|
||
### 内存模式数据丢失风险
|
||
- 内存存储在应用重启后数据会丢失
|
||
- 不适用于生产环境的持久化需求
|
||
- 建议仅在开发测试环境使用
|
||
- 缓解措施:提供数据导出/导入功能
|
||
|
||
### WebSocket连接管理风险
|
||
- 大量并发连接可能导致内存泄漏
|
||
- 网络不稳定时连接频繁断开重连
|
||
- 房间成员过多时广播性能下降
|
||
- 缓解措施:连接数限制、心跳检测、分片广播
|
||
|
||
### 实时通信性能风险
|
||
- 高频位置更新可能导致服务器压力
|
||
- 消息广播延迟影响游戏体验
|
||
- WebSocket消息丢失或重复
|
||
- 缓解措施:消息限流、优先级队列、消息确认机制
|
||
|
||
### 双模式一致性风险
|
||
- 内存模式和数据库模式行为可能不一致
|
||
- 模式切换时数据同步问题
|
||
- 测试覆盖不完整导致隐藏差异
|
||
- 缓解措施:统一接口抽象、完整的对比测试
|
||
|
||
### 第三方集成风险
|
||
- Zulip服务不可用时影响聊天功能
|
||
- 邮件服务故障影响用户注册
|
||
- Redis连接失败时缓存降级
|
||
- 缓解措施:服务降级、重试机制、监控告警
|
||
|
||
### 并发操作风险
|
||
- 内存模式的ID生成锁机制相对简单
|
||
- 高并发场景可能存在性能瓶颈
|
||
- 位置更新冲突和数据竞争
|
||
- 建议在生产环境使用数据库模式和分布式锁
|
||
|
||
### 数据一致性风险
|
||
- 跨模块操作时可能存在数据不一致
|
||
- WebSocket连接状态与用户状态不同步
|
||
- 需要注意事务边界的设计
|
||
- 建议使用分布式事务或补偿机制
|
||
|
||
### 安全风险
|
||
- WebSocket连接缺少足够的认证验证
|
||
- 用户位置信息泄露风险
|
||
- 管理员权限过度集中
|
||
- 缓解措施:JWT认证、数据脱敏、权限细分
|
||
```
|
||
|
||
### 📝 文档质量要求
|
||
|
||
#### 内容质量标准
|
||
- **准确性**:所有信息必须与代码实现一致
|
||
- **完整性**:覆盖所有公共接口和重要功能
|
||
- **简洁性**:每个说明控制在一句话内,突出核心要点
|
||
- **实用性**:提供对开发者有价值的信息和建议
|
||
|
||
#### 语言表达规范
|
||
- 使用中文进行描述,专业术语可保留英文
|
||
- 语言简洁明了,避免冗长的句子
|
||
- 统一术语使用,保持前后一致
|
||
- 避免主观评价,客观描述功能和特性
|
||
|
||
---
|
||
|
||
## 🛠️ 实用工具和技巧
|
||
|
||
### 📋 检查清单
|
||
|
||
#### 命名规范检查清单
|
||
- [ ] 文件名使用snake_case(下划线分隔)
|
||
- [ ] 变量和函数使用camelCase(小驼峰)
|
||
- [ ] 类和接口使用PascalCase(大驼峰)
|
||
- [ ] 常量使用SCREAMING_SNAKE_CASE(全大写+下划线)
|
||
- [ ] 路由使用kebab-case(短横线分隔)
|
||
- [ ] 避免过度嵌套的文件夹结构
|
||
- [ ] Core层业务支撑模块使用_core后缀,通用工具模块不使用后缀
|
||
|
||
#### 注释规范检查清单
|
||
- [ ] 文件头注释包含功能描述、职责分离、修改记录
|
||
- [ ] 类注释包含职责、主要方法、使用场景
|
||
- [ ] 方法注释包含业务逻辑、参数说明、返回值、异常、示例
|
||
- [ ] 修改记录使用正确的日期和修改者信息
|
||
- [ ] 版本号按规则递增
|
||
- [ ] @author字段正确处理(AI标识替换为实际作者)
|
||
|
||
#### 代码质量检查清单
|
||
- [ ] 清理所有未使用的导入
|
||
- [ ] 清理所有未使用的变量和方法
|
||
- [ ] 常量使用正确的命名规范
|
||
- [ ] 方法长度控制在合理范围内(建议不超过50行)
|
||
- [ ] 避免代码重复
|
||
- [ ] 处理所有TODO项(实现功能或删除代码)
|
||
|
||
#### 架构分层检查清单
|
||
- [ ] Core层专注技术实现,不包含业务逻辑
|
||
- [ ] Business层专注业务逻辑,不包含技术实现细节
|
||
- [ ] 依赖关系符合分层架构要求
|
||
- [ ] 模块职责清晰,边界明确
|
||
|
||
#### 测试覆盖检查清单
|
||
- [ ] 每个Service都有对应的.spec.ts测试文件
|
||
- [ ] 测试文件与源文件严格一对一映射
|
||
- [ ] 测试内容严格限于对应源文件的功能范围
|
||
- [ ] 所有集成测试已移动到test/integration/目录
|
||
- [ ] 所有E2E测试已移动到test/e2e/目录
|
||
- [ ] 所有性能测试已移动到test/performance/目录
|
||
- [ ] 所有属性测试已移动到test/property/目录
|
||
- [ ] 单元测试文件中不包含集成测试或跨文件测试代码
|
||
- [ ] 所有公共方法都有测试覆盖
|
||
- [ ] 测试覆盖正常情况、异常情况、边界情况
|
||
- [ ] 测试代码质量高,真实有效
|
||
- [ ] 复杂Service提供集成测试
|
||
- [ ] 测试能够成功执行
|
||
|
||
#### 功能文档检查清单
|
||
- [ ] 每个功能模块都有README.md文档
|
||
- [ ] 文档包含模块概述、对外接口、内部依赖、核心特性、潜在风险
|
||
- [ ] 所有公共接口都有准确的功能描述
|
||
- [ ] 如果是business模块且开放了API,必须列出所有API接口及功能说明
|
||
- [ ] 文档内容与代码实现一致
|
||
- [ ] 语言表达简洁明了
|
||
|
||
### 🔧 常用命令
|
||
|
||
#### 测试相关命令
|
||
```bash
|
||
# 游戏服务器测试命令(更新后的结构)
|
||
npm run test:unit # 单元测试(只测试src/目录中的.spec.ts)
|
||
npm run test:integration # 集成测试(test/integration/目录)
|
||
npm run test:e2e # E2E测试(test/e2e/目录)
|
||
npm run test:property # 属性测试(test/property/目录)
|
||
npm run test:performance # 性能测试(test/performance/目录)
|
||
npm run test:cov # 测试覆盖率
|
||
npm run test:all # 全部测试(按顺序执行)
|
||
|
||
# 分阶段测试执行(推荐)
|
||
npm run test:unit && npm run test:integration && npm run test:e2e
|
||
|
||
# Jest特定目录测试
|
||
jest src/ # 只测试源文件目录
|
||
jest test/integration/ # 只测试集成测试
|
||
jest test/e2e/ # 只测试E2E测试
|
||
jest test/performance/ # 只测试性能测试
|
||
|
||
# WebSocket测试(需要启动服务)
|
||
npm run dev & # 后台启动开发服务器
|
||
npm run test:e2e # 运行E2E测试
|
||
```
|
||
|
||
#### 代码检查命令
|
||
```bash
|
||
# TypeScript类型检查
|
||
npx tsc --noEmit
|
||
|
||
# ESLint代码检查
|
||
npx eslint src/**/*.ts
|
||
|
||
# Prettier代码格式化
|
||
npx prettier --write src/**/*.ts
|
||
```
|
||
|
||
### 🚨 常见错误和解决方案
|
||
|
||
#### 命名规范常见错误
|
||
1. **短横线命名错误(不符合项目规范)**
|
||
- 错误:`admin-operation-log.service.ts`
|
||
- 正确:`admin_operation_log.service.ts`
|
||
- 解决:统一使用下划线分隔,保持项目一致性
|
||
|
||
2. **游戏服务器特殊文件命名错误**
|
||
- 错误:`locationBroadcast.gateway.ts`
|
||
- 正确:`location_broadcast.gateway.ts`
|
||
- 错误:`websocketAuth.guard.ts`
|
||
- 正确:`websocket_auth.guard.ts`
|
||
|
||
3. **常量命名错误**
|
||
- 错误:`const saltRounds = 10;`
|
||
- 正确:`const SALT_ROUNDS = 10;`
|
||
- 解决:常量使用全大写+下划线
|
||
|
||
#### 架构分层常见错误
|
||
1. **Business层包含技术实现**
|
||
- 错误:直接操作数据库连接
|
||
- 正确:调用Core层服务
|
||
- 解决:通过依赖注入使用Core层服务
|
||
|
||
2. **Core层包含业务逻辑**
|
||
- 错误:在数据层进行业务验证
|
||
- 正确:只处理技术实现
|
||
- 解决:将业务逻辑移到Business层
|
||
|
||
#### 测试覆盖常见错误
|
||
1. **测试文件位置错误**
|
||
- 错误:测试文件放在单独的tests/文件夹中
|
||
- 正确:测试文件必须与源文件放在同一目录
|
||
- 解决:将测试文件移动到对应源文件的同一目录
|
||
|
||
2. **测试范围混乱**
|
||
- 错误:单元测试中包含集成测试代码
|
||
- 正确:严格区分单元测试和集成测试
|
||
- 解决:将集成测试移动到test/integration/目录
|
||
|
||
3. **一对多测试文件**
|
||
- 错误:一个测试文件测试多个源文件
|
||
- 正确:每个测试文件严格对应一个源文件
|
||
- 解决:拆分测试文件,确保一对一映射
|
||
|
||
4. **WebSocket测试文件缺失**
|
||
- 错误:Gateway没有对应的.spec.ts文件
|
||
- 解决:为每个Gateway创建完整的连接、消息处理测试
|
||
|
||
5. **双模式测试不完整**
|
||
- 错误:只测试数据库模式,忽略内存模式
|
||
- 正确:确保两种模式行为一致性测试
|
||
- 解决:创建对比测试用例
|
||
|
||
6. **属性测试缺失**
|
||
- 错误:管理员模块缺少随机化测试
|
||
- 正确:使用fast-check进行属性测试
|
||
- 解决:在test/property/目录补充基于属性的测试用例
|
||
|
||
7. **实时通信测试场景不完整**
|
||
- 错误:只测试正常连接,忽略异常断开
|
||
- 正确:测试连接、断开、重连、消息处理全流程
|
||
- 解决:补充WebSocket生命周期测试
|
||
|
||
---
|
||
|
||
## 📈 最佳实践建议
|
||
|
||
### 🎯 开发流程建议
|
||
|
||
1. **编码前**:明确模块职责和架构定位
|
||
2. **编码中**:遵循命名规范和注释规范
|
||
3. **编码后**:进行代码质量检查和测试覆盖
|
||
4. **提交前**:生成或更新功能文档
|
||
|
||
### 🔄 持续改进
|
||
|
||
1. **定期检查**:建议每周进行一次全面的代码规范检查
|
||
2. **团队协作**:通过Code Review确保规范执行
|
||
3. **工具辅助**:使用ESLint、Prettier等工具自动化检查
|
||
4. **文档维护**:及时更新文档,保持与代码同步
|
||
|
||
### 📊 质量指标
|
||
|
||
1. **命名规范达标率**:目标100%
|
||
2. **注释覆盖率**:文件头、类、公共方法100%覆盖
|
||
3. **测试覆盖率**:单元测试覆盖率>90%
|
||
4. **文档完整性**:每个功能模块都有README文档
|
||
|
||
---
|
||
|
||
## 🤝 团队协作
|
||
|
||
### 👥 角色职责
|
||
|
||
- **开发者**:遵循规范进行开发,自检代码质量
|
||
- **Code Reviewer**:检查代码是否符合规范要求
|
||
- **架构师**:制定和维护架构分层规范
|
||
- **测试工程师**:确保测试覆盖率和测试质量
|
||
|
||
### 📋 Review检查点
|
||
|
||
1. **命名规范**:文件、变量、函数、类的命名是否符合规范
|
||
2. **注释完整性**:文件头、类、方法注释是否完整准确
|
||
3. **代码质量**:是否有未使用的代码,常量定义是否规范
|
||
4. **架构合规性**:是否符合分层架构要求
|
||
5. **测试覆盖**:是否有对应的测试文件和完整的测试用例
|
||
6. **文档同步**:README文档是否与代码实现一致
|
||
|
||
### 🛡️ 质量保障
|
||
|
||
1. **自动化检查**:集成ESLint、Prettier、Jest等工具
|
||
2. **CI/CD集成**:在构建流程中加入代码规范检查
|
||
3. **定期审计**:定期进行代码规范审计和改进
|
||
4. **培训推广**:定期组织团队培训,提高规范意识
|
||
|
||
---
|
||
|
||
## 📞 支持和反馈
|
||
|
||
如果在使用过程中遇到问题或有改进建议,请:
|
||
|
||
1. 查阅本文档的相关章节
|
||
2. 参考常见错误和解决方案
|
||
3. 向团队架构师或技术负责人咨询
|
||
4. 提交改进建议,持续优化规范
|
||
|
||
**记住:代码规范不是束缚,而是提高代码质量和团队协作效率的有力工具!** 🚀
|
||
|
||
---
|
||
|
||
## 🎮 游戏服务器特殊优化建议
|
||
|
||
### 🚀 实时通信优化
|
||
|
||
1. **WebSocket连接管理**
|
||
- 实现连接池和心跳检测
|
||
- 设置合理的连接超时和重连机制
|
||
- 监控连接数量和消息处理延迟
|
||
|
||
2. **消息广播优化**
|
||
- 使用房间分片减少广播范围
|
||
- 实现消息优先级队列
|
||
- 添加消息确认和重试机制
|
||
|
||
3. **位置更新优化**
|
||
- 实现位置更新频率限制
|
||
- 使用差分更新减少数据传输
|
||
- 添加位置验证防止作弊
|
||
|
||
### 🔄 双模式架构优化
|
||
|
||
1. **模式切换优化**
|
||
- 提供平滑的模式切换机制
|
||
- 实现数据迁移和同步工具
|
||
- 添加模式状态监控
|
||
|
||
2. **一致性保障**
|
||
- 统一接口抽象层
|
||
- 完整的行为对比测试
|
||
- 自动化一致性检查
|
||
|
||
3. **性能对比**
|
||
- 定期进行性能基准测试
|
||
- 监控两种模式的资源使用
|
||
- 优化内存模式的并发处理
|
||
|
||
### 🧪 测试策略优化
|
||
|
||
1. **严格一对一测试映射**
|
||
- 每个测试文件严格对应一个源文件
|
||
- 测试内容严格限于对应源文件的功能
|
||
- 禁止跨文件测试和混合测试
|
||
|
||
2. **分层测试架构**
|
||
- 单元测试:放在源文件同目录,测试单个模块功能
|
||
- 集成测试:统一放在test/integration/,测试模块间协作
|
||
- E2E测试:统一放在test/e2e/,测试完整业务流程
|
||
- 性能测试:统一放在test/performance/,测试系统性能
|
||
- 属性测试:统一放在test/property/,进行随机化测试
|
||
|
||
3. **属性测试应用**
|
||
- 管理员模块使用fast-check
|
||
- 随机化用户状态变更测试
|
||
- 边界条件自动发现
|
||
|
||
4. **集成测试重点**
|
||
- WebSocket连接生命周期
|
||
- 双模式服务一致性
|
||
- 第三方服务集成
|
||
|
||
5. **E2E测试场景**
|
||
- 完整的用户游戏流程
|
||
- 多用户实时交互
|
||
- 异常恢复和降级
|
||
|
||
6. **测试执行顺序**
|
||
- 第一阶段:单元测试(快速反馈)
|
||
- 第二阶段:集成测试(模块协作)
|
||
- 第三阶段:E2E测试(业务流程)
|
||
- 第四阶段:性能测试(系统性能)
|
||
|
||
### 📊 监控和告警
|
||
|
||
1. **关键指标监控**
|
||
- WebSocket连接数和延迟
|
||
- 位置更新频率和处理时间
|
||
- 内存使用和GC频率
|
||
- 第三方服务可用性
|
||
|
||
2. **告警策略**
|
||
- 连接数超过阈值
|
||
- 消息处理延迟过高
|
||
- 服务降级和故障转移
|
||
- 数据一致性检查失败 |