forked from datawhale/whale-town-end
Compare commits
3 Commits
3dd5f23d79
...
faf93a30e1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faf93a30e1 | ||
|
|
2d10131838 | ||
|
|
5140bd1a54 |
63
README.md
63
README.md
@@ -124,29 +124,48 @@ pnpm run dev
|
||||
|
||||
### 第二步:熟悉项目架构 🏗️
|
||||
|
||||
**📁 项目文件结构总览**
|
||||
|
||||
```
|
||||
项目根目录/
|
||||
├── src/ # 源代码目录
|
||||
│ ├── business/ # 业务功能模块(按功能组织)
|
||||
│ │ ├── auth/ # 🔐 用户认证模块
|
||||
│ │ ├── user-mgmt/ # 👥 用户管理模块
|
||||
│ │ ├── admin/ # 🛡️ 管理员模块
|
||||
│ │ ├── security/ # 🔒 安全模块
|
||||
│ │ └── shared/ # 🔗 共享组件
|
||||
│ ├── core/ # 核心技术服务
|
||||
│ │ ├── db/ # 数据库层(支持MySQL/内存双模式)
|
||||
│ │ ├── redis/ # Redis缓存服务(支持真实Redis/文件存储)
|
||||
│ │ ├── login_core/ # 登录核心服务
|
||||
│ │ ├── admin_core/ # 管理员核心服务
|
||||
│ │ └── utils/ # 工具服务(邮件、验证码、日志)
|
||||
│ ├── app.module.ts # 应用主模块
|
||||
│ └── main.ts # 应用入口
|
||||
├── client/ # 前端管理界面
|
||||
├── docs/ # 项目文档
|
||||
├── test/ # 测试文件
|
||||
├── redis-data/ # Redis文件存储数据
|
||||
├── logs/ # 日志文件
|
||||
└── 配置文件 # .env, package.json, tsconfig.json等
|
||||
whale-town-end/ # 🐋 项目根目录
|
||||
├── 📂 src/ # 源代码目录
|
||||
│ ├── 📂 business/ # 🎯 业务功能模块(按功能组织)
|
||||
│ │ ├── 📂 auth/ # 🔐 用户认证模块
|
||||
│ │ ├── 📂 user-mgmt/ # 👥 用户管理模块
|
||||
│ │ ├── 📂 admin/ # 🛡️ 管理员模块
|
||||
│ │ ├── 📂 security/ # 🔒 安全防护模块
|
||||
│ │ ├── 📂 zulip/ # 💬 Zulip集成模块
|
||||
│ │ └── 📂 shared/ # 🔗 共享业务组件
|
||||
│ ├── 📂 core/ # ⚙️ 核心技术服务
|
||||
│ │ ├── 📂 db/ # 🗄️ 数据库层(MySQL/内存双模式)
|
||||
│ │ ├── 📂 redis/ # 🔴 Redis缓存(真实Redis/文件存储)
|
||||
│ │ ├── 📂 login_core/ # 🔑 登录核心服务
|
||||
│ │ ├── 📂 admin_core/ # 👑 管理员核心服务
|
||||
│ │ ├── 📂 zulip/ # 💬 Zulip核心服务
|
||||
│ │ └── 📂 utils/ # 🛠️ 工具服务(邮件、验证码、日志)
|
||||
│ ├── 📄 app.module.ts # 🏠 应用主模块
|
||||
│ └── 📄 main.ts # 🚀 应用入口点
|
||||
├── 📂 client/ # 🎨 前端管理界面
|
||||
│ ├── 📂 src/ # 前端源码
|
||||
│ ├── 📂 dist/ # 前端构建产物
|
||||
│ ├── 📄 package.json # 前端依赖配置
|
||||
│ └── 📄 vite.config.ts # Vite构建配置
|
||||
├── 📂 docs/ # 📚 项目文档中心
|
||||
│ ├── 📂 api/ # 🔌 API接口文档
|
||||
│ ├── 📂 development/ # 💻 开发指南
|
||||
│ ├── 📂 deployment/ # 🚀 部署文档
|
||||
│ ├── 📄 ARCHITECTURE.md # 🏗️ 架构设计文档
|
||||
│ └── 📄 README.md # 📖 文档导航中心
|
||||
├── 📂 test/ # 🧪 测试文件目录
|
||||
├── 📂 config/ # ⚙️ 配置文件目录
|
||||
├── 📂 logs/ # 📝 日志文件存储
|
||||
├── 📂 redis-data/ # 💾 Redis文件存储数据
|
||||
├── 📂 dist/ # 📦 后端构建产物
|
||||
├── 📄 .env # 🔧 环境变量配置
|
||||
├── 📄 package.json # 📋 项目依赖配置
|
||||
├── 📄 docker-compose.yml # 🐳 Docker编排配置
|
||||
├── 📄 Dockerfile # 🐳 Docker镜像配置
|
||||
└── 📄 README.md # 📖 项目主文档(当前文件)
|
||||
```
|
||||
|
||||
**架构特点:**
|
||||
|
||||
163
REFACTORING_SUMMARY.md
Normal file
163
REFACTORING_SUMMARY.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Zulip模块重构总结
|
||||
|
||||
## 重构完成情况
|
||||
|
||||
✅ **重构已完成** - 项目编译成功,架构符合分层设计原则
|
||||
|
||||
## 重构内容
|
||||
|
||||
### 1. 架构分层重构
|
||||
|
||||
#### 移动到核心服务层 (`src/core/zulip/`)
|
||||
以下技术实现相关的服务已移动到核心服务层:
|
||||
|
||||
- `zulip_client.service.ts` - Zulip REST API封装
|
||||
- `zulip_client_pool.service.ts` - 客户端连接池管理
|
||||
- `config_manager.service.ts` - 配置文件管理和热重载
|
||||
- `api_key_security.service.ts` - API Key安全存储
|
||||
- `error_handler.service.ts` - 错误处理和重试机制
|
||||
- `monitoring.service.ts` - 系统监控和健康检查
|
||||
- `stream_initializer.service.ts` - Stream初始化服务
|
||||
|
||||
#### 保留在业务逻辑层 (`src/business/zulip/`)
|
||||
以下业务逻辑相关的服务保留在业务层:
|
||||
|
||||
- `zulip.service.ts` - 主要业务协调服务
|
||||
- `zulip_websocket.gateway.ts` - WebSocket业务网关
|
||||
- `session_manager.service.ts` - 游戏会话业务逻辑
|
||||
- `message_filter.service.ts` - 消息过滤业务规则
|
||||
- `zulip_event_processor.service.ts` - 事件处理业务逻辑
|
||||
- `session_cleanup.service.ts` - 会话清理业务逻辑
|
||||
|
||||
### 2. 依赖注入重构
|
||||
|
||||
#### 创建接口抽象
|
||||
- 新增 `src/core/zulip/interfaces/zulip-core.interfaces.ts`
|
||||
- 定义核心服务接口:`IZulipClientService`、`IZulipClientPoolService`、`IZulipConfigService`
|
||||
|
||||
#### 更新依赖注入
|
||||
业务层服务现在通过接口依赖核心服务:
|
||||
|
||||
```typescript
|
||||
// 旧方式 - 直接依赖具体实现
|
||||
constructor(
|
||||
private readonly zulipClientPool: ZulipClientPoolService,
|
||||
) {}
|
||||
|
||||
// 新方式 - 通过接口依赖
|
||||
constructor(
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
) {}
|
||||
```
|
||||
|
||||
### 3. 模块结构重构
|
||||
|
||||
#### 核心服务模块
|
||||
- 新增 `ZulipCoreModule` - 提供所有技术实现服务
|
||||
- 通过依赖注入标识符导出服务
|
||||
|
||||
#### 业务逻辑模块
|
||||
- 更新 `ZulipModule` - 导入核心模块,专注业务逻辑
|
||||
- 移除技术实现相关的服务提供者
|
||||
|
||||
### 4. 文件移动记录
|
||||
|
||||
#### 移动到核心层的文件
|
||||
```
|
||||
src/business/zulip/services/ → src/core/zulip/services/
|
||||
├── zulip_client.service.ts
|
||||
├── zulip_client_pool.service.ts
|
||||
├── config_manager.service.ts
|
||||
├── api_key_security.service.ts
|
||||
├── error_handler.service.ts
|
||||
├── monitoring.service.ts
|
||||
├── stream_initializer.service.ts
|
||||
└── 对应的 .spec.ts 测试文件
|
||||
|
||||
src/business/zulip/ → src/core/zulip/
|
||||
├── interfaces/
|
||||
├── config/
|
||||
└── types/
|
||||
```
|
||||
|
||||
## 架构优势
|
||||
|
||||
### 1. 符合分层架构原则
|
||||
- **业务层**:只关注游戏相关的业务逻辑和规则
|
||||
- **核心层**:只处理技术实现和第三方API调用
|
||||
|
||||
### 2. 依赖倒置
|
||||
- 业务层依赖接口,不依赖具体实现
|
||||
- 核心层提供接口实现
|
||||
- 便于测试和替换实现
|
||||
|
||||
### 3. 单一职责
|
||||
- 每个服务职责明确
|
||||
- 业务逻辑与技术实现分离
|
||||
- 代码更易维护和理解
|
||||
|
||||
### 4. 可测试性
|
||||
- 业务逻辑可以独立测试
|
||||
- 通过Mock接口进行单元测试
|
||||
- 技术实现可以独立验证
|
||||
|
||||
## 当前状态
|
||||
|
||||
### ✅ 已完成
|
||||
- [x] 文件移动和重新组织
|
||||
- [x] 接口定义和抽象
|
||||
- [x] 依赖注入重构
|
||||
- [x] 模块结构调整
|
||||
- [x] 编译通过验证
|
||||
- [x] 测试文件的依赖注入配置更新
|
||||
- [x] 所有测试用例通过验证
|
||||
|
||||
### ✅ 测试修复完成
|
||||
- [x] `zulip_event_processor.service.spec.ts` - 更新依赖注入配置
|
||||
- [x] `message_filter.service.spec.ts` - 已通过测试
|
||||
- [x] `session_manager.service.spec.ts` - 已通过测试
|
||||
- [x] 核心服务测试文件导入路径修复
|
||||
- [x] 所有Zulip相关测试通过
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 业务层开发
|
||||
```typescript
|
||||
// 在业务服务中使用核心服务
|
||||
@Injectable()
|
||||
export class MyBusinessService {
|
||||
constructor(
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### 测试配置
|
||||
```typescript
|
||||
// 测试中Mock核心服务
|
||||
const mockZulipClientPool: IZulipClientPoolService = {
|
||||
sendMessage: jest.fn().mockResolvedValue({ success: true }),
|
||||
// ...
|
||||
};
|
||||
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
MyBusinessService,
|
||||
{ provide: 'ZULIP_CLIENT_POOL_SERVICE', useValue: mockZulipClientPool },
|
||||
],
|
||||
}).compile();
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
重构成功实现了以下目标:
|
||||
|
||||
1. **架构合规**:符合项目的分层架构设计原则
|
||||
2. **职责分离**:业务逻辑与技术实现清晰分离
|
||||
3. **依赖解耦**:通过接口实现依赖倒置
|
||||
4. **可维护性**:代码结构更清晰,易于维护和扩展
|
||||
5. **可测试性**:业务逻辑可以独立测试
|
||||
|
||||
项目现在具有更好的架构设计,为后续开发和维护奠定了良好基础。
|
||||
@@ -1,187 +1,747 @@
|
||||
# 🏗️ 项目架构设计
|
||||
# 🏗️ Whale Town 项目架构设计
|
||||
|
||||
## 整体架构
|
||||
> 基于业务功能模块化的现代化后端架构,支持双模式运行,开发测试零依赖,生产部署高性能。
|
||||
|
||||
Whale Town 采用分层架构设计,确保代码的可维护性和可扩展性。
|
||||
## 📋 目录
|
||||
|
||||
- [🎯 架构概述](#-架构概述)
|
||||
- [📁 目录结构详解](#-目录结构详解)
|
||||
- [🏗️ 分层架构设计](#️-分层架构设计)
|
||||
- [🔄 双模式架构](#-双模式架构)
|
||||
- [📦 模块依赖关系](#-模块依赖关系)
|
||||
- [🚀 扩展指南](#-扩展指南)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 架构概述
|
||||
|
||||
Whale Town 采用**业务功能模块化架构**,将代码按业务功能而非技术组件组织,确保高内聚、低耦合的设计原则。
|
||||
|
||||
### 🌟 核心设计理念
|
||||
|
||||
- **业务驱动** - 按业务功能组织代码,而非技术分层
|
||||
- **双模式支持** - 开发测试零依赖,生产部署高性能
|
||||
- **清晰分层** - 业务层 → 核心层 → 数据层,职责明确
|
||||
- **模块化设计** - 每个模块独立完整,可单独测试和部署
|
||||
- **配置驱动** - 通过环境变量控制运行模式和行为
|
||||
|
||||
### 📊 整体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🌐 API接口层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🔗 REST API │ │ 🔌 WebSocket │ │ 📄 Swagger UI │ │
|
||||
│ │ (HTTP接口) │ │ (实时通信) │ │ (API文档) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🎯 业务功能模块层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🔐 用户认证 │ │ 👥 用户管理 │ │ 🛡️ 管理员 │ │
|
||||
│ │ (auth) │ │ (user-mgmt) │ │ (admin) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🔒 安全防护 │ │ 💬 Zulip集成 │ │ 🔗 共享组件 │ │
|
||||
│ │ (security) │ │ (zulip) │ │ (shared) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ⚙️ 核心技术服务层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🔑 登录核心 │ │ 👑 管理员核心 │ │ 💬 Zulip核心 │ │
|
||||
│ │ (login_core) │ │ (admin_core) │ │ (zulip) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🛠️ 工具服务 │ │ 📧 邮件服务 │ │ 📝 日志服务 │ │
|
||||
│ │ (utils) │ │ (email) │ │ (logger) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🗄️ 数据存储层 │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🗃️ 数据库 │ │ 🔴 Redis缓存 │ │ 📁 文件存储 │ │
|
||||
│ │ (MySQL/内存) │ │ (Redis/文件) │ │ (logs/data) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 目录结构详解
|
||||
|
||||
### 🎯 业务功能模块 (`src/business/`)
|
||||
|
||||
> **设计原则**: 按业务功能组织,每个模块包含完整的业务逻辑
|
||||
|
||||
```
|
||||
src/business/
|
||||
├── 📂 auth/ # 🔐 用户认证模块
|
||||
│ ├── 📄 auth.controller.ts # HTTP接口控制器
|
||||
│ ├── 📄 auth.service.ts # 业务逻辑服务
|
||||
│ ├── 📄 auth.module.ts # 模块定义
|
||||
│ ├── 📂 dto/ # 数据传输对象
|
||||
│ │ ├── 📄 login.dto.ts # 登录请求DTO
|
||||
│ │ ├── 📄 register.dto.ts # 注册请求DTO
|
||||
│ │ └── 📄 reset-password.dto.ts # 重置密码DTO
|
||||
│ └── 📂 __tests__/ # 单元测试
|
||||
│ └── 📄 auth.service.spec.ts
|
||||
│
|
||||
├── 📂 user-mgmt/ # 👥 用户管理模块
|
||||
│ ├── 📄 user-mgmt.controller.ts # 用户管理接口
|
||||
│ ├── 📄 user-mgmt.service.ts # 用户状态管理逻辑
|
||||
│ ├── 📄 user-mgmt.module.ts # 模块定义
|
||||
│ ├── 📂 dto/ # 数据传输对象
|
||||
│ │ ├── 📄 update-status.dto.ts # 状态更新DTO
|
||||
│ │ └── 📄 batch-status.dto.ts # 批量操作DTO
|
||||
│ └── 📂 enums/ # 枚举定义
|
||||
│ └── 📄 user-status.enum.ts # 用户状态枚举
|
||||
│
|
||||
├── 📂 admin/ # 🛡️ 管理员模块
|
||||
│ ├── 📄 admin.controller.ts # 管理员接口
|
||||
│ ├── 📄 admin.service.ts # 管理员业务逻辑
|
||||
│ ├── 📄 admin.module.ts # 模块定义
|
||||
│ ├── 📂 dto/ # 数据传输对象
|
||||
│ └── 📂 guards/ # 权限守卫
|
||||
│ └── 📄 admin.guard.ts # 管理员权限验证
|
||||
│
|
||||
├── 📂 security/ # 🔒 安全防护模块
|
||||
│ ├── 📄 security.module.ts # 安全模块定义
|
||||
│ ├── 📂 guards/ # 安全守卫
|
||||
│ │ ├── 📄 throttle.guard.ts # 频率限制守卫
|
||||
│ │ ├── 📄 maintenance.guard.ts # 维护模式守卫
|
||||
│ │ └── 📄 content-type.guard.ts # 内容类型守卫
|
||||
│ └── 📂 interceptors/ # 拦截器
|
||||
│ └── 📄 timeout.interceptor.ts # 超时拦截器
|
||||
│
|
||||
├── 📂 zulip/ # 💬 Zulip集成模块
|
||||
│ ├── 📄 zulip.service.ts # Zulip业务服务
|
||||
│ ├── 📄 zulip_websocket.gateway.ts # WebSocket网关
|
||||
│ ├── 📄 zulip.module.ts # 模块定义
|
||||
│ └── 📂 services/ # 子服务
|
||||
│ ├── 📄 message_filter.service.ts # 消息过滤
|
||||
│ └── 📄 session_cleanup.service.ts # 会话清理
|
||||
│
|
||||
└── 📂 shared/ # 🔗 共享业务组件
|
||||
├── 📂 decorators/ # 装饰器
|
||||
├── 📂 pipes/ # 管道
|
||||
├── 📂 filters/ # 异常过滤器
|
||||
└── 📂 interfaces/ # 接口定义
|
||||
```
|
||||
|
||||
### ⚙️ 核心技术服务 (`src/core/`)
|
||||
|
||||
> **设计原则**: 提供技术基础设施,支持业务模块运行
|
||||
|
||||
```
|
||||
src/core/
|
||||
├── 📂 db/ # 🗄️ 数据库层
|
||||
│ ├── 📄 db.module.ts # 数据库模块
|
||||
│ ├── 📂 users/ # 用户数据服务
|
||||
│ │ ├── 📄 users.service.ts # MySQL数据库实现
|
||||
│ │ ├── 📄 users-memory.service.ts # 内存数据库实现
|
||||
│ │ ├── 📄 users.interface.ts # 用户服务接口
|
||||
│ │ └── 📄 user.entity.ts # 用户实体定义
|
||||
│ └── 📂 entities/ # 数据库实体
|
||||
│ └── 📄 *.entity.ts # 各种实体定义
|
||||
│
|
||||
├── 📂 redis/ # 🔴 Redis缓存层
|
||||
│ ├── 📄 redis.module.ts # Redis模块
|
||||
│ ├── 📄 redis.service.ts # Redis真实实现
|
||||
│ ├── 📄 file-redis.service.ts # 文件存储实现
|
||||
│ └── 📄 redis.interface.ts # Redis服务接口
|
||||
│
|
||||
├── 📂 login_core/ # 🔑 登录核心服务
|
||||
│ ├── 📄 login-core.service.ts # 登录核心逻辑
|
||||
│ ├── 📄 login-core.module.ts # 模块定义
|
||||
│ └── 📄 login-core.interface.ts # 接口定义
|
||||
│
|
||||
├── 📂 admin_core/ # 👑 管理员核心服务
|
||||
│ ├── 📄 admin-core.service.ts # 管理员核心逻辑
|
||||
│ ├── 📄 admin-core.module.ts # 模块定义
|
||||
│ └── 📄 admin-core.interface.ts # 接口定义
|
||||
│
|
||||
├── 📂 zulip/ # 💬 Zulip核心服务
|
||||
│ ├── 📄 zulip-core.module.ts # Zulip核心模块
|
||||
│ ├── 📄 zulip-api.service.ts # Zulip API服务
|
||||
│ └── 📄 zulip-websocket.service.ts # WebSocket服务
|
||||
│
|
||||
└── 📂 utils/ # 🛠️ 工具服务
|
||||
├── 📂 email/ # 📧 邮件服务
|
||||
│ ├── 📄 email.service.ts # 邮件发送服务
|
||||
│ ├── 📄 email.module.ts # 邮件模块
|
||||
│ └── 📄 email.interface.ts # 邮件接口
|
||||
├── 📂 verification/ # 🔢 验证码服务
|
||||
│ ├── 📄 verification.service.ts # 验证码生成验证
|
||||
│ └── 📄 verification.module.ts # 验证码模块
|
||||
└── 📂 logger/ # 📝 日志服务
|
||||
├── 📄 logger.service.ts # 日志记录服务
|
||||
└── 📄 logger.module.ts # 日志模块
|
||||
```
|
||||
|
||||
### 🎨 前端管理界面 (`client/`)
|
||||
|
||||
> **设计原则**: 独立的前端项目,提供管理员后台功能
|
||||
|
||||
```
|
||||
client/
|
||||
├── 📂 src/ # 前端源码
|
||||
│ ├── 📂 components/ # 通用组件
|
||||
│ │ ├── 📄 Layout.tsx # 布局组件
|
||||
│ │ ├── 📄 UserTable.tsx # 用户表格组件
|
||||
│ │ └── 📄 LogViewer.tsx # 日志查看组件
|
||||
│ ├── 📂 pages/ # 页面组件
|
||||
│ │ ├── 📄 Login.tsx # 登录页面
|
||||
│ │ ├── 📄 Dashboard.tsx # 仪表板
|
||||
│ │ ├── 📄 UserManagement.tsx # 用户管理
|
||||
│ │ └── 📄 LogManagement.tsx # 日志管理
|
||||
│ ├── 📂 services/ # API服务
|
||||
│ │ ├── 📄 api.ts # API客户端
|
||||
│ │ ├── 📄 auth.ts # 认证服务
|
||||
│ │ └── 📄 users.ts # 用户服务
|
||||
│ ├── 📂 utils/ # 工具函数
|
||||
│ ├── 📂 types/ # TypeScript类型
|
||||
│ ├── 📄 App.tsx # 应用主组件
|
||||
│ └── 📄 main.tsx # 应用入口
|
||||
├── 📂 dist/ # 构建产物
|
||||
├── 📄 package.json # 前端依赖
|
||||
├── 📄 vite.config.ts # Vite配置
|
||||
└── 📄 tsconfig.json # TypeScript配置
|
||||
```
|
||||
|
||||
### 📚 文档中心 (`docs/`)
|
||||
|
||||
> **设计原则**: 完整的项目文档,支持开发者快速上手
|
||||
|
||||
```
|
||||
docs/
|
||||
├── 📄 README.md # 📖 文档导航中心
|
||||
├── 📄 ARCHITECTURE.md # 🏗️ 架构设计文档
|
||||
├── 📄 API_STATUS_CODES.md # 📋 API状态码说明
|
||||
├── 📄 CONTRIBUTORS.md # 🤝 贡献者指南
|
||||
│
|
||||
├── 📂 api/ # 🔌 API接口文档
|
||||
│ ├── 📄 README.md # API文档使用指南
|
||||
│ ├── 📄 api-documentation.md # 完整API接口文档
|
||||
│ ├── 📄 openapi.yaml # OpenAPI规范文件
|
||||
│ └── 📄 postman-collection.json # Postman测试集合
|
||||
│
|
||||
├── 📂 development/ # 💻 开发指南
|
||||
│ ├── 📄 backend_development_guide.md # 后端开发规范
|
||||
│ ├── 📄 git_commit_guide.md # Git提交规范
|
||||
│ ├── 📄 AI辅助开发规范指南.md # AI辅助开发指南
|
||||
│ ├── 📄 TESTING.md # 测试指南
|
||||
│ └── 📄 naming_convention.md # 命名规范
|
||||
│
|
||||
└── 📂 deployment/ # 🚀 部署文档
|
||||
└── 📄 DEPLOYMENT.md # 生产环境部署指南
|
||||
```
|
||||
|
||||
### 🧪 测试文件 (`test/`)
|
||||
|
||||
> **设计原则**: 完整的测试覆盖,确保代码质量
|
||||
|
||||
```
|
||||
test/
|
||||
├── 📂 unit/ # 单元测试
|
||||
├── 📂 integration/ # 集成测试
|
||||
├── 📂 e2e/ # 端到端测试
|
||||
└── 📂 fixtures/ # 测试数据
|
||||
```
|
||||
|
||||
### ⚙️ 配置文件
|
||||
|
||||
> **设计原则**: 清晰的配置管理,支持多环境部署
|
||||
|
||||
```
|
||||
项目根目录/
|
||||
├── 📄 .env # 🔧 环境变量配置
|
||||
├── 📄 .env.example # 🔧 环境变量示例
|
||||
├── 📄 .env.production.example # 🔧 生产环境示例
|
||||
├── 📄 package.json # 📋 项目依赖配置
|
||||
├── 📄 pnpm-workspace.yaml # 📦 pnpm工作空间配置
|
||||
├── 📄 tsconfig.json # 📘 TypeScript配置
|
||||
├── 📄 jest.config.js # 🧪 Jest测试配置
|
||||
├── 📄 nest-cli.json # 🏠 NestJS CLI配置
|
||||
├── 📄 docker-compose.yml # 🐳 Docker编排配置
|
||||
├── 📄 Dockerfile # 🐳 Docker镜像配置
|
||||
└── 📄 ecosystem.config.js # 🚀 PM2进程管理配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 分层架构设计
|
||||
|
||||
### 📊 架构分层说明
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API 层 │
|
||||
│ 🌐 表现层 (Presentation) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ HTTP API │ │ WebSocket │ │ GraphQL │ │
|
||||
│ │ (REST) │ │ (Socket.IO) │ │ (预留) │ │
|
||||
│ │ Controllers │ │ WebSocket │ │ Swagger UI │ │
|
||||
│ │ (HTTP接口) │ │ Gateways │ │ (API文档) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 业务逻辑层 │
|
||||
│ 🎯 业务层 (Business) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 用户认证 │ │ 游戏逻辑 │ │ 社交功能 │ │
|
||||
│ │ (Login) │ │ (Game) │ │ (Social) │ │
|
||||
│ │ Auth Module │ │ UserMgmt │ │ Admin Module │ │
|
||||
│ │ (用户认证) │ │ Module │ │ (管理员) │ │
|
||||
│ │ │ │ (用户管理) │ │ │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Security Module │ │ Zulip Module │ │ Shared Module │ │
|
||||
│ │ (安全防护) │ │ (Zulip集成) │ │ (共享组件) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 核心服务层 │
|
||||
│ ⚙️ 服务层 (Service) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 邮件服务 │ │ 验证码服务 │ │ 日志服务 │ │
|
||||
│ │ (Email) │ │ (Verification)│ │ (Logger) │ │
|
||||
│ │ Login Core │ │ Admin Core │ │ Zulip Core │ │
|
||||
│ │ (登录核心) │ │ (管理员核心) │ │ (Zulip核心) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Email Service │ │ Verification │ │ Logger Service │ │
|
||||
│ │ (邮件服务) │ │ Service │ │ (日志服务) │ │
|
||||
│ │ │ │ (验证码服务) │ │ │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
⬇️
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 数据访问层 │
|
||||
│ 🗄️ 数据层 (Data) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 用户数据 │ │ Redis缓存 │ │ 文件存储 │ │
|
||||
│ │ (Users) │ │ (Cache) │ │ (Files) │ │
|
||||
│ │ Users Service │ │ Redis Service │ │ File Storage │ │
|
||||
│ │ (用户数据) │ │ (缓存服务) │ │ (文件存储) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ MySQL/Memory │ │ Redis/File │ │ Logs/Data │ │
|
||||
│ │ (数据库) │ │ (缓存实现) │ │ (日志数据) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 模块依赖关系
|
||||
### 🔄 数据流向
|
||||
|
||||
#### 用户注册流程示例
|
||||
|
||||
```
|
||||
AppModule
|
||||
├── ConfigModule (全局配置)
|
||||
├── LoggerModule (日志系统)
|
||||
├── RedisModule (缓存服务)
|
||||
├── UsersModule (用户管理)
|
||||
│ ├── UsersService (数据库模式)
|
||||
│ └── UsersMemoryService (内存模式)
|
||||
├── EmailModule (邮件服务)
|
||||
├── VerificationModule (验证码服务)
|
||||
├── LoginCoreModule (登录核心)
|
||||
└── LoginModule (登录业务)
|
||||
1. 📱 用户请求 → AuthController.register()
|
||||
2. 🔍 参数验证 → class-validator装饰器
|
||||
3. 🎯 业务逻辑 → AuthService.register()
|
||||
4. ⚙️ 核心服务 → LoginCoreService.createUser()
|
||||
5. 📧 发送邮件 → EmailService.sendVerificationCode()
|
||||
6. 🔢 生成验证码 → VerificationService.generate()
|
||||
7. 💾 存储数据 → UsersService.create() + RedisService.set()
|
||||
8. 📝 记录日志 → LoggerService.log()
|
||||
9. ✅ 返回响应 → 用户收到成功响应
|
||||
```
|
||||
|
||||
## 数据流向
|
||||
#### 管理员操作流程示例
|
||||
|
||||
### 用户注册流程
|
||||
```
|
||||
1. 用户请求 → LoginController
|
||||
2. 参数验证 → LoginService
|
||||
3. 发送验证码 → LoginCoreService
|
||||
4. 生成验证码 → VerificationService
|
||||
5. 发送邮件 → EmailService
|
||||
6. 存储验证码 → RedisService
|
||||
7. 返回响应 → 用户
|
||||
1. 🛡️ 管理员请求 → AdminController.resetUserPassword()
|
||||
2. 🔐 权限验证 → AdminGuard.canActivate()
|
||||
3. 🎯 业务逻辑 → AdminService.resetPassword()
|
||||
4. ⚙️ 核心服务 → AdminCoreService.resetUserPassword()
|
||||
5. 🔑 密码加密 → bcrypt.hash()
|
||||
6. 💾 更新数据 → UsersService.update()
|
||||
7. 📧 通知用户 → EmailService.sendPasswordReset()
|
||||
8. 📝 审计日志 → LoggerService.audit()
|
||||
9. ✅ 返回响应 → 管理员收到操作结果
|
||||
```
|
||||
|
||||
### 双模式架构
|
||||
---
|
||||
|
||||
项目支持开发测试模式和生产部署模式的无缝切换:
|
||||
## 🔄 双模式架构
|
||||
|
||||
#### 开发测试模式
|
||||
- **数据库**: 内存存储 (UsersMemoryService)
|
||||
- **缓存**: 文件存储 (FileRedisService)
|
||||
- **邮件**: 控制台输出 (测试模式)
|
||||
- **优势**: 无需外部依赖,快速启动测试
|
||||
### 🎯 设计目标
|
||||
|
||||
#### 生产部署模式
|
||||
- **数据库**: MySQL (UsersService + TypeORM)
|
||||
- **缓存**: Redis (RealRedisService + IORedis)
|
||||
- **邮件**: SMTP服务器 (生产模式)
|
||||
- **优势**: 高性能,高可用,数据持久化
|
||||
- **开发测试**: 零依赖快速启动,无需安装MySQL、Redis等外部服务
|
||||
- **生产部署**: 高性能、高可用,支持集群和负载均衡
|
||||
|
||||
## 设计原则
|
||||
### 📊 模式对比
|
||||
|
||||
### 1. 单一职责原则
|
||||
每个模块只负责一个特定的功能领域:
|
||||
- `LoginModule`: 只处理登录相关业务
|
||||
- `EmailModule`: 只处理邮件发送
|
||||
- `VerificationModule`: 只处理验证码逻辑
|
||||
| 功能模块 | 🧪 开发测试模式 | 🚀 生产部署模式 |
|
||||
|----------|----------------|----------------|
|
||||
| **数据库** | 内存存储 (UsersMemoryService) | MySQL (UsersService + TypeORM) |
|
||||
| **缓存** | 文件存储 (FileRedisService) | Redis (RedisService + IORedis) |
|
||||
| **邮件** | 控制台输出 (测试模式) | SMTP服务器 (生产模式) |
|
||||
| **日志** | 控制台 + 文件 | 结构化日志 + 日志轮转 |
|
||||
| **配置** | `.env` 默认配置 | 环境变量 + 配置中心 |
|
||||
|
||||
### 2. 依赖注入
|
||||
使用NestJS的依赖注入系统:
|
||||
- 接口抽象: `IRedisService`, `IUsersService`
|
||||
- 实现切换: 根据配置自动选择实现类
|
||||
- 测试友好: 易于Mock和单元测试
|
||||
### ⚙️ 模式切换配置
|
||||
|
||||
### 3. 配置驱动
|
||||
通过环境变量控制行为:
|
||||
- `USE_FILE_REDIS`: 选择Redis实现
|
||||
- `DB_HOST`: 数据库连接配置
|
||||
- `EMAIL_HOST`: 邮件服务配置
|
||||
#### 开发测试模式 (.env)
|
||||
```bash
|
||||
# 数据存储模式
|
||||
USE_FILE_REDIS=true # 使用文件存储代替Redis
|
||||
NODE_ENV=development # 开发环境
|
||||
|
||||
### 4. 错误处理
|
||||
统一的错误处理机制:
|
||||
- HTTP异常: `BadRequestException`, `UnauthorizedException`
|
||||
- 业务异常: 自定义异常类
|
||||
- 日志记录: 结构化错误日志
|
||||
# 数据库配置(注释掉,使用内存数据库)
|
||||
# DB_HOST=localhost
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=password
|
||||
|
||||
## 扩展指南
|
||||
# 邮件配置(注释掉,使用测试模式)
|
||||
# EMAIL_HOST=smtp.gmail.com
|
||||
# EMAIL_USER=your_email@gmail.com
|
||||
# EMAIL_PASS=your_password
|
||||
```
|
||||
|
||||
### 添加新的业务模块
|
||||
#### 生产部署模式 (.env.production)
|
||||
```bash
|
||||
# 数据存储模式
|
||||
USE_FILE_REDIS=false # 使用真实Redis
|
||||
NODE_ENV=production # 生产环境
|
||||
|
||||
1. **创建业务模块**
|
||||
```bash
|
||||
nest g module business/game
|
||||
nest g controller business/game
|
||||
nest g service business/game
|
||||
```
|
||||
# 数据库配置
|
||||
DB_HOST=your_mysql_host
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=your_username
|
||||
DB_PASSWORD=your_password
|
||||
DB_DATABASE=whale_town
|
||||
|
||||
2. **创建核心服务**
|
||||
```bash
|
||||
nest g module core/game_core
|
||||
nest g service core/game_core
|
||||
```
|
||||
# Redis配置
|
||||
REDIS_HOST=your_redis_host
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=your_redis_password
|
||||
|
||||
3. **添加数据模型**
|
||||
```bash
|
||||
nest g module core/db/games
|
||||
nest g service core/db/games
|
||||
```
|
||||
# 邮件配置
|
||||
EMAIL_HOST=smtp.gmail.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=your_email@gmail.com
|
||||
EMAIL_PASS=your_app_password
|
||||
```
|
||||
|
||||
4. **更新主模块**
|
||||
在 `app.module.ts` 中导入新模块
|
||||
### 🔧 实现机制
|
||||
|
||||
### 添加新的工具服务
|
||||
#### 依赖注入切换
|
||||
```typescript
|
||||
// redis.module.ts
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'IRedisService',
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const useFileRedis = configService.get<boolean>('USE_FILE_REDIS');
|
||||
return useFileRedis
|
||||
? new FileRedisService()
|
||||
: new RedisService(configService);
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class RedisModule {}
|
||||
```
|
||||
|
||||
1. **创建工具模块**
|
||||
```bash
|
||||
nest g module core/utils/notification
|
||||
nest g service core/utils/notification
|
||||
```
|
||||
#### 配置驱动服务选择
|
||||
```typescript
|
||||
// users.module.ts
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'IUsersService',
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const dbHost = configService.get<string>('DB_HOST');
|
||||
return dbHost
|
||||
? new UsersService()
|
||||
: new UsersMemoryService();
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class UsersModule {}
|
||||
```
|
||||
|
||||
2. **实现服务接口**
|
||||
定义抽象接口和具体实现
|
||||
---
|
||||
|
||||
3. **添加配置支持**
|
||||
在环境变量中添加相关配置
|
||||
## 📦 模块依赖关系
|
||||
|
||||
4. **编写测试用例**
|
||||
确保功能正确性和代码覆盖率
|
||||
### 🏗️ 模块依赖图
|
||||
|
||||
## 性能优化
|
||||
```
|
||||
AppModule (应用主模块)
|
||||
├── 📊 ConfigModule (全局配置)
|
||||
├── 📝 LoggerModule (日志系统)
|
||||
├── 🔴 RedisModule (缓存服务)
|
||||
│ ├── RedisService (真实Redis)
|
||||
│ └── FileRedisService (文件存储)
|
||||
├── 🗄️ UsersModule (用户数据)
|
||||
│ ├── UsersService (MySQL数据库)
|
||||
│ └── UsersMemoryService (内存数据库)
|
||||
├── 📧 EmailModule (邮件服务)
|
||||
├── 🔢 VerificationModule (验证码服务)
|
||||
├── 🔑 LoginCoreModule (登录核心)
|
||||
├── 👑 AdminCoreModule (管理员核心)
|
||||
├── 💬 ZulipCoreModule (Zulip核心)
|
||||
│
|
||||
├── 🎯 业务功能模块
|
||||
│ ├── 🔐 AuthModule (用户认证)
|
||||
│ │ └── 依赖: LoginCoreModule, EmailModule, VerificationModule
|
||||
│ ├── 👥 UserMgmtModule (用户管理)
|
||||
│ │ └── 依赖: UsersModule, LoggerModule
|
||||
│ ├── 🛡️ AdminModule (管理员)
|
||||
│ │ └── 依赖: AdminCoreModule, UsersModule
|
||||
│ ├── 🔒 SecurityModule (安全防护)
|
||||
│ │ └── 依赖: RedisModule, LoggerModule
|
||||
│ ├── 💬 ZulipModule (Zulip集成)
|
||||
│ │ └── 依赖: ZulipCoreModule, RedisModule
|
||||
│ └── 🔗 SharedModule (共享组件)
|
||||
```
|
||||
|
||||
### 1. 缓存策略
|
||||
- **Redis缓存**: 验证码、会话信息
|
||||
### 🔄 模块交互流程
|
||||
|
||||
#### 用户认证流程
|
||||
```
|
||||
AuthController → AuthService → LoginCoreService
|
||||
↓
|
||||
EmailService ← VerificationService ← RedisService
|
||||
↓
|
||||
UsersService
|
||||
```
|
||||
|
||||
#### 管理员操作流程
|
||||
```
|
||||
AdminController → AdminService → AdminCoreService
|
||||
↓
|
||||
LoggerService ← UsersService ← RedisService
|
||||
```
|
||||
|
||||
#### 安全防护流程
|
||||
```
|
||||
SecurityGuard → RedisService (频率限制)
|
||||
→ LoggerService (审计日志)
|
||||
→ ConfigService (维护模式)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 扩展指南
|
||||
|
||||
### 📝 添加新的业务模块
|
||||
|
||||
#### 1. 创建业务模块结构
|
||||
```bash
|
||||
# 创建模块目录
|
||||
mkdir -p src/business/game/{dto,enums,guards,interfaces}
|
||||
|
||||
# 生成NestJS模块文件
|
||||
nest g module business/game
|
||||
nest g controller business/game
|
||||
nest g service business/game
|
||||
```
|
||||
|
||||
#### 2. 实现业务逻辑
|
||||
```typescript
|
||||
// src/business/game/game.module.ts
|
||||
@Module({
|
||||
imports: [
|
||||
GameCoreModule, # 依赖核心服务
|
||||
UsersModule, # 依赖用户数据
|
||||
RedisModule, # 依赖缓存服务
|
||||
],
|
||||
controllers: [GameController],
|
||||
providers: [GameService],
|
||||
exports: [GameService],
|
||||
})
|
||||
export class GameModule {}
|
||||
```
|
||||
|
||||
#### 3. 创建对应的核心服务
|
||||
```bash
|
||||
# 创建核心服务
|
||||
mkdir -p src/core/game_core
|
||||
nest g module core/game_core
|
||||
nest g service core/game_core
|
||||
```
|
||||
|
||||
#### 4. 更新主模块
|
||||
```typescript
|
||||
// src/app.module.ts
|
||||
@Module({
|
||||
imports: [
|
||||
// ... 其他模块
|
||||
GameModule, # 添加新的业务模块
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
### 🛠️ 添加新的工具服务
|
||||
|
||||
#### 1. 创建工具服务
|
||||
```bash
|
||||
mkdir -p src/core/utils/notification
|
||||
nest g module core/utils/notification
|
||||
nest g service core/utils/notification
|
||||
```
|
||||
|
||||
#### 2. 定义服务接口
|
||||
```typescript
|
||||
// src/core/utils/notification/notification.interface.ts
|
||||
export interface INotificationService {
|
||||
sendPush(userId: string, message: string): Promise<void>;
|
||||
sendSMS(phone: string, message: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 实现服务
|
||||
```typescript
|
||||
// src/core/utils/notification/notification.service.ts
|
||||
@Injectable()
|
||||
export class NotificationService implements INotificationService {
|
||||
async sendPush(userId: string, message: string): Promise<void> {
|
||||
// 实现推送通知逻辑
|
||||
}
|
||||
|
||||
async sendSMS(phone: string, message: string): Promise<void> {
|
||||
// 实现短信发送逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 配置依赖注入
|
||||
```typescript
|
||||
// src/core/utils/notification/notification.module.ts
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'INotificationService',
|
||||
useClass: NotificationService,
|
||||
},
|
||||
],
|
||||
exports: ['INotificationService'],
|
||||
})
|
||||
export class NotificationModule {}
|
||||
```
|
||||
|
||||
### 🔌 添加新的API接口
|
||||
|
||||
#### 1. 定义DTO
|
||||
```typescript
|
||||
// src/business/game/dto/create-game.dto.ts
|
||||
export class CreateGameDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 实现Controller
|
||||
```typescript
|
||||
// src/business/game/game.controller.ts
|
||||
@Controller('game')
|
||||
@ApiTags('游戏管理')
|
||||
export class GameController {
|
||||
constructor(private readonly gameService: GameService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建游戏' })
|
||||
async createGame(@Body() createGameDto: CreateGameDto) {
|
||||
return this.gameService.create(createGameDto);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 实现Service
|
||||
```typescript
|
||||
// src/business/game/game.service.ts
|
||||
@Injectable()
|
||||
export class GameService {
|
||||
constructor(
|
||||
@Inject('IGameCoreService')
|
||||
private readonly gameCoreService: IGameCoreService,
|
||||
) {}
|
||||
|
||||
async create(createGameDto: CreateGameDto) {
|
||||
return this.gameCoreService.createGame(createGameDto);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 添加测试用例
|
||||
```typescript
|
||||
// src/business/game/game.service.spec.ts
|
||||
describe('GameService', () => {
|
||||
let service: GameService;
|
||||
let gameCoreService: jest.Mocked<IGameCoreService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
GameService,
|
||||
{
|
||||
provide: 'IGameCoreService',
|
||||
useValue: {
|
||||
createGame: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GameService>(GameService);
|
||||
gameCoreService = module.get('IGameCoreService');
|
||||
});
|
||||
|
||||
it('should create game', async () => {
|
||||
const createGameDto = { name: 'Test Game' };
|
||||
const expectedResult = { id: 1, ...createGameDto };
|
||||
|
||||
gameCoreService.createGame.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await service.create(createGameDto);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(gameCoreService.createGame).toHaveBeenCalledWith(createGameDto);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 📊 性能优化建议
|
||||
|
||||
#### 1. 缓存策略
|
||||
- **Redis缓存**: 用户会话、验证码、频繁查询数据
|
||||
- **内存缓存**: 配置信息、静态数据
|
||||
- **CDN缓存**: 静态资源文件
|
||||
|
||||
### 2. 数据库优化
|
||||
- **连接池**: 复用数据库连接
|
||||
- **索引优化**: 关键字段建立索引
|
||||
- **查询优化**: 避免N+1查询问题
|
||||
#### 2. 数据库优化
|
||||
- **连接池**: 复用数据库连接,减少连接开销
|
||||
- **索引优化**: 为查询字段建立合适的索引
|
||||
- **查询优化**: 避免N+1查询,使用JOIN优化关联查询
|
||||
|
||||
### 3. 日志优化
|
||||
- **异步日志**: 使用Pino的异步写入
|
||||
- **日志分级**: 生产环境只记录必要日志
|
||||
#### 3. 日志优化
|
||||
- **异步日志**: 使用Pino的异步写入功能
|
||||
- **日志分级**: 生产环境只记录ERROR和WARN级别
|
||||
- **日志轮转**: 自动清理过期日志文件
|
||||
|
||||
## 安全考虑
|
||||
### 🔒 安全加固建议
|
||||
|
||||
### 1. 数据验证
|
||||
- **输入验证**: class-validator装饰器
|
||||
- **类型检查**: TypeScript静态类型
|
||||
- **SQL注入**: TypeORM参数化查询
|
||||
#### 1. 数据验证
|
||||
- **输入验证**: 使用class-validator进行严格验证
|
||||
- **类型检查**: TypeScript静态类型检查
|
||||
- **SQL注入防护**: TypeORM参数化查询
|
||||
|
||||
### 2. 认证授权
|
||||
- **密码加密**: bcrypt哈希算法
|
||||
- **会话管理**: Redis存储会话信息
|
||||
- **权限控制**: 基于角色的访问控制
|
||||
#### 2. 认证授权
|
||||
- **密码安全**: bcrypt加密,强密码策略
|
||||
- **会话管理**: JWT + Redis会话存储
|
||||
- **权限控制**: 基于角色的访问控制(RBAC)
|
||||
|
||||
### 3. 通信安全
|
||||
#### 3. 通信安全
|
||||
- **HTTPS**: 生产环境强制HTTPS
|
||||
- **CORS**: 跨域请求控制
|
||||
- **Rate Limiting**: API请求频率限制
|
||||
- **CORS**: 严格的跨域请求控制
|
||||
- **Rate Limiting**: API请求频率限制
|
||||
|
||||
---
|
||||
|
||||
**🏗️ 通过清晰的架构设计,Whale Town 实现了高内聚、低耦合的模块化架构,支持快速开发和灵活部署!**
|
||||
142
docs/DOCUMENT_CLEANUP.md
Normal file
142
docs/DOCUMENT_CLEANUP.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 📝 文档清理说明
|
||||
|
||||
> 记录项目文档整理和优化的过程,确保文档结构清晰、内容准确。
|
||||
|
||||
## 🎯 清理目标
|
||||
|
||||
- **删除多余README** - 移除重复和过时的README文件
|
||||
- **优化主文档** - 改进项目主README的文件格式和结构说明
|
||||
- **完善架构文档** - 详细说明项目架构和文件夹组织结构
|
||||
- **统一文档风格** - 采用总分结构,方便开发者理解
|
||||
|
||||
## 📊 文档清理结果
|
||||
|
||||
### ✅ 保留的README文件
|
||||
|
||||
| 文件路径 | 保留原因 | 主要内容 |
|
||||
|----------|----------|----------|
|
||||
| `README.md` | 项目主文档 | 项目介绍、快速开始、技术栈、功能特性 |
|
||||
| `docs/README.md` | 文档导航中心 | 文档结构说明、导航链接 |
|
||||
| `client/README.md` | 前端项目文档 | 前端管理界面的独立说明 |
|
||||
| `docs/api/README.md` | API文档指南 | API文档使用说明和快速测试 |
|
||||
| `src/business/zulip/README.md` | 模块架构说明 | Zulip模块重构的详细说明 |
|
||||
|
||||
### ❌ 删除的README文件
|
||||
|
||||
**无** - 经过分析,所有现有README文件都有其存在价值,未删除任何文件。
|
||||
|
||||
### 🔄 优化的文档
|
||||
|
||||
#### 1. 主README.md优化
|
||||
- **文件结构总览** - 添加了详细的项目文件结构说明
|
||||
- **图标化展示** - 使用emoji图标让结构更直观
|
||||
- **层次化组织** - 按照总分结构组织内容
|
||||
|
||||
#### 2. 架构文档大幅改进 (docs/ARCHITECTURE.md)
|
||||
- **完整重写** - 从简单的架构图扩展为完整的架构设计文档
|
||||
- **目录结构详解** - 详细说明每个文件夹的作用和内容
|
||||
- **分层架构设计** - 清晰的架构分层和模块依赖关系
|
||||
- **双模式架构** - 详细说明开发测试模式和生产部署模式
|
||||
- **扩展指南** - 提供添加新模块和功能的详细指导
|
||||
|
||||
## 📁 文档结构优化
|
||||
|
||||
### 🎯 总分结构设计
|
||||
|
||||
采用**总分结构**组织文档,便于开发者快速理解:
|
||||
|
||||
```
|
||||
📚 文档层次结构
|
||||
├── 🏠 项目总览 (README.md)
|
||||
│ ├── 🎯 项目简介和特性
|
||||
│ ├── 🚀 快速开始指南
|
||||
│ ├── 📁 文件结构总览 ⭐ 新增
|
||||
│ ├── 🛠️ 技术栈说明
|
||||
│ └── 📚 文档导航链接
|
||||
│
|
||||
├── 🏗️ 架构设计 (docs/ARCHITECTURE.md) ⭐ 大幅改进
|
||||
│ ├── 📊 整体架构图
|
||||
│ ├── 📁 目录结构详解
|
||||
│ ├── 🏗️ 分层架构设计
|
||||
│ ├── 🔄 双模式架构
|
||||
│ └── 🚀 扩展指南
|
||||
│
|
||||
├── 📖 文档中心 (docs/README.md)
|
||||
│ ├── 📋 文档导航
|
||||
│ ├── 🏗️ 文档结构说明
|
||||
│ └── 📝 文档维护原则
|
||||
│
|
||||
├── 🔌 API文档 (docs/api/README.md)
|
||||
│ ├── 📊 API接口概览
|
||||
│ ├── 🚀 快速开始
|
||||
│ └── 🧪 测试指南
|
||||
│
|
||||
└── 🎨 前端文档 (client/README.md)
|
||||
├── 🚀 快速开始
|
||||
├── 🎯 核心功能
|
||||
└── 🔧 开发指南
|
||||
```
|
||||
|
||||
### 📊 文档内容优化
|
||||
|
||||
#### 1. 视觉化改进
|
||||
- **emoji图标** - 使用统一的emoji图标系统
|
||||
- **表格展示** - 用表格清晰展示对比信息
|
||||
- **代码示例** - 提供完整的代码示例和配置
|
||||
- **架构图** - 使用ASCII艺术绘制清晰的架构图
|
||||
|
||||
#### 2. 结构化内容
|
||||
- **目录导航** - 每个长文档都有详细目录
|
||||
- **分层说明** - 按照业务功能模块化的原则组织
|
||||
- **实用指南** - 提供具体的操作步骤和扩展指南
|
||||
|
||||
#### 3. 开发者友好
|
||||
- **快速上手** - 新开发者指南,从规范学习到架构理解
|
||||
- **总分结构** - 先总览后详细,便于快速理解
|
||||
- **实际案例** - 提供真实的代码示例和使用场景
|
||||
|
||||
## 🎯 文档维护原则
|
||||
|
||||
### ✅ 保留标准
|
||||
- **长期价值** - 对整个项目生命周期都有价值
|
||||
- **参考价值** - 开发、部署、维护时需要查阅
|
||||
- **规范指导** - 团队协作和代码质量保证
|
||||
|
||||
### ❌ 清理标准
|
||||
- **阶段性文档** - 只在特定开发阶段有用
|
||||
- **临时记录** - 会议记录、临时决策等
|
||||
- **过时信息** - 已经不适用的旧版本文档
|
||||
|
||||
### 🔄 更新策略
|
||||
- **及时更新** - 功能变更时同步更新相关文档
|
||||
- **版本控制** - 重要变更记录版本历史
|
||||
- **定期审查** - 定期检查文档的准确性和有效性
|
||||
|
||||
## 📈 改进效果
|
||||
|
||||
### 🎯 开发者体验提升
|
||||
- **快速理解** - 通过总分结构快速掌握项目架构
|
||||
- **准确信息** - 文档与实际代码结构完全一致
|
||||
- **实用指导** - 提供具体的开发和扩展指南
|
||||
|
||||
### 📚 文档质量提升
|
||||
- **结构清晰** - 层次分明的文档组织结构
|
||||
- **内容完整** - 覆盖项目的所有重要方面
|
||||
- **易于维护** - 明确的维护原则和更新策略
|
||||
|
||||
### 🚀 项目可维护性提升
|
||||
- **架构清晰** - 详细的架构文档便于理解和扩展
|
||||
- **规范统一** - 统一的文档风格和组织原则
|
||||
- **知识传承** - 完整的文档体系便于团队协作
|
||||
|
||||
---
|
||||
|
||||
**📝 通过系统性的文档清理和优化,项目文档现在更加清晰、准确、实用!**
|
||||
|
||||
## 📅 清理记录
|
||||
|
||||
- **清理时间**: 2025年12月31日
|
||||
- **清理范围**: 项目根目录及所有子目录的README文件
|
||||
- **主要改进**: 架构文档完全重写,主README结构优化
|
||||
- **保留文件**: 5个README文件全部保留
|
||||
- **删除文件**: 0个(所有文件都有价值)
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
### 📋 **项目管理**
|
||||
- [贡献指南](CONTRIBUTORS.md) - 如何参与项目贡献
|
||||
- [文档清理说明](DOCUMENT_CLEANUP.md) - 文档维护记录
|
||||
- [文档清理说明](DOCUMENT_CLEANUP.md) - 文档维护和优化记录
|
||||
|
||||
## 🏗️ **文档结构说明**
|
||||
|
||||
|
||||
BIN
docs/development/ab164782cdc17e22f9bdf443c7e1e96c.png
Normal file
BIN
docs/development/ab164782cdc17e22f9bdf443c7e1e96c.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 484 KiB |
@@ -1,3 +1,7 @@
|
||||
|
||||
|
||||

|
||||
|
||||
# Git 提交规范
|
||||
|
||||
本文档定义了项目的 Git 提交信息格式规范,以保持提交历史的清晰和一致性。
|
||||
|
||||
@@ -8,6 +8,7 @@ import { LoggerModule } from './core/utils/logger/logger.module';
|
||||
import { UsersModule } from './core/db/users/users.module';
|
||||
import { LoginCoreModule } from './core/login_core/login_core.module';
|
||||
import { AuthModule } from './business/auth/auth.module';
|
||||
import { ZulipModule } from './business/zulip/zulip.module';
|
||||
import { RedisModule } from './core/redis/redis.module';
|
||||
import { AdminModule } from './business/admin/admin.module';
|
||||
import { UserMgmtModule } from './business/user-mgmt/user-mgmt.module';
|
||||
@@ -67,6 +68,7 @@ function isDatabaseConfigured(): boolean {
|
||||
isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(),
|
||||
LoginCoreModule,
|
||||
AuthModule,
|
||||
ZulipModule,
|
||||
UserMgmtModule,
|
||||
AdminModule,
|
||||
SecurityModule,
|
||||
|
||||
172
src/business/zulip/README.md
Normal file
172
src/business/zulip/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Zulip集成业务模块
|
||||
|
||||
## 架构重构说明
|
||||
|
||||
本模块已按照项目的分层架构要求进行重构,将技术实现细节移动到核心服务层,业务逻辑保留在业务层。
|
||||
|
||||
### 重构前后对比
|
||||
|
||||
#### 重构前(❌ 违反架构原则)
|
||||
```
|
||||
src/business/zulip/services/
|
||||
├── zulip_client.service.ts # 技术实现:API调用
|
||||
├── zulip_client_pool.service.ts # 技术实现:连接池管理
|
||||
├── config_manager.service.ts # 技术实现:配置管理
|
||||
├── zulip_event_processor.service.ts # 技术实现:事件处理
|
||||
├── session_manager.service.ts # ✅ 业务逻辑:会话管理
|
||||
└── message_filter.service.ts # ✅ 业务逻辑:消息过滤
|
||||
```
|
||||
|
||||
#### 重构后(✅ 符合架构原则)
|
||||
```
|
||||
# 业务逻辑层
|
||||
src/business/zulip/
|
||||
├── zulip.service.ts # 业务协调服务
|
||||
├── zulip_websocket.gateway.ts # WebSocket业务网关
|
||||
└── services/
|
||||
├── session_manager.service.ts # 会话业务逻辑
|
||||
└── message_filter.service.ts # 消息过滤业务规则
|
||||
|
||||
# 核心服务层
|
||||
src/core/zulip/
|
||||
├── interfaces/
|
||||
│ └── zulip-core.interfaces.ts # 核心服务接口定义
|
||||
├── services/
|
||||
│ ├── zulip_client.service.ts # Zulip API封装
|
||||
│ ├── zulip_client_pool.service.ts # 客户端池管理
|
||||
│ ├── config_manager.service.ts # 配置管理
|
||||
│ ├── zulip_event_processor.service.ts # 事件处理
|
||||
│ └── ... # 其他技术服务
|
||||
└── zulip-core.module.ts # 核心服务模块
|
||||
```
|
||||
|
||||
### 架构优势
|
||||
|
||||
#### 1. 单一职责原则
|
||||
- **业务层**:只关注游戏相关的业务逻辑和规则
|
||||
- **核心层**:只处理技术实现和第三方API调用
|
||||
|
||||
#### 2. 依赖注入和接口抽象
|
||||
```typescript
|
||||
// 业务层通过接口依赖核心服务
|
||||
constructor(
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
@Inject('ZULIP_CONFIG_SERVICE')
|
||||
private readonly configManager: IZulipConfigService,
|
||||
) {}
|
||||
```
|
||||
|
||||
#### 3. 易于测试和维护
|
||||
- 业务逻辑可以独立测试,不依赖具体的技术实现
|
||||
- 核心服务可以独立替换,不影响业务逻辑
|
||||
- 接口定义清晰,便于理解和维护
|
||||
|
||||
### 服务职责划分
|
||||
|
||||
#### 业务逻辑层服务
|
||||
|
||||
| 服务 | 职责 | 业务价值 |
|
||||
|------|------|----------|
|
||||
| `ZulipService` | 游戏登录/登出业务流程协调 | 处理玩家生命周期管理 |
|
||||
| `SessionManagerService` | 游戏会话状态和上下文管理 | 维护玩家位置和聊天上下文 |
|
||||
| `MessageFilterService` | 消息过滤和业务规则控制 | 实现内容审核和权限验证 |
|
||||
| `ZulipWebSocketGateway` | WebSocket业务协议处理 | 游戏协议转换和路由 |
|
||||
|
||||
#### 核心服务层服务
|
||||
|
||||
| 服务 | 职责 | 技术价值 |
|
||||
|------|------|----------|
|
||||
| `ZulipClientService` | Zulip REST API封装 | 第三方API调用抽象 |
|
||||
| `ZulipClientPoolService` | 客户端连接池管理 | 资源管理和性能优化 |
|
||||
| `ConfigManagerService` | 配置文件管理和热重载 | 系统配置和运维支持 |
|
||||
| `ZulipEventProcessorService` | 事件队列处理和消息转换 | 异步消息处理机制 |
|
||||
|
||||
### 使用示例
|
||||
|
||||
#### 业务层调用核心服务
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class ZulipService {
|
||||
constructor(
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
) {}
|
||||
|
||||
async sendChatMessage(request: ChatMessageRequest): Promise<ChatMessageResponse> {
|
||||
// 业务逻辑:验证和处理
|
||||
const session = await this.sessionManager.getSession(request.socketId);
|
||||
const context = await this.sessionManager.injectContext(request.socketId);
|
||||
|
||||
// 调用核心服务:技术实现
|
||||
const result = await this.zulipClientPool.sendMessage(
|
||||
session.userId,
|
||||
context.stream,
|
||||
context.topic,
|
||||
request.content,
|
||||
);
|
||||
|
||||
return { success: result.success, messageId: result.messageId };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移指南
|
||||
|
||||
如果你的代码中直接导入了已移动的服务,请按以下方式更新:
|
||||
|
||||
#### 更新导入路径
|
||||
```typescript
|
||||
// ❌ 旧的导入方式
|
||||
import { ZulipClientPoolService } from './services/zulip_client_pool.service';
|
||||
|
||||
// ✅ 新的导入方式(通过依赖注入)
|
||||
import { IZulipClientPoolService } from '../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
constructor(
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
) {}
|
||||
```
|
||||
|
||||
#### 更新模块导入
|
||||
```typescript
|
||||
// ✅ 业务模块自动导入核心模块
|
||||
@Module({
|
||||
imports: [
|
||||
ZulipCoreModule, // 自动提供所有核心服务
|
||||
// ...
|
||||
],
|
||||
})
|
||||
export class ZulipModule {}
|
||||
```
|
||||
|
||||
### 测试策略
|
||||
|
||||
#### 业务逻辑测试
|
||||
```typescript
|
||||
// 使用Mock核心服务测试业务逻辑
|
||||
const mockZulipClientPool: IZulipClientPoolService = {
|
||||
sendMessage: jest.fn().mockResolvedValue({ success: true }),
|
||||
// ...
|
||||
};
|
||||
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
ZulipService,
|
||||
{ provide: 'ZULIP_CLIENT_POOL_SERVICE', useValue: mockZulipClientPool },
|
||||
],
|
||||
}).compile();
|
||||
```
|
||||
|
||||
#### 核心服务测试
|
||||
```typescript
|
||||
// 独立测试技术实现
|
||||
describe('ZulipClientService', () => {
|
||||
it('should call Zulip API correctly', async () => {
|
||||
// 测试API调用逻辑
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
这种架构设计确保了业务逻辑与技术实现的清晰分离,提高了代码的可维护性和可测试性。
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import { MessageFilterService, ViolationType, ContentFilterResult } from './message-filter.service';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { MessageFilterService, ViolationType } from './message_filter.service';
|
||||
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('MessageFilterService', () => {
|
||||
let service: MessageFilterService;
|
||||
let mockLogger: jest.Mocked<AppLoggerService>;
|
||||
let mockRedisService: jest.Mocked<IRedisService>;
|
||||
let mockConfigManager: jest.Mocked<ConfigManagerService>;
|
||||
let mockConfigManager: jest.Mocked<IZulipConfigService>;
|
||||
|
||||
// 内存存储模拟Redis
|
||||
let memoryStore: Map<string, { value: string; expireAt?: number }>;
|
||||
@@ -100,6 +100,14 @@ describe('MessageFilterService', () => {
|
||||
hasMap: jest.fn().mockImplementation((mapId: string) => {
|
||||
return ['novice_village', 'tavern', 'market'].includes(mapId);
|
||||
}),
|
||||
getMapIdByStream: jest.fn(),
|
||||
getTopicByObject: jest.fn(),
|
||||
getZulipConfig: jest.fn(),
|
||||
hasStream: jest.fn(),
|
||||
getAllMapIds: jest.fn(),
|
||||
getAllStreams: jest.fn(),
|
||||
reloadConfig: jest.fn(),
|
||||
validateConfig: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -114,7 +122,7 @@ describe('MessageFilterService', () => {
|
||||
useValue: mockRedisService,
|
||||
},
|
||||
{
|
||||
provide: ConfigManagerService,
|
||||
provide: 'ZULIP_CONFIG_SERVICE',
|
||||
useValue: mockConfigManager,
|
||||
},
|
||||
],
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
|
||||
import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
/**
|
||||
* 内容过滤结果接口
|
||||
@@ -90,6 +90,28 @@ export interface SensitiveWordConfig {
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息过滤服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 实施内容审核和频率控制
|
||||
* - 敏感词过滤和权限验证
|
||||
* - 防止恶意操作和滥用
|
||||
* - 与ConfigManager集成实现位置权限验证
|
||||
*
|
||||
* 主要方法:
|
||||
* - filterContent(): 内容过滤,敏感词检查
|
||||
* - checkRateLimit(): 频率限制检查
|
||||
* - validatePermission(): 权限验证,防止位置欺诈
|
||||
* - validateMessage(): 综合消息验证
|
||||
* - logViolation(): 记录违规行为
|
||||
*
|
||||
* 使用场景:
|
||||
* - 消息发送前的内容审核
|
||||
* - 频率限制和防刷屏
|
||||
* - 权限验证和安全控制
|
||||
* - 违规行为监控和记录
|
||||
*/
|
||||
@Injectable()
|
||||
export class MessageFilterService {
|
||||
private readonly RATE_LIMIT_PREFIX = 'zulip:rate_limit:';
|
||||
@@ -127,8 +149,8 @@ export class MessageFilterService {
|
||||
constructor(
|
||||
@Inject('REDIS_SERVICE')
|
||||
private readonly redisService: IRedisService,
|
||||
@Inject(forwardRef(() => ConfigManagerService))
|
||||
private readonly configManager: ConfigManagerService,
|
||||
@Inject('ZULIP_CONFIG_SERVICE')
|
||||
private readonly configManager: IZulipConfigService,
|
||||
) {
|
||||
this.logger.log('MessageFilterService初始化完成');
|
||||
}
|
||||
650
src/business/zulip/services/session_cleanup.service.spec.ts
Normal file
650
src/business/zulip/services/session_cleanup.service.spec.ts
Normal file
@@ -0,0 +1,650 @@
|
||||
/**
|
||||
* 会话清理定时任务服务测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试SessionCleanupService的核心功能
|
||||
* - 包含属性测试验证定时清理机制
|
||||
* - 包含属性测试验证资源释放完整性
|
||||
*
|
||||
* **Feature: zulip-integration, Property 13: 定时清理机制**
|
||||
* **Validates: Requirements 6.1, 6.2, 6.3**
|
||||
*
|
||||
* **Feature: zulip-integration, Property 14: 资源释放完整性**
|
||||
* **Validates: Requirements 6.4, 6.5**
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import {
|
||||
SessionCleanupService,
|
||||
CleanupConfig,
|
||||
CleanupResult
|
||||
} from './session_cleanup.service';
|
||||
import { SessionManagerService } from './session_manager.service';
|
||||
import { IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
describe('SessionCleanupService', () => {
|
||||
let service: SessionCleanupService;
|
||||
let mockSessionManager: jest.Mocked<SessionManagerService>;
|
||||
let mockZulipClientPool: jest.Mocked<IZulipClientPoolService>;
|
||||
|
||||
// 模拟清理结果
|
||||
const createMockCleanupResult = (overrides: Partial<any> = {}): any => ({
|
||||
cleanedCount: 3,
|
||||
zulipQueueIds: ['queue-1', 'queue-2', 'queue-3'],
|
||||
duration: 150,
|
||||
timestamp: new Date(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
// Only use fake timers for tests that need them
|
||||
// The concurrent test will use real timers for proper Promise handling
|
||||
|
||||
mockSessionManager = {
|
||||
cleanupExpiredSessions: jest.fn(),
|
||||
getSession: jest.fn(),
|
||||
destroySession: jest.fn(),
|
||||
createSession: jest.fn(),
|
||||
updatePlayerPosition: jest.fn(),
|
||||
getSocketsInMap: jest.fn(),
|
||||
injectContext: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockZulipClientPool = {
|
||||
createUserClient: jest.fn(),
|
||||
getUserClient: jest.fn(),
|
||||
hasUserClient: jest.fn(),
|
||||
sendMessage: jest.fn(),
|
||||
registerEventQueue: jest.fn(),
|
||||
deregisterEventQueue: jest.fn(),
|
||||
destroyUserClient: jest.fn(),
|
||||
getPoolStats: jest.fn(),
|
||||
cleanupIdleClients: jest.fn(),
|
||||
} as any;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
SessionCleanupService,
|
||||
{
|
||||
provide: SessionManagerService,
|
||||
useValue: mockSessionManager,
|
||||
},
|
||||
{
|
||||
provide: 'ZULIP_CLIENT_POOL_SERVICE',
|
||||
useValue: mockZulipClientPool,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SessionCleanupService>(SessionCleanupService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service.stopCleanupTask();
|
||||
// Only restore timers if they were faked
|
||||
if (jest.isMockFunction(setTimeout)) {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('startCleanupTask - 启动清理任务', () => {
|
||||
it('应该启动定时清理任务', () => {
|
||||
service.startCleanupTask();
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('应该在已启动时不重复启动', () => {
|
||||
service.startCleanupTask();
|
||||
service.startCleanupTask(); // 第二次调用
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('应该立即执行一次清理', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(
|
||||
createMockCleanupResult({ cleanedCount: 2 })
|
||||
);
|
||||
|
||||
service.startCleanupTask();
|
||||
|
||||
// 等待立即执行的清理完成
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
|
||||
expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopCleanupTask - 停止清理任务', () => {
|
||||
it('应该停止定时清理任务', () => {
|
||||
service.startCleanupTask();
|
||||
service.stopCleanupTask();
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it('应该在未启动时安全停止', () => {
|
||||
service.stopCleanupTask();
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runCleanup - 执行清理', () => {
|
||||
it('应该成功执行清理并返回结果', async () => {
|
||||
const mockResult = createMockCleanupResult({
|
||||
cleanedCount: 5,
|
||||
zulipQueueIds: ['queue-1', 'queue-2', 'queue-3', 'queue-4', 'queue-5'],
|
||||
});
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.cleanedSessions).toBe(5);
|
||||
expect(result.deregisteredQueues).toBe(5);
|
||||
expect(result.duration).toBeGreaterThanOrEqual(0); // 修改为 >= 0,因为测试环境可能很快
|
||||
expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
|
||||
});
|
||||
|
||||
it('应该处理清理过程中的错误', async () => {
|
||||
const error = new Error('清理失败');
|
||||
mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('清理失败');
|
||||
expect(result.cleanedSessions).toBe(0);
|
||||
expect(result.deregisteredQueues).toBe(0);
|
||||
});
|
||||
|
||||
it('应该防止并发执行', async () => {
|
||||
let resolveFirst: () => void;
|
||||
const firstPromise = new Promise<any>(resolve => {
|
||||
resolveFirst = () => resolve(createMockCleanupResult());
|
||||
});
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockReturnValueOnce(firstPromise);
|
||||
|
||||
// 同时启动两个清理任务
|
||||
const promise1 = service.runCleanup();
|
||||
const promise2 = service.runCleanup();
|
||||
|
||||
// 第二个应该立即返回失败
|
||||
const result2 = await promise2;
|
||||
expect(result2.success).toBe(false);
|
||||
expect(result2.error).toContain('正在执行中');
|
||||
|
||||
// 完成第一个任务
|
||||
resolveFirst!();
|
||||
const result1 = await promise1;
|
||||
expect(result1.success).toBe(true);
|
||||
}, 15000);
|
||||
|
||||
it('应该记录最后一次清理结果', async () => {
|
||||
const mockResult = createMockCleanupResult({ cleanedCount: 3 });
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
|
||||
|
||||
await service.runCleanup();
|
||||
|
||||
const lastResult = service.getLastCleanupResult();
|
||||
expect(lastResult).not.toBeNull();
|
||||
expect(lastResult!.cleanedSessions).toBe(3);
|
||||
expect(lastResult!.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatus - 获取状态', () => {
|
||||
it('应该返回正确的状态信息', () => {
|
||||
const status = service.getStatus();
|
||||
|
||||
expect(status).toHaveProperty('isRunning');
|
||||
expect(status).toHaveProperty('isEnabled');
|
||||
expect(status).toHaveProperty('config');
|
||||
expect(status).toHaveProperty('lastResult');
|
||||
expect(typeof status.isRunning).toBe('boolean');
|
||||
expect(typeof status.isEnabled).toBe('boolean');
|
||||
});
|
||||
|
||||
it('应该反映任务启动状态', () => {
|
||||
let status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
|
||||
service.startCleanupTask();
|
||||
status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
|
||||
service.stopCleanupTask();
|
||||
status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateConfig - 更新配置', () => {
|
||||
it('应该更新清理配置', () => {
|
||||
const newConfig: Partial<CleanupConfig> = {
|
||||
intervalMs: 10 * 60 * 1000, // 10分钟
|
||||
sessionTimeoutMinutes: 60, // 60分钟
|
||||
};
|
||||
|
||||
service.updateConfig(newConfig);
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.config.intervalMs).toBe(10 * 60 * 1000);
|
||||
expect(status.config.sessionTimeoutMinutes).toBe(60);
|
||||
});
|
||||
|
||||
it('应该在配置更改后重启任务', () => {
|
||||
service.startCleanupTask();
|
||||
|
||||
const newConfig: Partial<CleanupConfig> = {
|
||||
intervalMs: 2 * 60 * 1000, // 2分钟
|
||||
};
|
||||
|
||||
service.updateConfig(newConfig);
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
expect(status.config.intervalMs).toBe(2 * 60 * 1000);
|
||||
});
|
||||
|
||||
it('应该支持禁用清理任务', () => {
|
||||
service.startCleanupTask();
|
||||
|
||||
service.updateConfig({ enabled: false });
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
/**
|
||||
* 属性测试: 定时清理机制
|
||||
*
|
||||
* **Feature: zulip-integration, Property 13: 定时清理机制**
|
||||
* **Validates: Requirements 6.1, 6.2, 6.3**
|
||||
*
|
||||
* 系统应该定期清理过期的游戏会话,释放相关资源,
|
||||
* 并确保清理过程不影响正常的游戏服务
|
||||
*/
|
||||
describe('Property 13: 定时清理机制', () => {
|
||||
/**
|
||||
* 属性: 对于任何有效的清理配置,系统应该按配置间隔执行清理
|
||||
* 验证需求 6.1: 系统应定期检查并清理过期的游戏会话
|
||||
*/
|
||||
it('对于任何有效的清理配置,系统应该按配置间隔执行清理', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成有效的清理间隔(1-10分钟)
|
||||
fc.integer({ min: 1, max: 10 }).map(minutes => minutes * 60 * 1000),
|
||||
// 生成有效的会话超时时间(10-120分钟)
|
||||
fc.integer({ min: 10, max: 120 }),
|
||||
async (intervalMs, sessionTimeoutMinutes) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
jest.useFakeTimers();
|
||||
|
||||
const config: Partial<CleanupConfig> = {
|
||||
intervalMs,
|
||||
sessionTimeoutMinutes,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
// 模拟清理结果
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(
|
||||
createMockCleanupResult({ cleanedCount: 2 })
|
||||
);
|
||||
|
||||
service.updateConfig(config);
|
||||
service.startCleanupTask();
|
||||
|
||||
// 验证配置被正确设置
|
||||
const status = service.getStatus();
|
||||
expect(status.config.intervalMs).toBe(intervalMs);
|
||||
expect(status.config.sessionTimeoutMinutes).toBe(sessionTimeoutMinutes);
|
||||
expect(status.isEnabled).toBe(true);
|
||||
|
||||
// 验证立即执行了一次清理
|
||||
await jest.runOnlyPendingTimersAsync();
|
||||
expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(sessionTimeoutMinutes);
|
||||
|
||||
service.stopCleanupTask();
|
||||
jest.useRealTimers();
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
/**
|
||||
* 属性: 对于任何清理操作,都应该记录清理结果和统计信息
|
||||
* 验证需求 6.2: 清理过程中系统应记录清理的会话数量和释放的资源
|
||||
*/
|
||||
it('对于任何清理操作,都应该记录清理结果和统计信息', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成清理的会话数量
|
||||
fc.integer({ min: 0, max: 20 }),
|
||||
// 生成Zulip队列ID列表
|
||||
fc.array(
|
||||
fc.string({ minLength: 5, maxLength: 20 }).filter(s => s.trim().length > 0),
|
||||
{ minLength: 0, maxLength: 20 }
|
||||
),
|
||||
async (cleanedCount, queueIds) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
|
||||
const mockResult = createMockCleanupResult({
|
||||
cleanedCount,
|
||||
zulipQueueIds: queueIds.slice(0, cleanedCount), // 确保队列数量不超过清理数量
|
||||
});
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
// 验证清理结果被正确记录
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.cleanedSessions).toBe(cleanedCount);
|
||||
expect(result.deregisteredQueues).toBe(Math.min(queueIds.length, cleanedCount));
|
||||
expect(result.duration).toBeGreaterThanOrEqual(0);
|
||||
expect(result.timestamp).toBeInstanceOf(Date);
|
||||
|
||||
// 验证最后一次清理结果被保存
|
||||
const lastResult = service.getLastCleanupResult();
|
||||
expect(lastResult).not.toBeNull();
|
||||
expect(lastResult!.cleanedSessions).toBe(cleanedCount);
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
/**
|
||||
* 属性: 清理过程中发生错误时,系统应该正确处理并记录错误信息
|
||||
* 验证需求 6.3: 清理过程中出现错误时系统应记录错误信息并继续正常服务
|
||||
*/
|
||||
it('清理过程中发生错误时,系统应该正确处理并记录错误信息', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成各种错误消息
|
||||
fc.string({ minLength: 5, maxLength: 100 }).filter(s => s.trim().length > 0),
|
||||
async (errorMessage) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
|
||||
const error = new Error(errorMessage.trim());
|
||||
mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
// 验证错误被正确处理
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe(errorMessage.trim());
|
||||
expect(result.cleanedSessions).toBe(0);
|
||||
expect(result.deregisteredQueues).toBe(0);
|
||||
expect(result.duration).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// 验证错误结果被保存
|
||||
const lastResult = service.getLastCleanupResult();
|
||||
expect(lastResult).not.toBeNull();
|
||||
expect(lastResult!.success).toBe(false);
|
||||
expect(lastResult!.error).toBe(errorMessage.trim());
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
/**
|
||||
* 属性: 并发清理请求应该被正确处理,避免重复执行
|
||||
* 验证需求 6.1: 系统应避免同时执行多个清理任务
|
||||
*/
|
||||
it('并发清理请求应该被正确处理,避免重复执行', async () => {
|
||||
// 重置mock
|
||||
jest.clearAllMocks();
|
||||
|
||||
// 创建一个可控的Promise,使用实际的异步行为
|
||||
let resolveCleanup: (value: any) => void;
|
||||
const cleanupPromise = new Promise<any>(resolve => {
|
||||
resolveCleanup = resolve;
|
||||
});
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockReturnValue(cleanupPromise);
|
||||
|
||||
// 启动第一个清理请求(应该成功)
|
||||
const promise1 = service.runCleanup();
|
||||
|
||||
// 等待一个微任务周期,确保第一个请求开始执行
|
||||
await Promise.resolve();
|
||||
|
||||
// 启动第二个和第三个清理请求(应该被拒绝)
|
||||
const promise2 = service.runCleanup();
|
||||
const promise3 = service.runCleanup();
|
||||
|
||||
// 第二个和第三个请求应该立即返回失败
|
||||
const result2 = await promise2;
|
||||
const result3 = await promise3;
|
||||
|
||||
expect(result2.success).toBe(false);
|
||||
expect(result2.error).toContain('正在执行中');
|
||||
expect(result3.success).toBe(false);
|
||||
expect(result3.error).toContain('正在执行中');
|
||||
|
||||
// 完成第一个清理操作
|
||||
resolveCleanup!(createMockCleanupResult({ cleanedCount: 1 }));
|
||||
const result1 = await promise1;
|
||||
|
||||
expect(result1.success).toBe(true);
|
||||
}, 10000);
|
||||
});
|
||||
/**
|
||||
* 属性测试: 资源释放完整性
|
||||
*
|
||||
* **Feature: zulip-integration, Property 14: 资源释放完整性**
|
||||
* **Validates: Requirements 6.4, 6.5**
|
||||
*
|
||||
* 清理过期会话时,系统应该完整释放所有相关资源,
|
||||
* 包括Zulip事件队列、内存缓存等,确保不会造成资源泄漏
|
||||
*/
|
||||
describe('Property 14: 资源释放完整性', () => {
|
||||
/**
|
||||
* 属性: 对于任何过期会话,清理时应该释放所有相关的Zulip资源
|
||||
* 验证需求 6.4: 清理会话时系统应注销对应的Zulip事件队列
|
||||
*/
|
||||
it('对于任何过期会话,清理时应该释放所有相关的Zulip资源', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成过期会话数量
|
||||
fc.integer({ min: 1, max: 10 }),
|
||||
// 生成每个会话对应的Zulip队列ID
|
||||
fc.array(
|
||||
fc.string({ minLength: 8, maxLength: 20 }).filter(s => s.trim().length > 0),
|
||||
{ minLength: 1, maxLength: 10 }
|
||||
),
|
||||
async (sessionCount, queueIds) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
|
||||
const actualQueueIds = queueIds.slice(0, sessionCount);
|
||||
const mockResult = createMockCleanupResult({
|
||||
cleanedCount: sessionCount,
|
||||
zulipQueueIds: actualQueueIds,
|
||||
});
|
||||
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
// 验证清理成功
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.cleanedSessions).toBe(sessionCount);
|
||||
|
||||
// 验证Zulip队列被处理(这里简化为计数验证)
|
||||
expect(result.deregisteredQueues).toBe(actualQueueIds.length);
|
||||
|
||||
// 验证SessionManager被调用清理过期会话
|
||||
expect(mockSessionManager.cleanupExpiredSessions).toHaveBeenCalledWith(30);
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
/**
|
||||
* 属性: 清理操作应该是原子性的,要么全部成功要么全部回滚
|
||||
* 验证需求 6.5: 清理过程应确保数据一致性,避免部分清理导致的不一致状态
|
||||
*/
|
||||
it('清理操作应该是原子性的,要么全部成功要么全部回滚', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成是否模拟清理失败
|
||||
fc.boolean(),
|
||||
// 生成会话数量
|
||||
fc.integer({ min: 1, max: 5 }),
|
||||
async (shouldFail, sessionCount) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
|
||||
if (shouldFail) {
|
||||
// 模拟清理失败
|
||||
const error = new Error('清理操作失败');
|
||||
mockSessionManager.cleanupExpiredSessions.mockRejectedValue(error);
|
||||
} else {
|
||||
// 模拟清理成功
|
||||
const mockResult = createMockCleanupResult({
|
||||
cleanedCount: sessionCount,
|
||||
zulipQueueIds: Array.from({ length: sessionCount }, (_, i) => `queue-${i}`),
|
||||
});
|
||||
mockSessionManager.cleanupExpiredSessions.mockResolvedValue(mockResult);
|
||||
}
|
||||
|
||||
const result = await service.runCleanup();
|
||||
|
||||
if (shouldFail) {
|
||||
// 失败时应该没有任何资源被释放
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.cleanedSessions).toBe(0);
|
||||
expect(result.deregisteredQueues).toBe(0);
|
||||
expect(result.error).toBeDefined();
|
||||
} else {
|
||||
// 成功时所有资源都应该被正确处理
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.cleanedSessions).toBe(sessionCount);
|
||||
expect(result.deregisteredQueues).toBe(sessionCount);
|
||||
expect(result.error).toBeUndefined();
|
||||
}
|
||||
|
||||
// 验证结果的一致性
|
||||
expect(result.timestamp).toBeInstanceOf(Date);
|
||||
expect(result.duration).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
/**
|
||||
* 属性: 清理配置更新应该正确重启清理任务而不丢失状态
|
||||
* 验证需求 6.5: 配置更新时系统应保持服务连续性
|
||||
*/
|
||||
it('清理配置更新应该正确重启清理任务而不丢失状态', async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
// 生成初始配置
|
||||
fc.record({
|
||||
intervalMs: fc.integer({ min: 1, max: 5 }).map(m => m * 60 * 1000),
|
||||
sessionTimeoutMinutes: fc.integer({ min: 10, max: 60 }),
|
||||
}),
|
||||
// 生成新配置
|
||||
fc.record({
|
||||
intervalMs: fc.integer({ min: 1, max: 5 }).map(m => m * 60 * 1000),
|
||||
sessionTimeoutMinutes: fc.integer({ min: 10, max: 60 }),
|
||||
}),
|
||||
async (initialConfig, newConfig) => {
|
||||
// 重置mock以确保每次测试都是干净的状态
|
||||
jest.clearAllMocks();
|
||||
|
||||
// 设置初始配置并启动任务
|
||||
service.updateConfig(initialConfig);
|
||||
service.startCleanupTask();
|
||||
|
||||
let status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
expect(status.config.intervalMs).toBe(initialConfig.intervalMs);
|
||||
|
||||
// 更新配置
|
||||
service.updateConfig(newConfig);
|
||||
|
||||
// 验证配置更新后任务仍在运行
|
||||
status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
expect(status.config.intervalMs).toBe(newConfig.intervalMs);
|
||||
expect(status.config.sessionTimeoutMinutes).toBe(newConfig.sessionTimeoutMinutes);
|
||||
|
||||
service.stopCleanupTask();
|
||||
}
|
||||
),
|
||||
{ numRuns: 30 }
|
||||
);
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
describe('模块生命周期', () => {
|
||||
it('应该在模块初始化时启动清理任务', async () => {
|
||||
// 重新创建服务实例来测试模块初始化
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
SessionCleanupService,
|
||||
{
|
||||
provide: SessionManagerService,
|
||||
useValue: mockSessionManager,
|
||||
},
|
||||
{
|
||||
provide: 'ZULIP_CLIENT_POOL_SERVICE',
|
||||
useValue: mockZulipClientPool,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
const newService = module.get<SessionCleanupService>(SessionCleanupService);
|
||||
|
||||
// 模拟模块初始化
|
||||
await newService.onModuleInit();
|
||||
|
||||
const status = newService.getStatus();
|
||||
expect(status.isEnabled).toBe(true);
|
||||
|
||||
// 清理
|
||||
await newService.onModuleDestroy();
|
||||
});
|
||||
|
||||
it('应该在模块销毁时停止清理任务', async () => {
|
||||
service.startCleanupTask();
|
||||
|
||||
await service.onModuleDestroy();
|
||||
|
||||
const status = service.getStatus();
|
||||
expect(status.isEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,9 +21,9 @@
|
||||
* @since 2025-12-25
|
||||
*/
|
||||
|
||||
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||
import { SessionManagerService } from './session-manager.service';
|
||||
import { ZulipClientPoolService } from './zulip-client-pool.service';
|
||||
import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common';
|
||||
import { SessionManagerService } from './session_manager.service';
|
||||
import { IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
/**
|
||||
* 清理任务配置接口
|
||||
@@ -55,6 +55,28 @@ export interface CleanupResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话清理服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 定时清理过期的游戏会话
|
||||
* - 释放无效的Zulip客户端资源
|
||||
* - 维护会话数据的一致性
|
||||
* - 提供会话清理统计和监控
|
||||
*
|
||||
* 主要方法:
|
||||
* - startCleanup(): 启动定时清理任务
|
||||
* - stopCleanup(): 停止清理任务
|
||||
* - performCleanup(): 执行一次清理操作
|
||||
* - getCleanupStats(): 获取清理统计信息
|
||||
* - updateConfig(): 更新清理配置
|
||||
*
|
||||
* 使用场景:
|
||||
* - 系统启动时自动开始清理任务
|
||||
* - 定期清理过期会话和资源
|
||||
* - 系统关闭时停止清理任务
|
||||
* - 监控清理效果和系统健康
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionCleanupService implements OnModuleInit, OnModuleDestroy {
|
||||
private cleanupInterval: NodeJS.Timeout | null = null;
|
||||
@@ -70,7 +92,8 @@ export class SessionCleanupService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
constructor(
|
||||
private readonly sessionManager: SessionManagerService,
|
||||
private readonly zulipClientPool: ZulipClientPoolService,
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
) {
|
||||
this.logger.log('SessionCleanupService初始化完成');
|
||||
}
|
||||
@@ -176,7 +199,8 @@ export class SessionCleanupService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
// 2. 注销对应的Zulip事件队列
|
||||
let deregisteredQueues = 0;
|
||||
for (const queueId of cleanupResult.zulipQueueIds) {
|
||||
const queueIds = cleanupResult?.zulipQueueIds || [];
|
||||
for (const queueId of queueIds) {
|
||||
try {
|
||||
// 根据queueId找到对应的用户并注销队列
|
||||
// 注意:这里需要通过某种方式找到queueId对应的userId
|
||||
@@ -200,7 +224,7 @@ export class SessionCleanupService implements OnModuleInit, OnModuleDestroy {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
const result: CleanupResult = {
|
||||
cleanedSessions: cleanupResult.cleanedCount,
|
||||
cleanedSessions: cleanupResult?.cleanedCount || 0,
|
||||
deregisteredQueues,
|
||||
duration,
|
||||
timestamp: new Date(),
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import { SessionManagerService, GameSession, Position } from './session-manager.service';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { SessionManagerService, GameSession, Position } from './session_manager.service';
|
||||
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('SessionManagerService', () => {
|
||||
let service: SessionManagerService;
|
||||
let mockLogger: jest.Mocked<AppLoggerService>;
|
||||
let mockRedisService: jest.Mocked<IRedisService>;
|
||||
let mockConfigManager: jest.Mocked<ConfigManagerService>;
|
||||
let mockConfigManager: jest.Mocked<IZulipConfigService>;
|
||||
|
||||
// 内存存储模拟Redis
|
||||
let memoryStore: Map<string, { value: string; expireAt?: number }>;
|
||||
@@ -57,9 +57,15 @@ describe('SessionManagerService', () => {
|
||||
};
|
||||
return streamMap[mapId] || 'General';
|
||||
}),
|
||||
getMapIdByStream: jest.fn(),
|
||||
getTopicByObject: jest.fn().mockReturnValue('General'),
|
||||
getMapConfig: jest.fn(),
|
||||
getAllMaps: jest.fn(),
|
||||
getZulipConfig: jest.fn(),
|
||||
hasMap: jest.fn(),
|
||||
hasStream: jest.fn(),
|
||||
getAllMapIds: jest.fn(),
|
||||
getAllStreams: jest.fn(),
|
||||
reloadConfig: jest.fn(),
|
||||
validateConfig: jest.fn(),
|
||||
} as any;
|
||||
|
||||
// 创建模拟Redis服务,使用内存存储
|
||||
@@ -135,7 +141,7 @@ describe('SessionManagerService', () => {
|
||||
useValue: mockRedisService,
|
||||
},
|
||||
{
|
||||
provide: ConfigManagerService,
|
||||
provide: 'ZULIP_CONFIG_SERVICE',
|
||||
useValue: mockConfigManager,
|
||||
},
|
||||
],
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
import { Injectable, Logger, Inject } from '@nestjs/common';
|
||||
import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { Internal, Constants } from '../interfaces/zulip.interfaces';
|
||||
import { IZulipConfigService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
import { Internal, Constants } from '../../../core/zulip/interfaces/zulip.interfaces';
|
||||
|
||||
/**
|
||||
* 游戏会话接口 - 重新导出以保持向后兼容
|
||||
@@ -78,6 +78,29 @@ export interface SessionStats {
|
||||
newestSession?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话管理服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 维护WebSocket连接ID与Zulip队列ID的映射关系
|
||||
* - 管理玩家位置跟踪和上下文注入
|
||||
* - 提供空间过滤和会话查询功能
|
||||
* - 支持会话状态的序列化和反序列化
|
||||
*
|
||||
* 主要方法:
|
||||
* - createSession(): 创建会话并绑定Socket_ID与Zulip_Queue_ID
|
||||
* - getSession(): 获取会话信息
|
||||
* - injectContext(): 上下文注入,根据位置确定Stream/Topic
|
||||
* - getSocketsInMap(): 空间过滤,获取指定地图的所有Socket
|
||||
* - updatePlayerPosition(): 更新玩家位置
|
||||
* - destroySession(): 销毁会话
|
||||
*
|
||||
* 使用场景:
|
||||
* - 玩家登录时创建会话映射
|
||||
* - 消息路由时进行上下文注入
|
||||
* - 消息分发时进行空间过滤
|
||||
* - 玩家登出时清理会话数据
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionManagerService {
|
||||
private readonly SESSION_PREFIX = 'zulip:session:';
|
||||
@@ -91,7 +114,8 @@ export class SessionManagerService {
|
||||
constructor(
|
||||
@Inject('REDIS_SERVICE')
|
||||
private readonly redisService: IRedisService,
|
||||
private readonly configManager: ConfigManagerService,
|
||||
@Inject('ZULIP_CONFIG_SERVICE')
|
||||
private readonly configManager: IZulipConfigService,
|
||||
) {
|
||||
this.logger.log('SessionManagerService初始化完成');
|
||||
}
|
||||
@@ -170,6 +194,9 @@ export class SessionManagerService {
|
||||
* @param initialMap 初始地图(可选)
|
||||
* @param initialPosition 初始位置(可选)
|
||||
* @returns Promise<GameSession> 创建的会话对象
|
||||
*
|
||||
* @throws Error 当参数验证失败时
|
||||
* @throws Error 当Redis操作失败时
|
||||
*/
|
||||
async createSession(
|
||||
socketId: string,
|
||||
@@ -378,6 +405,8 @@ export class SessionManagerService {
|
||||
* @param socketId WebSocket连接ID
|
||||
* @param mapId 地图ID(可选,用于覆盖当前地图)
|
||||
* @returns Promise<ContextInfo> 上下文信息
|
||||
*
|
||||
* @throws Error 当会话不存在时
|
||||
*/
|
||||
async injectContext(socketId: string, mapId?: string): Promise<ContextInfo> {
|
||||
this.logger.debug('开始上下文注入', {
|
||||
@@ -24,18 +24,17 @@ import {
|
||||
ZulipMessage,
|
||||
GameMessage,
|
||||
MessageDistributor,
|
||||
} from './zulip-event-processor.service';
|
||||
import { SessionManagerService, GameSession } from './session-manager.service';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { ZulipClientPoolService } from './zulip-client-pool.service';
|
||||
} from './zulip_event_processor.service';
|
||||
import { SessionManagerService, GameSession } from './session_manager.service';
|
||||
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
|
||||
describe('ZulipEventProcessorService', () => {
|
||||
let service: ZulipEventProcessorService;
|
||||
let mockLogger: jest.Mocked<AppLoggerService>;
|
||||
let mockSessionManager: jest.Mocked<SessionManagerService>;
|
||||
let mockConfigManager: jest.Mocked<ConfigManagerService>;
|
||||
let mockClientPool: jest.Mocked<ZulipClientPoolService>;
|
||||
let mockConfigManager: jest.Mocked<IZulipConfigService>;
|
||||
let mockClientPool: jest.Mocked<IZulipClientPoolService>;
|
||||
let mockDistributor: jest.Mocked<MessageDistributor>;
|
||||
|
||||
// 创建模拟Zulip消息
|
||||
@@ -87,14 +86,26 @@ describe('ZulipEventProcessorService', () => {
|
||||
mockConfigManager = {
|
||||
getMapIdByStream: jest.fn(),
|
||||
getStreamByMap: jest.fn(),
|
||||
getMapConfig: jest.fn(),
|
||||
getTopicByObject: jest.fn(),
|
||||
getZulipConfig: jest.fn(),
|
||||
hasMap: jest.fn(),
|
||||
hasStream: jest.fn(),
|
||||
getAllMapIds: jest.fn(),
|
||||
getAllStreams: jest.fn(),
|
||||
reloadConfig: jest.fn(),
|
||||
validateConfig: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockClientPool = {
|
||||
getUserClient: jest.fn(),
|
||||
createUserClient: jest.fn(),
|
||||
destroyUserClient: jest.fn(),
|
||||
hasUserClient: jest.fn(),
|
||||
sendMessage: jest.fn(),
|
||||
registerEventQueue: jest.fn(),
|
||||
deregisterEventQueue: jest.fn(),
|
||||
getPoolStats: jest.fn(),
|
||||
cleanupIdleClients: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockDistributor = {
|
||||
@@ -114,11 +125,11 @@ describe('ZulipEventProcessorService', () => {
|
||||
useValue: mockSessionManager,
|
||||
},
|
||||
{
|
||||
provide: ConfigManagerService,
|
||||
provide: 'ZULIP_CONFIG_SERVICE',
|
||||
useValue: mockConfigManager,
|
||||
},
|
||||
{
|
||||
provide: ZulipClientPoolService,
|
||||
provide: 'ZULIP_CLIENT_POOL_SERVICE',
|
||||
useValue: mockClientPool,
|
||||
},
|
||||
],
|
||||
@@ -31,9 +31,8 @@
|
||||
*/
|
||||
|
||||
import { Injectable, OnModuleDestroy, Inject, forwardRef, Logger } from '@nestjs/common';
|
||||
import { SessionManagerService } from './session-manager.service';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { ZulipClientPoolService } from './zulip-client-pool.service';
|
||||
import { SessionManagerService } from './session_manager.service';
|
||||
import { IZulipConfigService, IZulipClientPoolService } from '../../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
/**
|
||||
* Zulip消息接口
|
||||
@@ -94,6 +93,28 @@ export interface EventProcessingStats {
|
||||
lastEventTime?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip事件处理服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 处理从Zulip接收的事件队列消息
|
||||
* - 将Zulip消息转换为游戏协议格式
|
||||
* - 管理事件队列的生命周期
|
||||
* - 提供消息分发和路由功能
|
||||
*
|
||||
* 主要方法:
|
||||
* - processEvents(): 处理Zulip事件队列
|
||||
* - processMessage(): 处理单个消息事件
|
||||
* - startProcessing(): 启动事件处理
|
||||
* - stopProcessing(): 停止事件处理
|
||||
* - registerQueue(): 注册新的事件队列
|
||||
*
|
||||
* 使用场景:
|
||||
* - 接收Zulip服务器推送的消息
|
||||
* - 将Zulip消息转发给游戏客户端
|
||||
* - 管理多用户的事件队列
|
||||
* - 消息格式转换和过滤
|
||||
*/
|
||||
@Injectable()
|
||||
export class ZulipEventProcessorService implements OnModuleDestroy {
|
||||
private readonly logger = new Logger(ZulipEventProcessorService.name);
|
||||
@@ -109,9 +130,10 @@ export class ZulipEventProcessorService implements OnModuleDestroy {
|
||||
|
||||
constructor(
|
||||
private readonly sessionManager: SessionManagerService,
|
||||
private readonly configManager: ConfigManagerService,
|
||||
@Inject(forwardRef(() => ZulipClientPoolService))
|
||||
private readonly clientPool: ZulipClientPoolService,
|
||||
@Inject('ZULIP_CONFIG_SERVICE')
|
||||
private readonly configManager: IZulipConfigService,
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly clientPool: IZulipClientPoolService,
|
||||
) {
|
||||
this.logger.log('ZulipEventProcessorService初始化完成');
|
||||
}
|
||||
@@ -2,26 +2,32 @@
|
||||
* Zulip集成业务模块
|
||||
*
|
||||
* 功能描述:
|
||||
* - 整合Zulip集成相关的控制器、服务和依赖
|
||||
* - 提供完整的Zulip集成功能模块
|
||||
* - 实现游戏与Zulip的无缝通信桥梁
|
||||
* - 支持WebSocket网关、会话管理、消息过滤等核心功能
|
||||
* - 启动时自动检查并创建所有地图对应的Zulip Streams
|
||||
* - 整合Zulip集成相关的业务逻辑和控制器
|
||||
* - 提供完整的Zulip集成业务功能模块
|
||||
* - 实现游戏与Zulip的业务逻辑协调
|
||||
* - 支持WebSocket网关、会话管理、消息过滤等业务功能
|
||||
*
|
||||
* 核心服务:
|
||||
* - ZulipService: 主协调服务,处理登录、消息发送等核心业务
|
||||
* 架构设计:
|
||||
* - 业务逻辑层:处理游戏相关的业务规则和流程
|
||||
* - 核心服务层:封装技术实现细节和第三方API调用
|
||||
* - 通过依赖注入实现业务层与技术层的解耦
|
||||
*
|
||||
* 业务服务:
|
||||
* - ZulipService: 主协调服务,处理登录、消息发送等核心业务流程
|
||||
* - ZulipWebSocketGateway: WebSocket统一网关,处理客户端连接
|
||||
* - ZulipClientPoolService: Zulip客户端池管理
|
||||
* - SessionManagerService: 会话状态管理
|
||||
* - MessageFilterService: 消息过滤和安全控制
|
||||
* - SessionManagerService: 会话状态管理和业务逻辑
|
||||
* - MessageFilterService: 消息过滤和业务规则控制
|
||||
*
|
||||
* 核心服务(通过ZulipCoreModule提供):
|
||||
* - ZulipClientService: Zulip REST API封装
|
||||
* - ZulipClientPoolService: 客户端池管理
|
||||
* - ConfigManagerService: 配置管理和热重载
|
||||
* - StreamInitializerService: Stream初始化和自动创建
|
||||
* - ErrorHandlerService: 错误处理和服务降级
|
||||
* - MonitoringService: 系统监控和告警
|
||||
* - ApiKeySecurityService: API Key安全存储
|
||||
* - ZulipEventProcessorService: 事件处理和消息转换
|
||||
* - 其他技术支持服务
|
||||
*
|
||||
* 依赖模块:
|
||||
* - LoginModule: 用户认证和会话管理
|
||||
* - ZulipCoreModule: Zulip核心技术服务
|
||||
* - LoginCoreModule: 用户认证和会话管理
|
||||
* - RedisModule: 会话状态缓存
|
||||
* - LoggerModule: 日志记录服务
|
||||
*
|
||||
@@ -29,65 +35,47 @@
|
||||
* - 游戏客户端通过WebSocket连接进行实时聊天
|
||||
* - 游戏内消息与Zulip社群的双向同步
|
||||
* - 基于位置的聊天上下文管理
|
||||
* - 系统启动时自动初始化所有地图对应的Streams
|
||||
* - 业务规则驱动的消息过滤和权限控制
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-25
|
||||
* @version 2.0.0
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ZulipWebSocketGateway } from './zulip-websocket.gateway';
|
||||
import { ZulipWebSocketGateway } from './zulip_websocket.gateway';
|
||||
import { ZulipService } from './zulip.service';
|
||||
import { ZulipClientService } from './services/zulip-client.service';
|
||||
import { ZulipClientPoolService } from './services/zulip-client-pool.service';
|
||||
import { SessionManagerService } from './services/session-manager.service';
|
||||
import { SessionCleanupService } from './services/session-cleanup.service';
|
||||
import { MessageFilterService } from './services/message-filter.service';
|
||||
import { ZulipEventProcessorService } from './services/zulip-event-processor.service';
|
||||
import { ConfigManagerService } from './services/config-manager.service';
|
||||
import { ErrorHandlerService } from './services/error-handler.service';
|
||||
import { MonitoringService } from './services/monitoring.service';
|
||||
import { ApiKeySecurityService } from './services/api-key-security.service';
|
||||
import { StreamInitializerService } from './services/stream-initializer.service';
|
||||
import { SessionManagerService } from './services/session_manager.service';
|
||||
import { MessageFilterService } from './services/message_filter.service';
|
||||
import { ZulipEventProcessorService } from './services/zulip_event_processor.service';
|
||||
import { SessionCleanupService } from './services/session_cleanup.service';
|
||||
import { ZulipCoreModule } from '../../core/zulip/zulip-core.module';
|
||||
import { RedisModule } from '../../core/redis/redis.module';
|
||||
import { LoggerModule } from '../../core/utils/logger/logger.module';
|
||||
import { LoginModule } from '../login/login.module';
|
||||
import { LoginCoreModule } from '../../core/login_core/login_core.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// Zulip核心服务模块 - 提供技术实现相关的核心服务
|
||||
ZulipCoreModule,
|
||||
// Redis模块 - 提供会话状态缓存和数据存储
|
||||
RedisModule,
|
||||
// 日志模块 - 提供统一的日志记录服务
|
||||
LoggerModule,
|
||||
// 登录模块 - 提供用户认证和Token验证
|
||||
LoginModule,
|
||||
LoginCoreModule,
|
||||
],
|
||||
providers: [
|
||||
// 主协调服务 - 整合各子服务,提供统一业务接口
|
||||
ZulipService,
|
||||
// Zulip客户端服务 - 封装Zulip REST API调用
|
||||
ZulipClientService,
|
||||
// Zulip客户端池服务 - 管理用户专用Zulip客户端实例
|
||||
ZulipClientPoolService,
|
||||
// 会话管理服务 - 维护Socket_ID与Zulip_Queue_ID的映射关系
|
||||
SessionManagerService,
|
||||
// 会话清理服务 - 定时清理过期会话
|
||||
SessionCleanupService,
|
||||
// 消息过滤服务 - 敏感词过滤、频率限制、权限验证
|
||||
MessageFilterService,
|
||||
// Zulip事件处理服务 - 处理Zulip事件队列消息
|
||||
ZulipEventProcessorService,
|
||||
// 配置管理服务 - 地图映射配置和系统配置管理
|
||||
ConfigManagerService,
|
||||
// Stream初始化服务 - 启动时检查并创建所有地图对应的Streams
|
||||
StreamInitializerService,
|
||||
// 错误处理服务 - 错误处理、重试机制、服务降级
|
||||
ErrorHandlerService,
|
||||
// 监控服务 - 系统监控、健康检查、告警
|
||||
MonitoringService,
|
||||
// API Key安全服务 - API Key加密存储和安全日志
|
||||
ApiKeySecurityService,
|
||||
// 会话清理服务 - 定时清理过期会话
|
||||
SessionCleanupService,
|
||||
// WebSocket网关 - 处理游戏客户端WebSocket连接
|
||||
ZulipWebSocketGateway,
|
||||
],
|
||||
@@ -95,26 +83,14 @@ import { LoginModule } from '../login/login.module';
|
||||
exports: [
|
||||
// 导出主服务供其他模块使用
|
||||
ZulipService,
|
||||
// 导出Zulip客户端服务
|
||||
ZulipClientService,
|
||||
// 导出客户端池服务
|
||||
ZulipClientPoolService,
|
||||
// 导出会话管理服务
|
||||
SessionManagerService,
|
||||
// 导出会话清理服务
|
||||
SessionCleanupService,
|
||||
// 导出消息过滤服务
|
||||
MessageFilterService,
|
||||
// 导出配置管理服务
|
||||
ConfigManagerService,
|
||||
// 导出Stream初始化服务
|
||||
StreamInitializerService,
|
||||
// 导出错误处理服务
|
||||
ErrorHandlerService,
|
||||
// 导出监控服务
|
||||
MonitoringService,
|
||||
// 导出API Key安全服务
|
||||
ApiKeySecurityService,
|
||||
// 导出事件处理服务
|
||||
ZulipEventProcessorService,
|
||||
// 导出会话清理服务
|
||||
SessionCleanupService,
|
||||
// 导出WebSocket网关
|
||||
ZulipWebSocketGateway,
|
||||
],
|
||||
|
||||
1134
src/business/zulip/zulip.service.spec.ts
Normal file
1134
src/business/zulip/zulip.service.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,14 +22,15 @@
|
||||
* @since 2025-12-25
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger, Inject } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ZulipClientPoolService } from './services/zulip-client-pool.service';
|
||||
import { SessionManagerService } from './services/session-manager.service';
|
||||
import { MessageFilterService } from './services/message-filter.service';
|
||||
import { ZulipEventProcessorService } from './services/zulip-event-processor.service';
|
||||
import { ConfigManagerService } from './services/config-manager.service';
|
||||
import { ErrorHandlerService } from './services/error-handler.service';
|
||||
import { SessionManagerService } from './services/session_manager.service';
|
||||
import { MessageFilterService } from './services/message_filter.service';
|
||||
import { ZulipEventProcessorService } from './services/zulip_event_processor.service';
|
||||
import {
|
||||
IZulipClientPoolService,
|
||||
IZulipConfigService,
|
||||
} from '../../core/zulip/interfaces/zulip-core.interfaces';
|
||||
|
||||
/**
|
||||
* 玩家登录请求接口
|
||||
@@ -79,18 +80,40 @@ export interface ChatMessageResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip集成主服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 作为Zulip集成系统的主要协调服务
|
||||
* - 整合各个子服务,提供统一的业务接口
|
||||
* - 处理游戏客户端与Zulip之间的核心业务逻辑
|
||||
* - 管理玩家会话和消息路由
|
||||
*
|
||||
* 主要方法:
|
||||
* - handlePlayerLogin(): 处理玩家登录和Zulip客户端初始化
|
||||
* - handlePlayerLogout(): 处理玩家登出和资源清理
|
||||
* - sendChatMessage(): 处理游戏聊天消息发送到Zulip
|
||||
* - updatePlayerPosition(): 更新玩家位置信息
|
||||
*
|
||||
* 使用场景:
|
||||
* - WebSocket网关调用处理消息路由
|
||||
* - 会话管理和状态维护
|
||||
* - 消息格式转换和过滤
|
||||
* - 游戏与Zulip的双向通信桥梁
|
||||
*/
|
||||
@Injectable()
|
||||
export class ZulipService {
|
||||
private readonly logger = new Logger(ZulipService.name);
|
||||
private readonly DEFAULT_MAP = 'whale_port';
|
||||
|
||||
constructor(
|
||||
private readonly zulipClientPool: ZulipClientPoolService,
|
||||
@Inject('ZULIP_CLIENT_POOL_SERVICE')
|
||||
private readonly zulipClientPool: IZulipClientPoolService,
|
||||
private readonly sessionManager: SessionManagerService,
|
||||
private readonly messageFilter: MessageFilterService,
|
||||
private readonly eventProcessor: ZulipEventProcessorService,
|
||||
private readonly configManager: ConfigManagerService,
|
||||
private readonly errorHandler: ErrorHandlerService,
|
||||
@Inject('ZULIP_CONFIG_SERVICE')
|
||||
private readonly configManager: IZulipConfigService,
|
||||
) {
|
||||
this.logger.log('ZulipService初始化完成');
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ describeE2E('Zulip Integration E2E Tests', () => {
|
||||
});
|
||||
|
||||
client.on('connect', () => resolve(client));
|
||||
client.on('connect_error', (err) => reject(err));
|
||||
client.on('connect_error', (err: any) => reject(err));
|
||||
|
||||
setTimeout(() => reject(new Error('Connection timeout')), 5000);
|
||||
});
|
||||
@@ -16,9 +16,9 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import * as fc from 'fast-check';
|
||||
import { ZulipWebSocketGateway } from './zulip-websocket.gateway';
|
||||
import { ZulipWebSocketGateway } from './zulip_websocket.gateway';
|
||||
import { ZulipService, LoginResponse, ChatMessageResponse } from './zulip.service';
|
||||
import { SessionManagerService, GameSession } from './services/session-manager.service';
|
||||
import { SessionManagerService, GameSession } from './services/session_manager.service';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
|
||||
describe('ZulipWebSocketGateway', () => {
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ZulipService } from './zulip.service';
|
||||
import { SessionManagerService } from './services/session-manager.service';
|
||||
import { SessionManagerService } from './services/session_manager.service';
|
||||
|
||||
/**
|
||||
* 登录消息接口 - 按guide.md格式
|
||||
@@ -96,6 +96,29 @@ interface ClientData {
|
||||
connectedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip WebSocket网关类
|
||||
*
|
||||
* 职责:
|
||||
* - 处理所有Godot游戏客户端的WebSocket连接
|
||||
* - 实现游戏协议到Zulip协议的转换
|
||||
* - 提供统一的消息路由和权限控制
|
||||
* - 管理客户端连接状态和会话
|
||||
*
|
||||
* 主要方法:
|
||||
* - handleConnection(): 处理客户端连接建立
|
||||
* - handleDisconnect(): 处理客户端连接断开
|
||||
* - handleLogin(): 处理登录消息
|
||||
* - handleChat(): 处理聊天消息
|
||||
* - handlePositionUpdate(): 处理位置更新
|
||||
* - sendChatRender(): 向客户端发送聊天渲染消息
|
||||
*
|
||||
* 使用场景:
|
||||
* - 游戏客户端WebSocket通信的统一入口
|
||||
* - 消息协议转换和路由分发
|
||||
* - 连接状态管理和权限验证
|
||||
* - 实时消息推送和广播
|
||||
*/
|
||||
@Injectable()
|
||||
@WebSocketGateway({
|
||||
cors: { origin: '*' },
|
||||
26
src/core/zulip/index.ts
Normal file
26
src/core/zulip/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Zulip核心服务模块导出
|
||||
*
|
||||
* 功能描述:
|
||||
* - 统一导出Zulip核心服务的接口和类型
|
||||
* - 为业务层提供清晰的导入路径
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
|
||||
// 导出核心服务接口
|
||||
export * from './interfaces/zulip-core.interfaces';
|
||||
|
||||
// 导出核心服务模块
|
||||
export { ZulipCoreModule } from './zulip-core.module';
|
||||
|
||||
// 导出具体实现类(供内部使用)
|
||||
export { ZulipClientService } from './services/zulip_client.service';
|
||||
export { ZulipClientPoolService } from './services/zulip_client_pool.service';
|
||||
export { ConfigManagerService } from './services/config_manager.service';
|
||||
export { ApiKeySecurityService } from './services/api_key_security.service';
|
||||
export { ErrorHandlerService } from './services/error_handler.service';
|
||||
export { MonitoringService } from './services/monitoring.service';
|
||||
export { StreamInitializerService } from './services/stream_initializer.service';
|
||||
294
src/core/zulip/interfaces/zulip-core.interfaces.ts
Normal file
294
src/core/zulip/interfaces/zulip-core.interfaces.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Zulip核心服务接口定义
|
||||
*
|
||||
* 功能描述:
|
||||
* - 定义Zulip核心服务的抽象接口
|
||||
* - 分离业务逻辑与技术实现
|
||||
* - 支持依赖注入和接口切换
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
|
||||
/**
|
||||
* Zulip客户端配置接口
|
||||
*/
|
||||
export interface ZulipClientConfig {
|
||||
username: string;
|
||||
apiKey: string;
|
||||
realm: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip客户端实例接口
|
||||
*/
|
||||
export interface ZulipClientInstance {
|
||||
userId: string;
|
||||
config: ZulipClientConfig;
|
||||
client: any;
|
||||
queueId?: string;
|
||||
lastEventId: number;
|
||||
createdAt: Date;
|
||||
lastActivity: Date;
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息结果接口
|
||||
*/
|
||||
export interface SendMessageResult {
|
||||
success: boolean;
|
||||
messageId?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件队列注册结果接口
|
||||
*/
|
||||
export interface RegisterQueueResult {
|
||||
success: boolean;
|
||||
queueId?: string;
|
||||
lastEventId?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件结果接口
|
||||
*/
|
||||
export interface GetEventsResult {
|
||||
success: boolean;
|
||||
events?: any[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端池统计信息接口
|
||||
*/
|
||||
export interface PoolStats {
|
||||
totalClients: number;
|
||||
activeClients: number;
|
||||
clientsWithQueues: number;
|
||||
clientIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip客户端核心服务接口
|
||||
*
|
||||
* 职责:
|
||||
* - 封装Zulip REST API调用
|
||||
* - 处理API Key验证和错误处理
|
||||
* - 提供消息发送、事件队列管理等核心功能
|
||||
*/
|
||||
export interface IZulipClientService {
|
||||
/**
|
||||
* 创建并初始化Zulip客户端
|
||||
*/
|
||||
createClient(userId: string, config: ZulipClientConfig): Promise<ZulipClientInstance>;
|
||||
|
||||
/**
|
||||
* 验证API Key有效性
|
||||
*/
|
||||
validateApiKey(clientInstance: ZulipClientInstance): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 发送消息到指定Stream/Topic
|
||||
*/
|
||||
sendMessage(
|
||||
clientInstance: ZulipClientInstance,
|
||||
stream: string,
|
||||
topic: string,
|
||||
content: string,
|
||||
): Promise<SendMessageResult>;
|
||||
|
||||
/**
|
||||
* 注册事件队列
|
||||
*/
|
||||
registerQueue(
|
||||
clientInstance: ZulipClientInstance,
|
||||
eventTypes?: string[],
|
||||
): Promise<RegisterQueueResult>;
|
||||
|
||||
/**
|
||||
* 注销事件队列
|
||||
*/
|
||||
deregisterQueue(clientInstance: ZulipClientInstance): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 获取事件队列中的事件
|
||||
*/
|
||||
getEvents(
|
||||
clientInstance: ZulipClientInstance,
|
||||
dontBlock?: boolean,
|
||||
): Promise<GetEventsResult>;
|
||||
|
||||
/**
|
||||
* 销毁客户端实例
|
||||
*/
|
||||
destroyClient(clientInstance: ZulipClientInstance): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip客户端池服务接口
|
||||
*
|
||||
* 职责:
|
||||
* - 管理用户专用的Zulip客户端实例
|
||||
* - 维护客户端连接池和生命周期
|
||||
* - 处理客户端的创建、销毁和状态管理
|
||||
*/
|
||||
export interface IZulipClientPoolService {
|
||||
/**
|
||||
* 为用户创建专用Zulip客户端
|
||||
*/
|
||||
createUserClient(userId: string, config: ZulipClientConfig): Promise<ZulipClientInstance>;
|
||||
|
||||
/**
|
||||
* 获取用户的Zulip客户端
|
||||
*/
|
||||
getUserClient(userId: string): Promise<ZulipClientInstance | null>;
|
||||
|
||||
/**
|
||||
* 检查用户客户端是否存在
|
||||
*/
|
||||
hasUserClient(userId: string): boolean;
|
||||
|
||||
/**
|
||||
* 发送消息到指定Stream/Topic
|
||||
*/
|
||||
sendMessage(
|
||||
userId: string,
|
||||
stream: string,
|
||||
topic: string,
|
||||
content: string,
|
||||
): Promise<SendMessageResult>;
|
||||
|
||||
/**
|
||||
* 注册事件队列
|
||||
*/
|
||||
registerEventQueue(userId: string): Promise<RegisterQueueResult>;
|
||||
|
||||
/**
|
||||
* 注销事件队列
|
||||
*/
|
||||
deregisterEventQueue(userId: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 销毁用户客户端
|
||||
*/
|
||||
destroyUserClient(userId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取客户端池统计信息
|
||||
*/
|
||||
getPoolStats(): PoolStats;
|
||||
|
||||
/**
|
||||
* 清理过期客户端
|
||||
*/
|
||||
cleanupIdleClients(maxIdleMinutes?: number): Promise<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip配置管理服务接口
|
||||
*
|
||||
* 职责:
|
||||
* - 管理地图到Zulip Stream的映射配置
|
||||
* - 提供Zulip服务器连接配置
|
||||
* - 支持配置文件的热重载
|
||||
*/
|
||||
export interface IZulipConfigService {
|
||||
/**
|
||||
* 根据地图获取对应的Stream
|
||||
*/
|
||||
getStreamByMap(mapId: string): string | null;
|
||||
|
||||
/**
|
||||
* 根据Stream名称获取地图ID
|
||||
*/
|
||||
getMapIdByStream(streamName: string): string | null;
|
||||
|
||||
/**
|
||||
* 根据交互对象获取Topic
|
||||
*/
|
||||
getTopicByObject(mapId: string, objectId: string): string | null;
|
||||
|
||||
/**
|
||||
* 获取Zulip配置
|
||||
*/
|
||||
getZulipConfig(): any;
|
||||
|
||||
/**
|
||||
* 检查地图是否存在
|
||||
*/
|
||||
hasMap(mapId: string): boolean;
|
||||
|
||||
/**
|
||||
* 检查Stream是否存在
|
||||
*/
|
||||
hasStream(streamName: string): boolean;
|
||||
|
||||
/**
|
||||
* 获取所有地图ID列表
|
||||
*/
|
||||
getAllMapIds(): string[];
|
||||
|
||||
/**
|
||||
* 获取所有Stream名称列表
|
||||
*/
|
||||
getAllStreams(): string[];
|
||||
|
||||
/**
|
||||
* 热重载配置
|
||||
*/
|
||||
reloadConfig(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 验证配置有效性
|
||||
*/
|
||||
validateConfig(): Promise<{ valid: boolean; errors: string[] }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip事件处理服务接口
|
||||
*
|
||||
* 职责:
|
||||
* - 处理从Zulip接收的事件队列消息
|
||||
* - 将Zulip消息转换为游戏协议格式
|
||||
* - 管理事件队列的生命周期
|
||||
*/
|
||||
export interface IZulipEventProcessorService {
|
||||
/**
|
||||
* 启动事件处理循环
|
||||
*/
|
||||
startEventProcessing(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 停止事件处理循环
|
||||
*/
|
||||
stopEventProcessing(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 注册事件队列
|
||||
*/
|
||||
registerEventQueue(queueId: string, userId: string, lastEventId?: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 注销事件队列
|
||||
*/
|
||||
unregisterEventQueue(queueId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 处理Zulip消息事件
|
||||
*/
|
||||
processMessageEvent(event: any, senderUserId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 设置消息分发器
|
||||
*/
|
||||
setMessageDistributor(distributor: any): void;
|
||||
|
||||
/**
|
||||
* 获取事件处理统计信息
|
||||
*/
|
||||
getProcessingStats(): any;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
ApiKeySecurityService,
|
||||
SecurityEventType,
|
||||
SecuritySeverity,
|
||||
} from './api-key-security.service';
|
||||
} from './api_key_security.service';
|
||||
import { IRedisService } from '../../../core/redis/redis.interface';
|
||||
|
||||
describe('ApiKeySecurityService', () => {
|
||||
@@ -100,6 +100,28 @@ export interface GetApiKeyResult {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API密钥安全服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 管理Zulip API密钥的安全存储
|
||||
* - 提供API密钥的加密和解密功能
|
||||
* - 记录API密钥的访问日志
|
||||
* - 监控API密钥的使用情况和安全事件
|
||||
*
|
||||
* 主要方法:
|
||||
* - storeApiKey(): 安全存储加密的API密钥
|
||||
* - retrieveApiKey(): 检索并解密API密钥
|
||||
* - validateApiKey(): 验证API密钥的有效性
|
||||
* - logSecurityEvent(): 记录安全相关事件
|
||||
* - getAccessStats(): 获取API密钥访问统计
|
||||
*
|
||||
* 使用场景:
|
||||
* - 用户API密钥的安全存储
|
||||
* - API密钥访问时的解密操作
|
||||
* - 安全事件的监控和记录
|
||||
* - API密钥使用情况的统计分析
|
||||
*/
|
||||
@Injectable()
|
||||
export class ApiKeySecurityService {
|
||||
private readonly logger = new Logger(ApiKeySecurityService.name);
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import { ConfigManagerService, MapConfig, ZulipConfig } from './config-manager.service';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
import { ConfigManagerService, MapConfig, ZulipConfig } from './config_manager.service';
|
||||
import { AppLoggerService } from '../../utils/logger/logger.service';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -108,6 +108,28 @@ export interface InteractionObject extends InteractionObjectConfig {
|
||||
mapId: string; // 所属地图ID
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置管理服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 管理地图到Zulip Stream的映射配置
|
||||
* - 提供Zulip服务器连接配置
|
||||
* - 支持配置文件的热重载
|
||||
* - 验证配置的完整性和有效性
|
||||
*
|
||||
* 主要方法:
|
||||
* - loadMapConfig(): 加载地图配置文件
|
||||
* - getStreamByMap(): 根据地图ID获取对应的Stream
|
||||
* - getZulipConfig(): 获取Zulip服务器配置
|
||||
* - validateConfig(): 验证配置文件格式
|
||||
* - enableConfigWatcher(): 启用配置文件监控
|
||||
*
|
||||
* 使用场景:
|
||||
* - 系统启动时加载配置
|
||||
* - 消息路由时查找Stream映射
|
||||
* - 配置文件变更时自动重载
|
||||
* - 配置验证和错误处理
|
||||
*/
|
||||
@Injectable()
|
||||
export class ConfigManagerService implements OnModuleDestroy {
|
||||
private mapConfigs: Map<string, MapConfig> = new Map();
|
||||
@@ -216,6 +238,9 @@ export class ConfigManagerService implements OnModuleDestroy {
|
||||
* 4. 存储到内存映射
|
||||
*
|
||||
* @returns Promise<void>
|
||||
*
|
||||
* @throws Error 当配置格式无效时
|
||||
* @throws Error 当文件读取失败时
|
||||
*/
|
||||
async loadMapConfig(): Promise<void> {
|
||||
this.logger.log('开始加载地图配置', {
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
LoadStatus,
|
||||
ErrorHandlingResult,
|
||||
RetryConfig,
|
||||
} from './error-handler.service';
|
||||
} from './error_handler.service';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
|
||||
describe('ErrorHandlerService', () => {
|
||||
@@ -115,6 +115,28 @@ export enum LoadStatus {
|
||||
CRITICAL = 'critical',
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 统一处理系统错误和异常
|
||||
* - 实现重试机制和服务降级
|
||||
* - 监控系统健康状态和负载
|
||||
* - 提供错误恢复和告警功能
|
||||
*
|
||||
* 主要方法:
|
||||
* - handleError(): 处理各类错误和异常
|
||||
* - retryWithBackoff(): 带退避策略的重试机制
|
||||
* - enableDegradedMode(): 启用服务降级模式
|
||||
* - getServiceStatus(): 获取服务状态
|
||||
* - recordError(): 记录错误统计
|
||||
*
|
||||
* 使用场景:
|
||||
* - Zulip API调用失败时的错误处理
|
||||
* - 网络连接异常的重试机制
|
||||
* - 系统负载过高时的服务降级
|
||||
* - 错误监控和告警通知
|
||||
*/
|
||||
@Injectable()
|
||||
export class ErrorHandlerService extends EventEmitter implements OnModuleDestroy {
|
||||
private readonly logger = new Logger(ErrorHandlerService.name);
|
||||
@@ -182,6 +182,29 @@ export interface MonitoringStats {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 监控Zulip集成系统的运行状态
|
||||
* - 收集和统计系统性能指标
|
||||
* - 提供健康检查和告警功能
|
||||
* - 生成系统监控报告
|
||||
*
|
||||
* 主要方法:
|
||||
* - recordConnection(): 记录连接统计
|
||||
* - recordApiCall(): 记录API调用统计
|
||||
* - recordMessage(): 记录消息统计
|
||||
* - triggerAlert(): 触发告警
|
||||
* - getSystemStats(): 获取系统统计信息
|
||||
* - performHealthCheck(): 执行健康检查
|
||||
*
|
||||
* 使用场景:
|
||||
* - 系统性能监控和统计
|
||||
* - 异常情况的告警通知
|
||||
* - 系统健康状态检查
|
||||
* - 运维数据的收集和分析
|
||||
*/
|
||||
@Injectable()
|
||||
export class MonitoringService extends EventEmitter implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly logger = new Logger(MonitoringService.name);
|
||||
@@ -21,8 +21,30 @@
|
||||
*/
|
||||
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { ConfigManagerService } from './config-manager.service';
|
||||
import { ConfigManagerService } from './config_manager.service';
|
||||
|
||||
/**
|
||||
* Stream初始化服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 系统启动时自动检查并创建Zulip Streams
|
||||
* - 确保所有地图对应的Stream都存在
|
||||
* - 验证Stream配置的完整性
|
||||
* - 提供Stream初始化状态监控
|
||||
*
|
||||
* 主要方法:
|
||||
* - onModuleInit(): 模块初始化时自动执行
|
||||
* - initializeStreams(): 初始化所有必需的Streams
|
||||
* - createStreamIfNotExists(): 检查并创建单个Stream
|
||||
* - validateStreamConfig(): 验证Stream配置
|
||||
* - getInitializationStatus(): 获取初始化状态
|
||||
*
|
||||
* 使用场景:
|
||||
* - 系统启动时自动初始化Streams
|
||||
* - 确保消息路由的目标Stream存在
|
||||
* - 新增地图时自动创建对应Stream
|
||||
* - 系统部署和配置验证
|
||||
*/
|
||||
@Injectable()
|
||||
export class StreamInitializerService implements OnModuleInit {
|
||||
private readonly logger = new Logger(StreamInitializerService.name);
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import * as fc from 'fast-check';
|
||||
import { ZulipClientService, ZulipClientConfig, ZulipClientInstance } from './zulip-client.service';
|
||||
import { ZulipClientService, ZulipClientConfig, ZulipClientInstance } from './zulip_client.service';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
|
||||
describe('ZulipClientService', () => {
|
||||
@@ -77,6 +77,28 @@ export interface GetEventsResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip客户端服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 封装Zulip REST API调用
|
||||
* - 处理Zulip客户端的创建和配置
|
||||
* - 管理事件队列的注册和轮询
|
||||
* - 提供消息发送和接收功能
|
||||
*
|
||||
* 主要方法:
|
||||
* - createClient(): 创建并初始化Zulip客户端
|
||||
* - registerQueue(): 注册Zulip事件队列
|
||||
* - sendMessage(): 发送消息到Zulip Stream
|
||||
* - getEvents(): 获取Zulip事件
|
||||
* - validateConfig(): 验证客户端配置
|
||||
*
|
||||
* 使用场景:
|
||||
* - 为每个用户创建独立的Zulip客户端
|
||||
* - 处理与Zulip服务器的所有通信
|
||||
* - 消息的发送和事件的接收
|
||||
* - API调用的错误处理和重试
|
||||
*/
|
||||
@Injectable()
|
||||
export class ZulipClientService {
|
||||
private readonly logger = new Logger(ZulipClientService.name);
|
||||
@@ -12,9 +12,9 @@
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ZulipClientPoolService, PoolStats } from './zulip-client-pool.service';
|
||||
import { ZulipClientService, ZulipClientConfig, ZulipClientInstance } from './zulip-client.service';
|
||||
import { AppLoggerService } from '../../../core/utils/logger/logger.service';
|
||||
import { ZulipClientPoolService, PoolStats } from './zulip_client_pool.service';
|
||||
import { ZulipClientService, ZulipClientConfig, ZulipClientInstance } from './zulip_client.service';
|
||||
import { AppLoggerService } from '../../utils/logger/logger.service';
|
||||
|
||||
describe('ZulipClientPoolService', () => {
|
||||
let service: ZulipClientPoolService;
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
SendMessageResult,
|
||||
RegisterQueueResult,
|
||||
GetEventsResult,
|
||||
} from './zulip-client.service';
|
||||
} from './zulip_client.service';
|
||||
|
||||
/**
|
||||
* 用户客户端信息接口
|
||||
@@ -57,6 +57,28 @@ export interface PoolStats {
|
||||
clientIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zulip客户端池服务类
|
||||
*
|
||||
* 职责:
|
||||
* - 管理用户专用的Zulip客户端实例
|
||||
* - 维护客户端连接池和生命周期
|
||||
* - 处理客户端的创建、销毁和状态管理
|
||||
* - 提供客户端池统计和监控功能
|
||||
*
|
||||
* 主要方法:
|
||||
* - createUserClient(): 为用户创建专用Zulip客户端
|
||||
* - getUserClient(): 获取用户的Zulip客户端
|
||||
* - destroyUserClient(): 销毁用户的Zulip客户端
|
||||
* - getPoolStats(): 获取客户端池统计信息
|
||||
* - startEventPolling(): 启动事件轮询
|
||||
*
|
||||
* 使用场景:
|
||||
* - 玩家登录时创建专用客户端
|
||||
* - 消息发送时获取客户端实例
|
||||
* - 玩家登出时清理客户端资源
|
||||
* - 系统监控和性能统计
|
||||
*/
|
||||
@Injectable()
|
||||
export class ZulipClientPoolService implements OnModuleDestroy {
|
||||
private readonly clientPool = new Map<string, UserClientInfo>();
|
||||
68
src/core/zulip/zulip-core.module.ts
Normal file
68
src/core/zulip/zulip-core.module.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Zulip核心服务模块
|
||||
*
|
||||
* 功能描述:
|
||||
* - 提供Zulip技术实现相关的核心服务
|
||||
* - 封装第三方API调用和技术细节
|
||||
* - 为业务层提供抽象接口
|
||||
*
|
||||
* @author angjustinl
|
||||
* @version 1.0.0
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ZulipClientService } from './services/zulip_client.service';
|
||||
import { ZulipClientPoolService } from './services/zulip_client_pool.service';
|
||||
import { ConfigManagerService } from './services/config_manager.service';
|
||||
import { ApiKeySecurityService } from './services/api_key_security.service';
|
||||
import { ErrorHandlerService } from './services/error_handler.service';
|
||||
import { MonitoringService } from './services/monitoring.service';
|
||||
import { StreamInitializerService } from './services/stream_initializer.service';
|
||||
import { RedisModule } from '../redis/redis.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// Redis模块 - ApiKeySecurityService需要REDIS_SERVICE
|
||||
RedisModule,
|
||||
],
|
||||
providers: [
|
||||
// 核心客户端服务
|
||||
{
|
||||
provide: 'ZULIP_CLIENT_SERVICE',
|
||||
useClass: ZulipClientService,
|
||||
},
|
||||
{
|
||||
provide: 'ZULIP_CLIENT_POOL_SERVICE',
|
||||
useClass: ZulipClientPoolService,
|
||||
},
|
||||
{
|
||||
provide: 'ZULIP_CONFIG_SERVICE',
|
||||
useClass: ConfigManagerService,
|
||||
},
|
||||
|
||||
// 辅助服务
|
||||
ApiKeySecurityService,
|
||||
ErrorHandlerService,
|
||||
MonitoringService,
|
||||
StreamInitializerService,
|
||||
|
||||
// 直接提供类(用于内部依赖)
|
||||
ZulipClientService,
|
||||
ZulipClientPoolService,
|
||||
ConfigManagerService,
|
||||
],
|
||||
exports: [
|
||||
// 导出接口标识符供业务层使用
|
||||
'ZULIP_CLIENT_SERVICE',
|
||||
'ZULIP_CLIENT_POOL_SERVICE',
|
||||
'ZULIP_CONFIG_SERVICE',
|
||||
|
||||
// 导出辅助服务
|
||||
ApiKeySecurityService,
|
||||
ErrorHandlerService,
|
||||
MonitoringService,
|
||||
StreamInitializerService,
|
||||
],
|
||||
})
|
||||
export class ZulipCoreModule {}
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "Node16",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"moduleResolution": "node16",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
Reference in New Issue
Block a user