修复问题: - ZulipModule:修正exports配置,导出ZulipCoreModule而非单独服务 - ZulipModule:添加CacheModule.register()解决CACHE_MANAGER依赖 - ZulipGatewayModule:添加LoginCoreModule解决JwtAuthGuard依赖 文档补充(step4-architecture-layer.md): - 新增「应用启动验证」强制检查步骤 - 添加常见启动错误示例和修复方案 - 明确启动验证是步骤4的强制完成条件 - 补充启动验证检查清单和失败处理流程
860 lines
27 KiB
Markdown
860 lines
27 KiB
Markdown
# 步骤4:架构分层检查
|
||
|
||
## ⚠️ 执行前必读规范
|
||
|
||
**🔥 重要:在执行本步骤之前,AI必须先完整阅读同级目录下的 `README.md` 文件!**
|
||
|
||
该README文件包含:
|
||
- 🎯 执行前准备和用户信息收集要求
|
||
- 🔄 强制执行原则和分步执行流程
|
||
- 🔥 修改后立即重新执行当前步骤的强制规则
|
||
- 📝 文件修改记录规范和版本号递增规则
|
||
- 🧪 测试文件调试规范和测试指令使用规范
|
||
- 🚨 全局约束和游戏服务器特殊要求
|
||
|
||
**不阅读README直接执行步骤将导致执行不规范,违反项目要求!**
|
||
|
||
---
|
||
|
||
## 🎯 检查目标
|
||
检查架构分层的合规性,确保Core层和Business层职责清晰、依赖关系正确。
|
||
|
||
## 🏗️ 架构层级识别
|
||
|
||
### 项目分层结构
|
||
```
|
||
src/
|
||
├── gateway/ # Gateway层:网关层(HTTP协议处理)
|
||
│ ├── auth/ # 认证网关
|
||
│ ├── users/ # 用户网关
|
||
│ └── admin/ # 管理网关
|
||
├── business/ # Business层:业务逻辑层
|
||
│ ├── auth/ # 认证业务
|
||
│ ├── users/ # 用户业务
|
||
│ └── admin/ # 管理业务
|
||
├── core/ # Core层:技术实现层
|
||
│ ├── db/ # 数据访问
|
||
│ ├── redis/ # 缓存服务
|
||
│ └── utils/ # 工具服务
|
||
└── common/ # 公共层:通用组件
|
||
```
|
||
|
||
### 4层架构说明
|
||
|
||
**Gateway Layer(网关层)**
|
||
- 位置:`src/gateway/`
|
||
- 职责:HTTP协议处理、数据验证、路由管理、认证守卫、错误转换
|
||
- 依赖:Business层
|
||
|
||
**Business Layer(业务层)**
|
||
- 位置:`src/business/`
|
||
- 职责:业务逻辑实现、业务流程控制、服务协调、业务规则验证
|
||
- 依赖:Core层
|
||
|
||
**Core Layer(核心层)**
|
||
- 位置:`src/core/`
|
||
- 职责:数据访问、基础设施、外部系统集成、技术实现细节
|
||
- 依赖:无(或第三方库)
|
||
|
||
### 检查范围
|
||
- **限制范围**:仅检查当前执行检查的文件夹
|
||
- **不跨模块**:不考虑其他同层功能模块
|
||
- **专注职责**:确保当前模块职责清晰
|
||
- **按层检查**:根据文件夹所在层级应用对应的检查规则
|
||
|
||
## 🌐 Gateway层规范检查
|
||
|
||
### 职责定义
|
||
**Gateway层专注HTTP协议处理,不包含业务逻辑**
|
||
|
||
### Gateway层协议处理示例
|
||
```typescript
|
||
// ✅ 正确:Gateway层只做协议转换
|
||
@Controller('auth')
|
||
export class LoginController {
|
||
constructor(private readonly loginService: LoginService) {}
|
||
|
||
@Post('login')
|
||
async login(@Body() loginDto: LoginDto, @Res() res: Response): Promise<void> {
|
||
// 1. 接收HTTP请求,使用DTO验证
|
||
// 2. 调用Business层服务
|
||
const result = await this.loginService.login({
|
||
identifier: loginDto.identifier,
|
||
password: loginDto.password
|
||
});
|
||
|
||
// 3. 将业务响应转换为HTTP响应
|
||
this.handleResponse(result, res);
|
||
}
|
||
|
||
private handleResponse(result: any, res: Response): void {
|
||
if (result.success) {
|
||
res.status(HttpStatus.OK).json(result);
|
||
} else {
|
||
const statusCode = this.getErrorStatusCode(result);
|
||
res.status(statusCode).json(result);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ❌ 错误:Gateway层包含业务逻辑
|
||
@Controller('auth')
|
||
export class LoginController {
|
||
@Post('login')
|
||
async login(@Body() loginDto: LoginDto): Promise<any> {
|
||
// 错误:在Controller中实现业务逻辑
|
||
const user = await this.userRepository.findOne({
|
||
where: { username: loginDto.identifier }
|
||
});
|
||
|
||
if (!user) {
|
||
throw new NotFoundException('用户不存在');
|
||
}
|
||
|
||
const isValid = await bcrypt.compare(loginDto.password, user.password);
|
||
if (!isValid) {
|
||
throw new UnauthorizedException('密码错误');
|
||
}
|
||
|
||
// ... 更多业务逻辑
|
||
}
|
||
}
|
||
```
|
||
|
||
### Gateway层依赖关系检查
|
||
```typescript
|
||
// ✅ 允许的导入
|
||
import { Controller, Post, Body, Res } from '@nestjs/common'; # NestJS框架
|
||
import { Response } from 'express'; # Express类型
|
||
import { LoginService } from '../../business/auth/login.service'; # Business层服务
|
||
import { LoginDto } from './dto/login.dto'; # 同层DTO
|
||
import { JwtAuthGuard } from './jwt_auth.guard'; # 同层Guard
|
||
|
||
// ❌ 禁止的导入
|
||
import { LoginCoreService } from '../../core/login_core/login_core.service'; # 跳过Business层直接调用Core层
|
||
import { UsersRepository } from '../../core/db/users/users.repository'; # 直接访问数据层
|
||
import { RedisService } from '../../core/redis/redis.service'; # 直接访问技术服务
|
||
```
|
||
|
||
### Gateway层文件类型检查
|
||
```typescript
|
||
// ✅ Gateway层应该包含的文件类型
|
||
- *.controller.ts # HTTP控制器
|
||
- *.dto.ts # 数据传输对象
|
||
- *.guard.ts # 认证/授权守卫
|
||
- *.decorator.ts # 参数装饰器
|
||
- *.interceptor.ts # 拦截器
|
||
- *.filter.ts # 异常过滤器
|
||
- *.gateway.module.ts # 网关模块
|
||
|
||
// ❌ Gateway层不应该包含的文件类型
|
||
- *.service.ts # 业务服务(应在Business层)
|
||
- *.repository.ts # 数据仓库(应在Core层)
|
||
- *.entity.ts # 数据实体(应在Core层)
|
||
```
|
||
|
||
### Gateway层职责检查清单
|
||
- [ ] Controller方法是否只做协议转换?
|
||
- [ ] 是否使用DTO进行数据验证?
|
||
- [ ] 是否调用Business层服务而非Core层?
|
||
- [ ] 是否有统一的错误处理机制?
|
||
- [ ] 是否包含Swagger API文档?
|
||
- [ ] 是否使用限流和超时保护?
|
||
|
||
## 🔧 Core层规范检查
|
||
|
||
### 职责定义
|
||
**Core层专注技术实现,不包含业务逻辑**
|
||
|
||
### 命名规范检查
|
||
|
||
#### 业务支撑模块(使用_core后缀)
|
||
专门为特定业务功能提供技术支撑:
|
||
```typescript
|
||
✅ 正确示例:
|
||
src/core/location_broadcast_core/ # 为位置广播业务提供技术支撑
|
||
src/core/admin_core/ # 为管理员业务提供技术支撑
|
||
src/core/user_auth_core/ # 为用户认证业务提供技术支撑
|
||
src/core/zulip_core/ # 为Zulip集成提供技术支撑
|
||
|
||
❌ 错误示例:
|
||
src/core/location_broadcast/ # 应该是location_broadcast_core
|
||
src/core/admin/ # 应该是admin_core
|
||
```
|
||
|
||
#### 通用工具模块(不使用后缀)
|
||
提供可复用的数据访问或技术服务:
|
||
```typescript
|
||
✅ 正确示例:
|
||
src/core/db/user_profiles/ # 通用的用户档案数据访问
|
||
src/core/redis/ # 通用的Redis技术封装
|
||
src/core/utils/logger/ # 通用的日志工具服务
|
||
src/core/db/zulip_accounts/ # 通用的Zulip账户数据访问
|
||
|
||
❌ 错误示例:
|
||
src/core/db/user_profiles_core/ # 应该是user_profiles(通用工具)
|
||
src/core/redis_core/ # 应该是redis(通用工具)
|
||
```
|
||
|
||
### 命名判断流程
|
||
```
|
||
1. 模块是否专门为某个特定业务功能服务?
|
||
├─ 是 → 检查模块名称是否体现业务领域
|
||
│ ├─ 是 → 使用 _core 后缀
|
||
│ └─ 否 → 重新设计模块职责
|
||
└─ 否 → 模块是否提供通用的技术服务?
|
||
├─ 是 → 不使用 _core 后缀
|
||
└─ 否 → 重新评估模块定位
|
||
|
||
2. 实际案例判断:
|
||
- user_profiles: 通用的用户档案数据访问 → 不使用后缀 ✓
|
||
- location_broadcast_core: 专门为位置广播业务服务 → 使用_core后缀 ✓
|
||
- redis: 通用的缓存技术服务 → 不使用后缀 ✓
|
||
- zulip_core: 专门为Zulip集成业务服务 → 使用_core后缀 ✓
|
||
```
|
||
|
||
### Core层技术实现示例
|
||
```typescript
|
||
// ✅ 正确:Core层专注技术实现
|
||
@Injectable()
|
||
export class LocationBroadcastCoreService {
|
||
/**
|
||
* 广播位置更新到指定房间
|
||
*
|
||
* 技术实现:
|
||
* 1. 验证WebSocket连接状态
|
||
* 2. 序列化位置数据
|
||
* 3. 通过Socket.IO广播消息
|
||
* 4. 记录广播性能指标
|
||
* 5. 处理广播异常和重试
|
||
*/
|
||
async broadcastToRoom(roomId: string, data: PositionData): Promise<void> {
|
||
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层依赖关系检查
|
||
```typescript
|
||
// ✅ 允许的导入
|
||
import { Injectable } from '@nestjs/common'; # NestJS框架
|
||
import { Server } from 'socket.io'; # 第三方技术库
|
||
import { RedisService } from '../redis/redis.service'; # 其他Core层模块
|
||
import * as crypto from 'crypto'; # Node.js内置模块
|
||
|
||
// ❌ 禁止的导入
|
||
import { UserBusinessService } from '../../business/users/user.service'; # Business层模块
|
||
import { AdminController } from '../../business/admin/admin.controller'; # Business层模块
|
||
```
|
||
|
||
## 💼 Business层规范检查
|
||
|
||
### 职责定义
|
||
**Business层专注业务逻辑实现,不关心底层技术细节**
|
||
|
||
### 业务逻辑完备性检查
|
||
```typescript
|
||
// ✅ 正确:完整的业务逻辑
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
/**
|
||
* 用户注册业务流程
|
||
*
|
||
* 业务逻辑:
|
||
* 1. 验证用户信息完整性
|
||
* 2. 检查用户名/邮箱是否已存在
|
||
* 3. 验证邮箱格式和域名白名单
|
||
* 4. 生成用户唯一标识
|
||
* 5. 设置默认用户权限
|
||
* 6. 发送欢迎邮件
|
||
* 7. 记录注册日志
|
||
* 8. 返回注册结果
|
||
*/
|
||
async registerUser(registerData: RegisterUserDto): Promise<UserResult> {
|
||
await this.validateUserBusinessRules(registerData);
|
||
const user = await this.userCoreService.create(registerData);
|
||
await this.emailService.sendWelcomeEmail(user.email);
|
||
await this.logService.recordUserRegistration(user.id);
|
||
return this.buildUserResult(user);
|
||
}
|
||
}
|
||
|
||
// ❌ 错误:业务逻辑不完整
|
||
@Injectable()
|
||
export class UserBusinessService {
|
||
async registerUser(registerData: RegisterUserDto): Promise<User> {
|
||
// 只是简单调用数据库保存,缺少业务验证和流程
|
||
return this.userRepository.save(registerData);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Business层依赖关系检查
|
||
```typescript
|
||
// ✅ 允许的导入
|
||
import { UserCoreService } from '../../core/user_auth_core/user_core.service'; # 对应Core层业务支撑
|
||
import { CacheService } from '../../core/redis/cache.service'; # Core层通用工具
|
||
import { EmailService } from '../../core/utils/email.service'; # Core层通用工具
|
||
import { OtherBusinessService } from '../other/other.service'; # 其他Business层(谨慎)
|
||
|
||
// ❌ 禁止的导入
|
||
import { createConnection } from 'typeorm'; # 直接技术实现
|
||
import * as Redis from 'ioredis'; # 直接技术实现
|
||
import { DatabaseConnection } from '../../core/db/connection'; # 底层技术细节
|
||
```
|
||
|
||
## 🚨 常见架构违规
|
||
|
||
### Gateway层违规示例
|
||
```typescript
|
||
// ❌ 错误:Gateway层包含业务逻辑
|
||
@Controller('users')
|
||
export class UserController {
|
||
@Post('register')
|
||
async register(@Body() registerDto: RegisterDto): Promise<User> {
|
||
// 违规:在Controller中实现业务验证
|
||
if (registerDto.age < 18) {
|
||
throw new BadRequestException('用户年龄必须大于18岁');
|
||
}
|
||
|
||
// 违规:在Controller中协调多个服务
|
||
const user = await this.userCoreService.create(registerDto);
|
||
await this.emailService.sendWelcomeEmail(user.email);
|
||
await this.zulipService.createAccount(user);
|
||
|
||
return user;
|
||
}
|
||
}
|
||
|
||
// ❌ 错误:Gateway层直接调用Core层
|
||
@Controller('auth')
|
||
export class LoginController {
|
||
constructor(
|
||
private readonly loginCoreService: LoginCoreService, // 违规:跳过Business层
|
||
) {}
|
||
|
||
@Post('login')
|
||
async login(@Body() loginDto: LoginDto): Promise<any> {
|
||
// 违规:直接调用Core层服务
|
||
return this.loginCoreService.login(loginDto);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 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('不允许竞争对手注册');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🎮 游戏服务器架构特殊检查
|
||
|
||
### WebSocket Gateway分层
|
||
```typescript
|
||
// ✅ 正确:Gateway在Business层,调用Core层服务
|
||
@WebSocketGateway()
|
||
export class LocationBroadcastGateway {
|
||
constructor(
|
||
private readonly locationBroadcastCore: LocationBroadcastCoreService,
|
||
private readonly userProfiles: UserProfilesService,
|
||
) {}
|
||
|
||
@SubscribeMessage('position_update')
|
||
async handlePositionUpdate(client: Socket, data: PositionData): Promise<void> {
|
||
// 业务逻辑:验证、权限检查
|
||
await this.validateUserPermission(client.userId);
|
||
|
||
// 调用Core层技术实现
|
||
await this.locationBroadcastCore.broadcastToRoom(client.roomId, data);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 双模式服务分层
|
||
```typescript
|
||
// ✅ 正确:Business层统一接口,Core层不同实现
|
||
@Injectable()
|
||
export class UsersBusinessService {
|
||
constructor(
|
||
@Inject('USERS_SERVICE')
|
||
private readonly usersCore: UsersMemoryService | UsersDatabaseService,
|
||
) {}
|
||
|
||
async createUser(userData: CreateUserDto): Promise<User> {
|
||
// 业务逻辑:验证、权限、流程
|
||
await this.validateUserBusinessRules(userData);
|
||
|
||
// 调用Core层(内存或数据库模式)
|
||
const user = await this.usersCore.create(userData);
|
||
|
||
// 业务逻辑:后续处理
|
||
await this.sendWelcomeNotification(user);
|
||
return user;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🔧 NestJS依赖注入检查(重要)
|
||
|
||
### 依赖注入完整性检查
|
||
**在NestJS中,如果一个类(如Guard、Service、Controller)需要注入其他服务,必须确保该服务在模块的imports中可访问。**
|
||
|
||
### 常见依赖注入问题
|
||
```typescript
|
||
// ❌ 错误:JwtAuthGuard需要LoginCoreService,但模块未导入LoginCoreModule
|
||
@Module({
|
||
imports: [
|
||
AuthModule, // AuthModule虽然导入了LoginCoreModule,但没有重新导出
|
||
],
|
||
providers: [
|
||
JwtAuthGuard, // 错误:无法注入LoginCoreService
|
||
],
|
||
})
|
||
export class AuthGatewayModule {}
|
||
|
||
@Injectable()
|
||
export class JwtAuthGuard {
|
||
constructor(
|
||
private readonly loginCoreService: LoginCoreService, // 注入失败!
|
||
) {}
|
||
}
|
||
|
||
// ✅ 正确方案1:直接导入需要的Core模块
|
||
@Module({
|
||
imports: [
|
||
AuthModule,
|
||
LoginCoreModule, // 直接导入,使LoginCoreService可用
|
||
],
|
||
providers: [
|
||
JwtAuthGuard, // 现在可以成功注入LoginCoreService
|
||
],
|
||
})
|
||
export class AuthGatewayModule {}
|
||
|
||
// ✅ 正确方案2:在中间模块重新导出
|
||
@Module({
|
||
imports: [LoginCoreModule],
|
||
exports: [LoginCoreModule], // 重新导出,让导入AuthModule的模块也能访问
|
||
})
|
||
export class AuthModule {}
|
||
```
|
||
|
||
### 依赖注入检查规则
|
||
|
||
#### 1. 检查Provider的构造函数依赖
|
||
```typescript
|
||
// 对于每个Provider(Service、Guard、Interceptor等)
|
||
@Injectable()
|
||
export class SomeGuard {
|
||
constructor(
|
||
private readonly serviceA: ServiceA, // 依赖1
|
||
private readonly serviceB: ServiceB, // 依赖2
|
||
) {}
|
||
}
|
||
|
||
// 检查清单:
|
||
// ✓ ServiceA是否在当前模块的imports中?
|
||
// ✓ ServiceB是否在当前模块的imports中?
|
||
// ✓ 如果不在,是否需要添加对应的Module到imports?
|
||
```
|
||
|
||
#### 2. 检查Module的导出完整性
|
||
```typescript
|
||
// ❌ 错误:导入了模块但没有导出,导致上层模块无法访问
|
||
@Module({
|
||
imports: [LoginCoreModule],
|
||
providers: [LoginService],
|
||
exports: [LoginService], // 只导出了LoginService,没有导出LoginCoreModule
|
||
})
|
||
export class AuthModule {}
|
||
|
||
// 如果上层模块需要直接使用LoginCoreService:
|
||
@Module({
|
||
imports: [AuthModule], // 无法访问LoginCoreService
|
||
providers: [JwtAuthGuard], // JwtAuthGuard需要LoginCoreService,会失败
|
||
})
|
||
export class AuthGatewayModule {}
|
||
|
||
// ✅ 正确:根据需要导出Module
|
||
@Module({
|
||
imports: [LoginCoreModule],
|
||
providers: [LoginService],
|
||
exports: [
|
||
LoginService,
|
||
LoginCoreModule, // 导出Module,让上层也能访问
|
||
],
|
||
})
|
||
export class AuthModule {}
|
||
```
|
||
|
||
#### 3. 检查跨层依赖的模块导入
|
||
```typescript
|
||
// Gateway层的Guard直接依赖Core层Service的情况
|
||
@Injectable()
|
||
export class JwtAuthGuard {
|
||
constructor(
|
||
private readonly loginCoreService: LoginCoreService, // 直接依赖Core层
|
||
) {}
|
||
}
|
||
|
||
// 检查清单:
|
||
// ✓ AuthGatewayModule是否导入了LoginCoreModule?
|
||
// ✓ 如果通过AuthModule间接导入,AuthModule是否导出了LoginCoreModule?
|
||
// ✓ 是否符合架构分层原则(Gateway可以直接依赖Core用于技术实现)?
|
||
```
|
||
|
||
### 依赖注入检查步骤
|
||
|
||
1. **扫描所有Injectable类**
|
||
- 找出所有使用@Injectable()装饰器的类
|
||
- 包括Service、Guard、Interceptor、Pipe等
|
||
|
||
2. **分析构造函数依赖**
|
||
- 检查每个类的constructor参数
|
||
- 列出所有需要注入的服务
|
||
|
||
3. **检查Module的imports**
|
||
- 确认每个依赖的服务是否在Module的imports中
|
||
- 检查imports的Module是否导出了需要的服务
|
||
|
||
4. **验证依赖链完整性**
|
||
- 如果A模块导入B模块,B模块导入C模块
|
||
- 确认A模块是否能访问C模块的服务(取决于B是否导出C)
|
||
|
||
5. **检查常见错误模式**
|
||
- Guard/Interceptor依赖Service但模块未导入
|
||
- 中间模块导入但未导出,导致上层无法访问
|
||
- 循环依赖问题
|
||
|
||
### 依赖注入错误识别
|
||
|
||
#### 典型错误信息
|
||
```
|
||
Nest can't resolve dependencies of the JwtAuthGuard (?).
|
||
Please make sure that the argument LoginCoreService at index [0]
|
||
is available in the AuthGatewayModule context.
|
||
```
|
||
|
||
#### 错误分析流程
|
||
```
|
||
1. 识别问题类:JwtAuthGuard
|
||
2. 识别缺失依赖:LoginCoreService(索引0)
|
||
3. 识别所在模块:AuthGatewayModule
|
||
4. 检查解决方案:
|
||
├─ LoginCoreService在哪个Module中提供?
|
||
│ └─ 答:LoginCoreModule
|
||
├─ AuthGatewayModule是否导入了LoginCoreModule?
|
||
│ └─ 否 → 需要添加到imports
|
||
└─ 如果通过其他Module间接导入,该Module是否导出了LoginCoreModule?
|
||
└─ 否 → 需要在中间Module的exports中添加
|
||
```
|
||
|
||
### 依赖注入最佳实践
|
||
|
||
```typescript
|
||
// ✅ 推荐:明确的依赖关系
|
||
@Module({
|
||
imports: [
|
||
// 业务层模块
|
||
AuthModule,
|
||
// 直接需要的核心层模块(用于Guard等技术组件)
|
||
LoginCoreModule,
|
||
],
|
||
controllers: [LoginController],
|
||
providers: [JwtAuthGuard],
|
||
exports: [JwtAuthGuard],
|
||
})
|
||
export class AuthGatewayModule {}
|
||
|
||
// ✅ 推荐:完整的导出链
|
||
@Module({
|
||
imports: [LoginCoreModule, UsersModule],
|
||
providers: [LoginService],
|
||
exports: [
|
||
LoginService, // 导出自己的服务
|
||
LoginCoreModule, // 导出依赖的模块(如果上层需要)
|
||
],
|
||
})
|
||
export class AuthModule {}
|
||
```
|
||
|
||
## 🔍 检查执行步骤
|
||
|
||
1. **识别当前模块的层级**
|
||
- 确定是Gateway层、Business层还是Core层
|
||
- 检查文件夹路径和命名
|
||
- 根据层级应用对应的检查规则
|
||
|
||
2. **Gateway层检查(如果是Gateway层)**
|
||
- 检查是否只包含协议处理代码
|
||
- 检查是否使用DTO进行数据验证
|
||
- 检查是否只调用Business层服务
|
||
- 检查是否有统一的错误处理
|
||
- 检查文件类型是否符合Gateway层规范
|
||
|
||
3. **Business层检查(如果是Business层)**
|
||
- 检查是否只包含业务逻辑
|
||
- 检查是否协调多个Core层服务
|
||
- 检查是否返回统一的业务响应
|
||
- 检查是否不包含HTTP协议处理
|
||
|
||
4. **Core层检查(如果是Core层)**
|
||
- 检查Core层命名规范
|
||
- 业务支撑模块是否使用_core后缀
|
||
- 通用工具模块是否不使用后缀
|
||
- 根据模块职责判断命名正确性
|
||
- 检查是否只包含技术实现
|
||
|
||
5. **检查职责分离**
|
||
- Gateway层是否只做协议转换
|
||
- Business层是否只包含业务逻辑
|
||
- Core层是否只包含技术实现
|
||
- 是否有跨层职责混乱
|
||
|
||
6. **🔥 检查依赖注入完整性(关键步骤)**
|
||
- 扫描所有Injectable类的构造函数依赖
|
||
- 检查Module的imports是否包含所有依赖的Module
|
||
- 验证中间Module是否正确导出了需要的服务
|
||
- 确认依赖链的完整性和可访问性
|
||
- 识别并修复常见的依赖注入错误
|
||
|
||
7. **检查依赖关系**
|
||
- Gateway层是否只依赖Business层
|
||
- Business层是否只依赖Core层
|
||
- Core层是否不依赖业务层
|
||
- 依赖注入是否正确使用
|
||
|
||
8. **检查架构违规**
|
||
- 识别常见的分层违规模式
|
||
- 检查技术实现和业务逻辑的边界
|
||
- 检查协议处理和业务逻辑的边界
|
||
- 确保架构清晰度
|
||
|
||
9. **游戏服务器特殊检查**
|
||
- WebSocket Gateway的分层正确性
|
||
- 双模式服务的架构设计
|
||
- 实时通信组件的职责分离
|
||
|
||
10. **🚀 应用启动验证(强制步骤)**
|
||
- 执行 `pnpm dev` 或 `npm run dev` 启动应用
|
||
- 验证应用能够成功启动,无模块依赖错误
|
||
- 检查控制台是否有依赖注入失败的错误信息
|
||
- 如有启动错误,必须修复后重新验证
|
||
|
||
## 🚀 应用启动验证(强制要求)
|
||
|
||
### 为什么需要启动验证?
|
||
**静态代码检查无法发现所有的模块依赖问题!** 以下问题只有在应用启动时才会暴露:
|
||
|
||
1. **Module exports 配置错误**:导出了不属于当前模块的服务
|
||
2. **依赖注入链断裂**:中间模块未正确导出依赖
|
||
3. **循环依赖问题**:模块间存在循环引用
|
||
4. **Provider 注册遗漏**:服务未在正确的模块中注册
|
||
5. **CacheModule/ConfigModule 等全局模块缺失**
|
||
|
||
### 常见启动错误示例
|
||
|
||
#### 错误1:导出不属于当前模块的服务
|
||
```
|
||
UnknownExportException [Error]: Nest cannot export a provider/module that
|
||
is not a part of the currently processed module (ZulipModule).
|
||
Please verify whether the exported DynamicConfigManagerService is available
|
||
in this particular context.
|
||
```
|
||
|
||
**原因**:ZulipModule 尝试导出 DynamicConfigManagerService,但该服务来自 ZulipCoreModule,不是 ZulipModule 自己的 provider。
|
||
|
||
**修复方案**:
|
||
```typescript
|
||
// ❌ 错误:直接导出其他模块的服务
|
||
@Module({
|
||
imports: [ZulipCoreModule],
|
||
exports: [DynamicConfigManagerService], // 错误!
|
||
})
|
||
export class ZulipModule {}
|
||
|
||
// ✅ 正确:导出整个模块
|
||
@Module({
|
||
imports: [ZulipCoreModule],
|
||
exports: [ZulipCoreModule], // 正确:导出模块而非服务
|
||
})
|
||
export class ZulipModule {}
|
||
```
|
||
|
||
#### 错误2:依赖注入失败
|
||
```
|
||
Nest can't resolve dependencies of the JwtAuthGuard (?).
|
||
Please make sure that the argument LoginCoreService at index [0]
|
||
is available in the ZulipGatewayModule context.
|
||
```
|
||
|
||
**原因**:JwtAuthGuard 需要 LoginCoreService,但 ZulipGatewayModule 没有导入 LoginCoreModule。
|
||
|
||
**修复方案**:
|
||
```typescript
|
||
// ❌ 错误:缺少必要的模块导入
|
||
@Module({
|
||
imports: [ZulipModule, AuthModule],
|
||
providers: [JwtAuthGuard],
|
||
})
|
||
export class ZulipGatewayModule {}
|
||
|
||
// ✅ 正确:添加缺失的模块导入
|
||
@Module({
|
||
imports: [
|
||
ZulipModule,
|
||
AuthModule,
|
||
LoginCoreModule, // 添加:JwtAuthGuard 依赖 LoginCoreService
|
||
],
|
||
providers: [JwtAuthGuard],
|
||
})
|
||
export class ZulipGatewayModule {}
|
||
```
|
||
|
||
#### 错误3:CACHE_MANAGER 未注册
|
||
```
|
||
Nest can't resolve dependencies of the SomeService (?).
|
||
Please make sure that the argument "CACHE_MANAGER" at index [2]
|
||
is available in the SomeModule context.
|
||
```
|
||
|
||
**原因**:服务使用了 @Inject(CACHE_MANAGER),但模块未导入 CacheModule。
|
||
|
||
**修复方案**:
|
||
```typescript
|
||
// ❌ 错误:缺少 CacheModule
|
||
@Module({
|
||
imports: [OtherModule],
|
||
providers: [SomeService],
|
||
})
|
||
export class SomeModule {}
|
||
|
||
// ✅ 正确:添加 CacheModule
|
||
import { CacheModule } from '@nestjs/cache-manager';
|
||
|
||
@Module({
|
||
imports: [
|
||
CacheModule.register(), // 添加缓存模块
|
||
OtherModule,
|
||
],
|
||
providers: [SomeService],
|
||
})
|
||
export class SomeModule {}
|
||
```
|
||
|
||
### 启动验证执行流程
|
||
|
||
```bash
|
||
# 1. 执行启动命令
|
||
pnpm dev
|
||
# 或
|
||
npm run dev
|
||
|
||
# 2. 观察控制台输出,检查是否有以下错误类型:
|
||
# - UnknownExportException
|
||
# - Nest can't resolve dependencies
|
||
# - Circular dependency detected
|
||
# - Module not found
|
||
|
||
# 3. 如果启动成功,应该看到类似输出:
|
||
# [Nest] LOG [NestFactory] Starting Nest application...
|
||
# [Nest] LOG [RoutesResolver] AppController {/}: +Xms
|
||
# [Nest] LOG [NestApplication] Nest application successfully started +Xms
|
||
|
||
# 4. 验证健康检查接口
|
||
curl http://localhost:3000/health
|
||
# 应返回:{"status":"ok",...}
|
||
```
|
||
|
||
### 启动验证检查清单
|
||
|
||
- [ ] 执行 `pnpm dev` 或 `npm run dev`
|
||
- [ ] 确认无 UnknownExportException 错误
|
||
- [ ] 确认无依赖注入失败错误
|
||
- [ ] 确认无循环依赖错误
|
||
- [ ] 确认应用成功启动并监听端口
|
||
- [ ] 验证健康检查接口返回正常
|
||
- [ ] 如有错误,修复后重新启动验证
|
||
|
||
### 🚨 启动验证失败处理
|
||
|
||
**如果启动验证失败,必须:**
|
||
1. **分析错误信息**:识别具体的模块和依赖问题
|
||
2. **定位问题模块**:找到报错的 Module 文件
|
||
3. **修复依赖配置**:
|
||
- 添加缺失的 imports
|
||
- 修正错误的 exports
|
||
- 注册缺失的 providers
|
||
4. **重新启动验证**:修复后必须再次执行启动验证
|
||
5. **记录修改**:更新文件头部的修改记录
|
||
|
||
**🔥 重要:启动验证是步骤4的强制完成条件,不能跳过!**
|
||
|
||
## 🔥 重要提醒
|
||
|
||
**如果在本步骤中执行了任何修改操作(调整分层结构、修正依赖关系、重构代码等),必须立即重新执行步骤4的完整检查!**
|
||
|
||
- ✅ 执行修改 → 🔥 立即重新执行步骤4 → 提供验证报告 → 等待用户确认
|
||
- ❌ 执行修改 → 直接进入步骤5(错误做法)
|
||
|
||
**🚨 重要强调:纯检查步骤不更新修改记录**
|
||
**如果检查发现架构分层已经符合规范,无需任何修改,则:**
|
||
- ❌ **禁止添加检查记录**:不要添加"AI代码检查步骤4:架构分层检查和优化"
|
||
- ❌ **禁止更新时间戳**:不要修改@lastModified字段
|
||
- ❌ **禁止递增版本号**:不要修改@version字段
|
||
- ✅ **仅提供检查报告**:说明检查结果,确认符合规范
|
||
|
||
**🚀 步骤4完成的强制条件:**
|
||
1. **架构分层检查通过**:Gateway/Business/Core层职责清晰
|
||
2. **依赖注入检查通过**:所有Module的imports/exports配置正确
|
||
3. **🔥 应用启动验证通过**:执行 `pnpm dev` 应用能成功启动,无依赖错误
|
||
|
||
**不能跳过应用启动验证环节!如果启动失败,必须修复后重新执行整个步骤4!** |