17 Commits

Author SHA1 Message Date
moyin
963e6ca90f refactor(auth): 移除登录注册时的Zulip内存关联逻辑
范围: src/business/auth/
涉及文件:
- src/business/auth/login.service.ts
- src/business/auth/register.service.ts

主要改进:
- 移除登录时建立Zulip内存关联的代码
- 移除注册时建立Zulip内存关联的代码
- 改为在WebSocket连接时由Zulip客户端创建内存关联
- 优化了内存关联的时机,避免不必要的提前创建

技术说明:
- 原逻辑在登录/注册时就建立内存关联,但用户可能不会立即使用Zulip
- 新逻辑延迟到WebSocket连接时创建,更加合理和高效
- 减少了登录/注册流程的复杂度和耦合度
2026-01-19 17:59:58 +08:00
moyin
cd2a197288 feat(chat): 添加地图切换功能
范围: src/gateway/chat/
- 新增 change_map 事件处理
- 实现 handleChangeMap() 方法
- 支持玩家在不同地图间切换
- 自动更新房间成员和广播通知
- 完善地图切换的错误处理

功能说明:
- 玩家可以通过 WebSocket 发送 change_map 事件切换地图
- 自动处理房间加入/离开逻辑
- 向旧地图广播玩家离开,向新地图广播玩家加入
- 支持携带初始位置坐标,默认使用 (400, 300)
2026-01-19 17:43:59 +08:00
01787d701c Merge pull request 'refactor:将 ZulipAccountsModule 改为全局单例模块' (#51) from docs/ai-reading-guide-20260115 into main
Reviewed-on: #51
2026-01-15 15:00:14 +08:00
6e7de1a11a Merge branch 'main' into docs/ai-reading-guide-20260115 2026-01-15 15:00:07 +08:00
moyin
d92a078fc7 refactor:将 ZulipAccountsModule 改为全局单例模块
- 在 AppModule 中统一导入 ZulipAccountsModule.forRoot()
- 移除 admin.module、auth.module、zulip.module 中的重复导入
- 添加数据库 charset: utf8mb4 配置,支持中文和 emoji
2026-01-15 14:58:28 +08:00
9785908ca9 Merge pull request 'docs/ai-reading-guide-20260115' (#50) from docs/ai-reading-guide-20260115 into main
Reviewed-on: #50
2026-01-15 14:31:33 +08:00
592a745b8f Merge branch 'main' into docs/ai-reading-guide-20260115 2026-01-15 14:31:26 +08:00
moyin
cde20c6fd7 docs:补充合并文档不纳入Git提交的规范说明
- 添加合并文档排除原因说明
- 补充操作规范和.gitignore配置建议
- 更新提交原则中的合并文档排除要求
2026-01-15 14:21:14 +08:00
moyin
a8de2564b6 docs:添加异常处理完整性检查规范
- 新增异常吞没问题定义和检查规则
- 添加Service/Repository层异常传播规范
- 补充常见错误模式和修复示例
- 更新检查清单和执行步骤顺序
2026-01-15 14:20:55 +08:00
moyin
9f4d291619 style(auth): 优化auth模块代码规范
范围: src/business/auth/
- register.service.ts: 清理未使用的导入TokenPair,增强userId非空验证
- register.service.spec.ts: 清理未使用的变量apiKeySecurityService
2026-01-15 14:17:38 +08:00
moyin
4f18f0fec6 refactor(login_core): 消除代码重复,提取手机号查找为私有方法
范围:src/core/login_core/
- 提取手机号查找逻辑为 findUserByPhone() 私有方法
- 添加 isPhoneExists() 私有方法检查手机号是否存在
- 消除 login、validateUserUniqueness、sendPasswordResetCode 等方法中的重复代码
- 测试文件添加文件头注释,清理未使用的 UsersService 导入
- 更新版本号 1.1.0 -> 1.1.1
2026-01-15 14:13:48 +08:00
moyin
519394645a docs(zulip_core): 完善模块文档和优化账号服务逻辑
范围:src/core/zulip_core/
- 补充README.md缺失的服务文档(用户注册、用户管理、动态配置、错误处理、监控日志)
- 优化zulip_account.service.ts中已存在用户的处理逻辑
- 增强userId获取的可靠性,优先使用userInfo,其次使用apiKeyResult
- 版本更新:1.1.1 -> 1.2.0
2026-01-15 13:53:56 +08:00
moyin
223ba2abb8 style(zulip_accounts): 代码规范优化 - 清理未使用导入和修复异常处理
范围:src/core/db/zulip_accounts/
涉及文件:
- zulip_accounts.repository.ts
- zulip_accounts.service.ts
- zulip_accounts_memory.service.ts

主要改进:
- 清理未使用的导入(FindOptionsWhere, NotFoundException, ConflictException)
- 修复异常处理:确保catch块中正确抛出异常,避免异常吞没
- 更新文件头部修改记录和版本号
2026-01-15 13:46:24 +08:00
moyin
e54d5e3939 style(users): 优化Core层users模块代码规范
范围: src/core/db/users/
- base_users.service.ts: 为保护方法补充@example示例
- users.constants.ts: 补充职责分离描述

检查人员: moyin
检查日期: 2026-01-15
2026-01-15 13:38:36 +08:00
299627dac7 Merge pull request 'docs:删除多余的文档' (#49) from feature/gateway-module-integration-20260115 into main
Reviewed-on: #49
2026-01-15 11:21:53 +08:00
ae3a256c52 Merge branch 'main' into feature/gateway-module-integration-20260115 2026-01-15 11:21:47 +08:00
moyin
434766beb5 docs:删除多余的文档 2026-01-15 11:21:17 +08:00
20 changed files with 684 additions and 288 deletions

View File

@@ -177,6 +177,227 @@ private validateUserData(userData: CreateUserDto | UpdateUserDto): void {
}
```
## 🚨 异常处理完整性检查(关键规范)
### 问题定义
**异常吞没Exception Swallowing** 是指在 catch 块中捕获异常后,只记录日志但不重新抛出,导致:
- 调用方无法感知错误
- 方法返回 undefined 而非声明的类型
- 数据不一致或静默失败
- 难以调试和定位问题
### 检查规则
#### 规则1catch 块必须有明确的异常处理策略
```typescript
// ❌ 严重错误catch 块吞没异常
async create(createDto: CreateDto): Promise<ResponseDto> {
try {
const result = await this.repository.create(createDto);
return this.toResponseDto(result);
} catch (error) {
this.logger.error('创建失败', error);
// 错误:没有 throw方法返回 undefined
// 但声明返回 Promise<ResponseDto>
}
}
// ❌ 错误:只记录日志不处理
async findById(id: string): Promise<Entity> {
try {
return await this.repository.findById(id);
} catch (error) {
monitor.error(error);
// 错误:异常被吞没,调用方无法感知
}
}
// ✅ 正确:重新抛出异常
async create(createDto: CreateDto): Promise<ResponseDto> {
try {
const result = await this.repository.create(createDto);
return this.toResponseDto(result);
} catch (error) {
this.logger.error('创建失败', error);
throw error; // 必须重新抛出
}
}
// ✅ 正确:转换为特定异常类型
async create(createDto: CreateDto): Promise<ResponseDto> {
try {
const result = await this.repository.create(createDto);
return this.toResponseDto(result);
} catch (error) {
this.logger.error('创建失败', error);
if (error.message.includes('duplicate')) {
throw new ConflictException('记录已存在');
}
throw error; // 其他错误继续抛出
}
}
// ✅ 正确返回错误响应仅限顶层API
async create(createDto: CreateDto): Promise<ApiResponse<ResponseDto>> {
try {
const result = await this.repository.create(createDto);
return { success: true, data: this.toResponseDto(result) };
} catch (error) {
this.logger.error('创建失败', error);
return {
success: false,
error: error.message,
errorCode: 'CREATE_FAILED'
}; // 顶层API可以返回错误响应
}
}
```
#### 规则2Service 层方法必须传播异常
```typescript
// ❌ 错误Service 层吞没异常
@Injectable()
export class UserService {
async update(id: string, dto: UpdateDto): Promise<ResponseDto> {
try {
const result = await this.repository.update(id, dto);
return this.toResponseDto(result);
} catch (error) {
this.logger.error('更新失败', { id, error });
// 错误Service 层不应吞没异常
}
}
}
// ✅ 正确Service 层传播异常
@Injectable()
export class UserService {
async update(id: string, dto: UpdateDto): Promise<ResponseDto> {
try {
const result = await this.repository.update(id, dto);
return this.toResponseDto(result);
} catch (error) {
this.logger.error('更新失败', { id, error });
throw error; // 传播给调用方处理
}
}
}
```
#### 规则3Repository 层必须传播数据库异常
```typescript
// ❌ 错误Repository 层吞没数据库异常
@Injectable()
export class UserRepository {
async findById(id: bigint): Promise<User | null> {
try {
return await this.repository.findOne({ where: { id } });
} catch (error) {
this.logger.error('查询失败', { id, error });
// 错误:数据库异常被吞没,调用方以为查询成功但返回 null
}
}
}
// ✅ 正确Repository 层传播异常
@Injectable()
export class UserRepository {
async findById(id: bigint): Promise<User | null> {
try {
return await this.repository.findOne({ where: { id } });
} catch (error) {
this.logger.error('查询失败', { id, error });
throw error; // 数据库异常必须传播
}
}
}
```
### 异常处理层级规范
| 层级 | 异常处理策略 | 说明 |
|------|-------------|------|
| **Repository 层** | 必须 throw | 数据访问异常必须传播 |
| **Service 层** | 必须 throw | 业务异常必须传播给调用方 |
| **Business 层** | 必须 throw | 业务逻辑异常必须传播 |
| **Gateway/Controller 层** | 可以转换为 HTTP 响应 | 顶层可以将异常转换为错误响应 |
### 检查清单
- [ ] **所有 catch 块是否有 throw 语句?**
- [ ] **方法返回类型与实际返回是否一致?**(避免返回 undefined
- [ ] **Service/Repository 层是否传播异常?**
- [ ] **只有顶层 API 才能将异常转换为错误响应?**
- [ ] **异常日志是否包含足够的上下文信息?**
### 快速检查命令
```bash
# 搜索可能吞没异常的 catch 块(没有 throw 的 catch
# 在代码审查时重点关注这些位置
grep -rn "catch.*error" --include="*.ts" | grep -v "throw"
```
### 常见错误模式
#### 模式1性能监控后忘记抛出
```typescript
// ❌ 常见错误
} catch (error) {
monitor.error(error); // 只记录监控
// 忘记 throw error;
}
// ✅ 正确
} catch (error) {
monitor.error(error);
throw error; // 必须抛出
}
```
#### 模式2条件分支遗漏 throw
```typescript
// ❌ 常见错误
} catch (error) {
if (error.code === 'DUPLICATE') {
throw new ConflictException('已存在');
}
// else 分支忘记 throw
this.logger.error(error);
}
// ✅ 正确
} catch (error) {
if (error.code === 'DUPLICATE') {
throw new ConflictException('已存在');
}
this.logger.error(error);
throw error; // else 分支也要抛出
}
```
#### 模式3返回类型不匹配
```typescript
// ❌ 错误:声明返回 Promise<Entity> 但可能返回 undefined
async findById(id: string): Promise<Entity> {
try {
return await this.repo.findById(id);
} catch (error) {
this.logger.error(error);
// 没有 throwTypeScript 不会报错但运行时返回 undefined
}
}
// ✅ 正确
async findById(id: string): Promise<Entity> {
try {
return await this.repo.findById(id);
} catch (error) {
this.logger.error(error);
throw error;
}
}
```
## 🚫 TODO项处理强制要求
### 处理原则
@@ -323,12 +544,19 @@ describe('AdminService Properties', () => {
- 抽象为可复用的工具方法
- 消除代码重复
6. **处理所有TODO项**
6. **🚨 检查异常处理完整性(关键步骤)**
- 扫描所有 catch 块
- 检查是否有 throw 语句
- 验证 Service/Repository 层是否传播异常
- 确认方法返回类型与实际返回一致
- 识别异常吞没模式并修复
7. **处理所有TODO项**
- 搜索所有TODO注释
- 要求真正实现功能或删除代码
- 确保最终文件无TODO项
7. **游戏服务器特殊检查**
8. **游戏服务器特殊检查**
- WebSocket连接管理完整性
- 双模式服务行为一致性
- 属性测试实现质量

View File

@@ -505,6 +505,37 @@ mkdir -p docs/merge-requests
- **监控要点**:关注 [具体的监控指标]
```
### 🚨 合并文档不纳入Git提交
**重要合并文档仅用于本地记录和合并操作参考不应加入到Git提交中**
#### 原因说明
- 合并文档是临时性的操作记录,不属于项目代码的一部分
- 避免在代码仓库中产生大量临时文档
- 合并完成后相关信息已体现在Git提交历史和PR记录中
#### 操作规范
```bash
# ❌ 禁止将合并文档加入Git提交
git add docs/merge-requests/ # 禁止!
# ✅ 正确做法:确保合并文档不被提交
# 方法1在.gitignore中已配置忽略推荐
# 方法2提交时明确排除
git add . -- ':!docs/merge-requests/'
# ✅ 检查暂存区,确认没有合并文档
git diff --cached --name-only | grep "merge-requests"
# 如果有输出,需要取消暂存
git reset HEAD docs/merge-requests/
```
#### .gitignore 配置建议
确保项目的 `.gitignore` 文件中包含:
```
# 合并文档目录(不纳入版本控制)
docs/merge-requests/
```
### 📝 独立合并文档创建示例
#### 1. 创建合并文档目录(如果不存在)
@@ -689,6 +720,7 @@ git remote show [远程仓库名]
- **完整性**:每次提交的代码都应该能正常运行
- **描述性**:提交信息要清晰描述改动内容、范围和原因
- **一致性**:文件修改记录必须与实际修改内容一致
- **合并文档排除**`docs/merge-requests/` 目录下的合并文档不纳入Git提交
### 质量保证
- 提交前必须验证代码能正常运行

View File

@@ -1,151 +0,0 @@
# 网关模块集成与文档优化合并请求
## 📋 变更概述
本次合并请求包含网关模块的集成工作和命名规范文档的优化,主要涉及应用主模块的架构完善和开发规范文档的更新。
## 🔍 主要变更内容
### 功能集成
- **网关模块集成**:完善 `src/app.module.ts` 应用主模块
- 集成 ChatGatewayModule聊天网关模块
- 集成 ZulipGatewayModuleZulip网关模块提供HTTP API接口
- 优化模块注释,明确各模块职责
- 完善应用架构,区分网关层和业务层职责
### 文档优化
- **命名规范文档**:优化 `docs/ai-reading/step1-naming-convention.md`
- 调整扁平化标准:从"≤3个文件"改为"1-2个文件"
- 明确单文件必须扁平化,双文件建议扁平化
- ≥3个文件保持独立文件夹结构
- 更新相关检查步骤和常见错误说明
## 📊 影响范围
- **修改文件数量**2个文件
- src/app.module.ts应用主模块
- docs/ai-reading/step1-naming-convention.md命名规范文档
- **涉及模块**:应用主模块、网关层
- **新增代码行数**+11行
- **删除代码行数**-6行
## 🧪 测试验证
- [x] 应用启动测试通过
- [x] 模块导入无循环依赖
- [x] 网关模块功能正常
- [x] 文档内容准确性检查通过
## 📝 提交记录
### 提交1文档优化
```
docs优化命名规范中的扁平化标准说明
- 将扁平化标准从≤3个文件调整为1-2个文件
- 明确单文件必须扁平化,双文件建议扁平化
- ≥3个文件保持独立文件夹结构
- 更新相关检查步骤和常见错误说明
```
### 提交2功能集成
```
feat集成聊天和Zulip网关模块到应用主模块
- 添加ChatGatewayModule到应用模块导入列表
- 添加ZulipGatewayModule到应用模块导入列表
- 优化模块注释说明,明确各网关模块职责
- 完善模块架构,区分网关层和业务层职责
```
## 🔗 相关信息
- **分支名称**feature/gateway-module-integration-20260115
- **基于分支**feature/code-standard-zulip-20260114
- **创建日期**2026-01-15
- **提交人员**moyin
- **提交数量**2个提交
## 📝 文件变更详情
### 1. src/app.module.ts
**变更类型**:功能增强
**主要变更**
- 新增导入:`ChatGatewayModule`
- 新增导入:`ZulipGatewayModule`
- 优化模块注释:
- `AuthGatewayModule``认证网关模块`
- 新增 `ChatGatewayModule``聊天网关模块`
- 新增 `ZulipGatewayModule``Zulip网关模块HTTP API接口`
- `ZulipModule``Zulip业务模块业务逻辑`
**架构改进**
- 明确网关层职责处理HTTP请求和WebSocket连接
- 明确业务层职责:处理业务逻辑和数据处理
- 完善模块分层架构
### 2. docs/ai-reading/step1-naming-convention.md
**变更类型**:文档优化
**主要变更**
- 扁平化标准调整:
- 旧标准:`≤3个文件必须扁平化处理`
- 新标准:
- `1个文件必须扁平化处理`
- `2个文件建议扁平化处理除非是完整功能模块`
- `≥3个文件保持独立文件夹`
- 更新常见错误说明:`遗漏≤3个文件文件夹的识别``遗漏单文件或双文件文件夹的识别`
- 更新检查步骤:`识别需要扁平化的文件夹≤3个文件``识别需要扁平化的文件夹1-2个文件`
**优化理由**
- 更精确的扁平化标准,避免过度扁平化
- 3个文件的文件夹通常代表完整功能模块应保持独立
- 提高代码组织的合理性和可维护性
## 📋 审查要点
请重点关注以下方面:
1. **模块集成正确性**:网关模块是否正确导入到应用主模块
2. **模块注释准确性**:模块注释是否准确反映模块职责
3. **架构合理性**:网关层和业务层职责是否清晰分离
4. **文档准确性**:扁平化标准调整是否合理
5. **向后兼容性**:变更是否影响现有功能
## ⚠️ 注意事项
- 本次变更为功能增强和文档优化,不涉及破坏性变更
- 新增的网关模块已在之前的开发中完成测试
- 扁平化标准调整不影响现有代码结构
- 建议在合并后验证应用启动和网关功能
## 🚀 合并后操作
1. 验证应用启动正常
2. 测试聊天网关功能
3. 测试Zulip网关功能
4. 确认模块导入无循环依赖
5. 通知团队成员架构变更和文档更新
## 🎯 架构说明
### 模块分层架构
```
应用层 (app.module.ts)
├── 网关层 (Gateway Layer)
│ ├── AuthGatewayModule - 认证网关HTTP + WebSocket
│ ├── ChatGatewayModule - 聊天网关WebSocket
│ └── ZulipGatewayModule - Zulip网关HTTP API
├── 业务层 (Business Layer)
│ ├── ZulipModule - Zulip业务逻辑
│ ├── UserMgmtModule - 用户管理业务
│ └── AdminModule - 管理员业务
└── 核心层 (Core Layer)
├── LoginCoreModule - 登录核心
├── SecurityCoreModule - 安全核心
└── RedisModule - Redis核心
```
### 职责划分
- **网关层**:处理外部请求,协议转换,请求路由
- **业务层**:业务逻辑处理,数据验证,业务规则
- **核心层**:基础设施,通用功能,底层服务
---
**文档生成时间**2026-01-15
**对应分支**feature/gateway-module-integration-20260115
**合并状态**:待合并
**提交数量**2个提交1个文档优化 + 1个功能集成

View File

@@ -6,6 +6,7 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerModule } from './core/utils/logger/logger.module';
import { UsersModule } from './core/db/users/users.module';
import { ZulipAccountsModule } from './core/db/zulip_accounts/zulip_accounts.module';
import { LoginCoreModule } from './core/login_core/login_core.module';
import { AuthGatewayModule } from './gateway/auth/auth.gateway.module';
import { ChatGatewayModule } from './gateway/chat/chat.gateway.module';
@@ -62,6 +63,8 @@ function isDatabaseConfigured(): boolean {
database: process.env.DB_NAME,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
// 字符集配置 - 支持中文和emoji
charset: 'utf8mb4',
// 添加连接超时和重试配置
connectTimeout: 10000,
retryAttempts: 3,
@@ -70,6 +73,8 @@ function isDatabaseConfigured(): boolean {
] : []),
// 根据数据库配置选择用户模块模式
isDatabaseConfigured() ? UsersModule.forDatabase() : UsersModule.forMemory(),
// Zulip账号关联模块 - 全局单例,其他模块无需重复导入
ZulipAccountsModule.forRoot(),
LoginCoreModule,
AuthGatewayModule, // 认证网关模块
ChatGatewayModule, // 聊天网关模块

View File

@@ -27,7 +27,6 @@ import { AdminCoreModule } from '../../core/admin_core/admin_core.module';
import { LoggerModule } from '../../core/utils/logger/logger.module';
import { UsersModule } from '../../core/db/users/users.module';
import { UserProfilesModule } from '../../core/db/user_profiles/user_profiles.module';
import { ZulipAccountsModule } from '../../core/db/zulip_accounts/zulip_accounts.module';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { AdminDatabaseController } from './admin_database.controller';
@@ -55,8 +54,7 @@ function isDatabaseConfigured(): boolean {
UsersModule,
// 根据数据库配置选择UserProfiles模块模式
isDatabaseConfigured() ? UserProfilesModule.forDatabase() : UserProfilesModule.forMemory(),
// 根据数据库配置选择ZulipAccounts模块模式
isDatabaseConfigured() ? ZulipAccountsModule.forDatabase() : ZulipAccountsModule.forMemory(),
// 注意:ZulipAccountsModule 是全局模块,已在 AppModule 中导入,无需重复导入
// 注册AdminOperationLog实体
TypeOrmModule.forFeature([AdminOperationLog])
],

View File

@@ -36,7 +36,6 @@ import { LoginService } from './login.service';
import { RegisterService } from './register.service';
import { LoginCoreModule } from '../../core/login_core/login_core.module';
import { ZulipCoreModule } from '../../core/zulip_core/zulip_core.module';
import { ZulipAccountsModule } from '../../core/db/zulip_accounts/zulip_accounts.module';
import { UsersModule } from '../../core/db/users/users.module';
@Module({
@@ -44,7 +43,7 @@ import { UsersModule } from '../../core/db/users/users.module';
// 导入核心层模块
LoginCoreModule,
ZulipCoreModule,
ZulipAccountsModule.forRoot(),
// 注意:ZulipAccountsModule 是全局模块,已在 AppModule 中导入,无需重复导入
UsersModule,
],
providers: [

View File

@@ -714,13 +714,7 @@ export class LoginService {
apiKeyResult.apiKey!
);
// 4. 更新内存关联
await this.zulipAccountService.linkGameAccount(
user.id.toString(),
zulipAccount.zulipUserId,
zulipAccount.zulipEmail,
apiKeyResult.apiKey!
);
// 注意不在登录时建立内存关联Zulip客户端将在WebSocket连接时创建
const duration = Date.now() - startTime;

View File

@@ -7,12 +7,13 @@
* - 测试Zulip账号集成
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的变量apiKeySecurityService (修改者: moyin)
* - 2026-01-12: 代码分离 - 从login.service.spec.ts中分离注册相关测试
*
* @author moyin
* @version 1.0.0
* @version 1.0.1
* @since 2026-01-12
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Test, TestingModule } from '@nestjs/testing';
@@ -25,7 +26,6 @@ describe('RegisterService', () => {
let service: RegisterService;
let loginCoreService: jest.Mocked<LoginCoreService>;
let zulipAccountService: jest.Mocked<ZulipAccountService>;
let apiKeySecurityService: jest.Mocked<ApiKeySecurityService>;
const mockUser = {
id: BigInt(1),
@@ -96,7 +96,6 @@ describe('RegisterService', () => {
service = module.get<RegisterService>(RegisterService);
loginCoreService = module.get(LoginCoreService);
zulipAccountService = module.get(ZulipAccountService);
apiKeySecurityService = module.get(ApiKeySecurityService);
// 设置默认的mock返回值
const mockTokenPair = {

View File

@@ -14,16 +14,17 @@
* - 处理注册相关的邮箱验证和Zulip集成
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的导入TokenPair增强userId非空验证 (修改者: moyin)
* - 2026-01-12: 代码分离 - 从login.service.ts中分离注册相关业务逻辑
*
* @author moyin
* @version 1.0.0
* @version 1.0.1
* @since 2026-01-12
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Injectable, Logger, Inject } from '@nestjs/common';
import { LoginCoreService, RegisterRequest, TokenPair } from '../../core/login_core/login_core.service';
import { LoginCoreService, RegisterRequest } from '../../core/login_core/login_core.service';
import { Users } from '../../core/db/users/users.entity';
import { ZulipAccountService } from '../../core/zulip_core/services/zulip_account.service';
import { ApiKeySecurityService } from '../../core/zulip_core/services/api_key_security.service';
@@ -487,6 +488,11 @@ export class RegisterService {
throw new Error(createResult.error || 'Zulip账号创建/绑定失败');
}
// 验证必须获取到 userId数据库字段 NOT NULL
if (createResult.userId === undefined || createResult.userId === null) {
throw new Error('Zulip账号创建成功但未能获取用户ID无法建立关联');
}
// 3. 处理API Key
let finalApiKey = createResult.apiKey;
@@ -520,22 +526,14 @@ export class RegisterService {
// 5. 在数据库中创建关联记录
await this.zulipAccountsService.create({
gameUserId: gameUser.id.toString(),
zulipUserId: createResult.userId!,
zulipUserId: createResult.userId, // 已在上面验证不为 undefined
zulipEmail: createResult.email!,
zulipFullName: gameUser.nickname,
zulipApiKeyEncrypted: finalApiKey ? 'stored_in_redis' : '',
status: 'active',
});
// 6. 建立游戏账号与Zulip账号的内存关联用于当前会话
if (finalApiKey) {
await this.zulipAccountService.linkGameAccount(
gameUser.id.toString(),
createResult.userId!,
createResult.email!,
finalApiKey
);
}
// 注意不在注册时建立内存关联Zulip客户端将在WebSocket连接时创建
const duration = Date.now() - startTime;

View File

@@ -36,7 +36,6 @@ import { ZulipEventProcessorService } from './services/zulip_event_processor.ser
import { ZulipAccountsBusinessService } from './services/zulip_accounts_business.service';
// 依赖模块
import { ZulipCoreModule } from '../../core/zulip_core/zulip_core.module';
import { ZulipAccountsModule } from '../../core/db/zulip_accounts/zulip_accounts.module';
import { RedisModule } from '../../core/redis/redis.module';
import { LoggerModule } from '../../core/utils/logger/logger.module';
import { LoginCoreModule } from '../../core/login_core/login_core.module';
@@ -50,8 +49,7 @@ import { ChatModule } from '../chat/chat.module';
CacheModule.register(),
// Zulip核心服务模块
ZulipCoreModule,
// Zulip账号关联模块
ZulipAccountsModule.forRoot(),
// 注意:ZulipAccountsModule 是全局模块,已在 AppModule 中导入,无需重复导入
// Redis模块
RedisModule,
// 日志模块

View File

@@ -14,13 +14,14 @@
* - 搜索优化:搜索异常的特殊处理机制
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 为保护方法补充@example示例 (修改者: moyin)
* - 2026-01-07: 代码规范优化 - 完善注释规范,添加完整的文件头和方法注释
* - 2026-01-07: 功能新增 - 添加敏感信息脱敏处理和结构化日志记录
*
* @author moyin
* @version 1.0.1
* @version 1.0.2
* @since 2025-01-07
* @lastModified 2026-01-07
* @lastModified 2026-01-15
*/
import { Logger, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
@@ -33,6 +34,12 @@ export abstract class BaseUsersService {
*
* @param error 原始错误对象
* @returns 格式化后的错误信息字符串
*
* @example
* ```typescript
* const errorMsg = this.formatError(new Error('数据库连接失败'));
* // 返回: "数据库连接失败"
* ```
*/
protected formatError(error: unknown): string {
if (error instanceof Error) {
@@ -48,6 +55,15 @@ export abstract class BaseUsersService {
* @param operation 操作名称
* @param context 上下文信息
* @throws 处理后的标准异常
*
* @example
* ```typescript
* try {
* // 业务操作
* } catch (error) {
* this.handleServiceError(error, '创建用户', { username: 'test' });
* }
* ```
*/
protected handleServiceError(error: unknown, operation: string, context?: Record<string, any>): never {
const errorMessage = this.formatError(error);
@@ -78,6 +94,15 @@ export abstract class BaseUsersService {
* @param operation 操作名称
* @param context 上下文信息
* @returns 空数组
*
* @example
* ```typescript
* try {
* // 搜索操作
* } catch (error) {
* return this.handleSearchError(error, '搜索用户', { keyword: 'test' });
* }
* ```
*/
protected handleSearchError(error: unknown, operation: string, context?: Record<string, any>): any[] {
const errorMessage = this.formatError(error);
@@ -98,6 +123,11 @@ export abstract class BaseUsersService {
* @param operation 操作名称
* @param context 上下文信息
* @param duration 操作耗时
*
* @example
* ```typescript
* this.logSuccess('创建用户', { userId: '123', username: 'test' }, 50);
* ```
*/
protected logSuccess(operation: string, context?: Record<string, any>, duration?: number): void {
this.logger.log(`${operation}成功`, {
@@ -113,6 +143,11 @@ export abstract class BaseUsersService {
*
* @param operation 操作名称
* @param context 上下文信息
*
* @example
* ```typescript
* this.logStart('创建用户', { username: 'test' });
* ```
*/
protected logStart(operation: string, context?: Record<string, any>): void {
this.logger.log(`开始${operation}`, {
@@ -127,6 +162,16 @@ export abstract class BaseUsersService {
*
* @param data 原始数据
* @returns 脱敏后的数据
*
* @example
* ```typescript
* const sanitized = this.sanitizeLogData({
* email: 'test@example.com',
* phone: '13800138000',
* password_hash: 'secret'
* });
* // 返回: { email: 'te***@example.com', phone: '138****00', password_hash: '[REDACTED]' }
* ```
*/
protected sanitizeLogData(data: Record<string, any>): Record<string, any> {
const sanitized = { ...data };

View File

@@ -6,13 +6,19 @@
* - 避免魔法数字,提高代码可维护性
* - 集中管理配置参数
*
* 职责分离:
* - 常量定义:用户角色、字段限制、查询限制等常量值
* - 错误消息:统一的错误消息定义和管理
* - 工具类:性能监控和验证工具的封装
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 补充职责分离描述 (修改者: moyin)
* - 2026-01-09: 代码质量优化 - 提取魔法数字为常量定义 (修改者: moyin)
*
* @author moyin
* @version 1.0.0
* @version 1.0.1
* @since 2026-01-09
* @lastModified 2026-01-09
* @lastModified 2026-01-15
*/
import { ValidationError } from 'class-validator';

View File

@@ -17,22 +17,21 @@
* - 并发控制:使用悲观锁防止竞态条件
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的导入FindOptionsWhere (修改者: moyin)
* - 2026-01-12: 性能优化 - 集成AppLoggerService优化查询和批量操作
* - 2026-01-07: 代码规范优化 - 使用统一的常量文件,提高代码质量
* - 2026-01-07: 代码规范优化 - 完善文件头注释和方法三级注释
* - 2026-01-07: 功能新增 - 添加事务支持防止并发竞态条件
* - 2026-01-07: 性能优化 - 优化查询语句添加LIMIT限制
* - 2026-01-07: 功能新增 - 新增existsByGameUserId方法
*
* @author angjustinl
* @version 1.2.0
* @version 1.2.1
* @since 2025-01-05
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Injectable, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere, DataSource, SelectQueryBuilder } from 'typeorm';
import { Repository, DataSource, SelectQueryBuilder } from 'typeorm';
import { ZulipAccounts } from './zulip_accounts.entity';
import { AppLoggerService } from '../../utils/logger/logger.service';
import {

View File

@@ -16,28 +16,19 @@
* 注意:业务逻辑已转移到 src/core/zulip_core/services/zulip_accounts_business.service.ts
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的导入NotFoundException (修改者: moyin)
* - 2026-01-12: 代码规范优化 - 修复依赖注入配置,添加@Inject装饰器确保正确的参数注入 (修改者: moyin)
* - 2026-01-12: 功能修改 - 优化create方法错误处理正确转换重复创建错误为ConflictException (修改者: moyin)
* - 2026-01-12: 架构优化 - 移除业务逻辑转移到zulip_core业务服务 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 清理重复导入,统一使用@Inject装饰器 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 完成所有性能监控代码优化统一使用createPerformanceMonitor方法 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 修复所有遗漏的BigInt转换使用列表响应构建工具方法 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 完善所有BigInt转换和数组映射的优化彻底消除重复代码 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 使用基类工具方法优化性能监控和BigInt转换减少重复代码 (修改者: moyin)
* - 2026-01-12: 性能优化 - 集成AppLoggerService和缓存机制添加性能监控
* - 2026-01-07: 代码规范优化 - 使用统一的常量文件,提高代码质量
* - 2026-01-07: 代码规范优化 - 修复导入路径,完善方法三级注释
* - 2026-01-07: 代码规范优化 - 完善文件头注释和方法三级注释
* - 2026-01-07: 功能修改 - 优化异常处理逻辑规范Repository和Service职责边界
* - 2026-01-07: 性能优化 - 移除Service层的重复唯一性检查依赖Repository事务
*
* @author angjustinl
* @version 2.1.0
* @version 2.1.1
* @since 2025-01-07
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Injectable, Inject, ConflictException, NotFoundException } from '@nestjs/common';
import { Injectable, Inject, ConflictException } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { BaseZulipAccountsService } from './base_zulip_accounts.service';
import { ZulipAccountsRepository } from './zulip_accounts.repository';
@@ -126,8 +117,10 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
errorMessage.includes('unique constraint')) {
const conflictError = new ConflictException(`游戏用户 ${createDto.gameUserId} 已存在Zulip账号关联`);
monitor.error(conflictError);
throw conflictError;
} else {
monitor.error(error);
throw error;
}
}
}
@@ -322,6 +315,7 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -363,6 +357,7 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -404,6 +399,7 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -424,6 +420,7 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -444,6 +441,7 @@ export class ZulipAccountsService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}

View File

@@ -15,26 +15,19 @@
* 注意:业务逻辑已转移到 src/core/zulip_core/services/zulip_accounts_business.service.ts
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的导入ConflictException和NotFoundException (修改者: moyin)
* - 2026-01-12: 架构优化 - 移除业务逻辑转移到zulip_core业务服务 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 修复导入语句添加缺失的AppLoggerService导入 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 修复logger初始化问题统一使用AppLoggerService (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 完成所有性能监控代码优化统一使用createPerformanceMonitor方法 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 修复所有遗漏的BigInt转换使用列表响应构建工具方法 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 完善所有BigInt转换和数组映射的优化彻底消除重复代码 (修改者: moyin)
* - 2026-01-12: 代码质量优化 - 使用基类工具方法优化性能监控和BigInt转换减少重复代码 (修改者: moyin)
* - 2026-01-07: 代码规范优化 - 使用统一的常量文件,提高代码质量
* - 2026-01-07: 代码规范优化 - 修复导入路径,完善方法三级注释
* - 2026-01-07: 代码规范优化 - 完善文件头注释和方法三级注释
* - 2026-01-07: 功能完善 - 优化异常处理逻辑和日志记录
* - 2025-01-07: 架构优化 - 统一Service层的职责边界和接口设计
*
* @author angjustinl
* @version 2.0.0
* @version 2.0.1
* @since 2025-01-07
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Injectable, Inject, ConflictException, NotFoundException } from '@nestjs/common';
import { Injectable, Inject } from '@nestjs/common';
import { BaseZulipAccountsService } from './base_zulip_accounts.service';
import { ZulipAccountsMemoryRepository } from './zulip_accounts_memory.repository';
import { ZulipAccounts } from './zulip_accounts.entity';
@@ -101,6 +94,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -146,6 +140,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -259,6 +254,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -281,6 +277,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -301,6 +298,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}
@@ -321,6 +319,7 @@ export class ZulipAccountsMemoryService extends BaseZulipAccountsService {
} catch (error) {
monitor.error(error);
throw error;
}
}

View File

@@ -1,8 +1,42 @@
/**
* 登录核心模块测试套件
*
* 功能描述:
* - 测试LoginCoreModule的模块配置和依赖注入
* - 验证服务提供者的正确注册和实例化
* - 确保JWT配置的正确加载和访问
* - 测试模块导出和依赖关系
*
* 测试覆盖范围:
* - 模块定义:模块实例化和配置验证
* - 服务提供者LoginCoreService和ConfigService的注入
* - JWT配置JWT密钥和过期时间的配置访问
* - 模块依赖:依赖模块的正确导入
* - 模块导出:服务的正确导出和可用性
*
* 测试策略:
* - 单元测试:独立测试模块配置
* - Mock测试模拟所有外部依赖服务
* - 配置测试:验证配置项的正确读取
*
* 依赖模块:
* - Jest: 测试框架和Mock功能
* - NestJS Testing: 提供测试模块和依赖注入
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 清理未使用的导入(UsersService) (修改者: moyin)
* - 2026-01-15: 代码规范优化 - 添加文件头注释 (修改者: moyin)
*
* @author moyin
* @version 1.0.2
* @since 2025-12-17
* @lastModified 2026-01-15
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { LoginCoreService } from './login_core.service';
import { UsersService } from '../db/users/users.service';
import { EmailService } from '../utils/email/email.service';
import { VerificationService } from '../utils/verification/verification.service';

View File

@@ -12,16 +12,16 @@
* - 为business层提供可复用的服务
*
* 最近修改:
* - 2026-01-15: 代码规范优化 - 提取手机号查找为私有方法消除重复代码 (修改者: moyin)
* - 2026-01-12: 代码规范优化 - 提取魔法数字为常量,拆分过长方法,消除代码重复 (修改者: moyin)
* - 2026-01-12: 代码规范优化 - 添加LoginCoreService类注释完善类职责和方法说明 (修改者: moyin)
* - 2026-01-12: 代码规范优化 - 处理TODO项移除短信发送相关的TODO注释 (修改者: moyin)
* - 2025-01-07: 代码规范优化 - 清理未使用的导入(EmailSendResult, crypto)
* - 2025-01-07: 代码规范优化 - 修复常量命名(saltRounds -> SALT_ROUNDS)
*
* @author moyin
* @version 1.1.0
* @version 1.1.1
* @since 2025-12-17
* @lastModified 2026-01-12
* @lastModified 2026-01-15
*/
import { Injectable, UnauthorizedException, ConflictException, NotFoundException, BadRequestException, ForbiddenException, Inject } from '@nestjs/common';
@@ -243,8 +243,7 @@ export class LoginCoreService {
// 如果邮箱未找到,尝试手机号查找(简单验证)
if (!user && this.isPhoneNumber(identifier)) {
const users = await this.usersService.findAll();
user = users.find((u: Users) => u.phone === identifier) || null;
user = await this.findUserByPhone(identifier);
}
// 用户不存在
@@ -340,9 +339,8 @@ export class LoginCoreService {
// 检查手机号是否已存在
if (phone) {
const users = await this.usersService.findAll();
const existingPhone = users.find((u: Users) => u.phone === phone);
if (existingPhone) {
const phoneExists = await this.isPhoneExists(phone);
if (phoneExists) {
throw new ConflictException('手机号已存在');
}
}
@@ -555,8 +553,7 @@ export class LoginCoreService {
throw new BadRequestException('邮箱未验证,无法重置密码');
}
} else if (this.isPhoneNumber(identifier)) {
const users = await this.usersService.findAll();
user = users.find((u: Users) => u.phone === identifier) || null;
user = await this.findUserByPhone(identifier);
}
if (!user) {
@@ -616,8 +613,7 @@ export class LoginCoreService {
if (this.isEmail(identifier)) {
user = await this.usersService.findByEmail(identifier);
} else if (this.isPhoneNumber(identifier)) {
const users = await this.usersService.findAll();
user = users.find((u: Users) => u.phone === identifier) || null;
user = await this.findUserByPhone(identifier);
}
if (!user) {
@@ -847,6 +843,30 @@ export class LoginCoreService {
return phoneRegex.test(str.replace(/\s/g, ''));
}
/**
* 通过手机号查找用户
*
* @param phone 手机号
* @returns 用户信息或null
* @private
*/
private async findUserByPhone(phone: string): Promise<Users | null> {
const users = await this.usersService.findAll();
return users.find((u: Users) => u.phone === phone) || null;
}
/**
* 检查手机号是否已存在
*
* @param phone 手机号
* @returns 是否存在
* @private
*/
private async isPhoneExists(phone: string): Promise<boolean> {
const user = await this.findUserByPhone(phone);
return user !== null;
}
/**
* 验证码登录
*
@@ -888,8 +908,7 @@ export class LoginCoreService {
}
} else if (this.isPhoneNumber(identifier)) {
// 手机号登录
const users = await this.usersService.findAll();
user = users.find((u: Users) => u.phone === identifier) || null;
user = await this.findUserByPhone(identifier);
verificationType = VerificationCodeType.SMS_VERIFICATION;
} else {
throw new BadRequestException('请提供有效的邮箱或手机号');
@@ -964,8 +983,7 @@ export class LoginCoreService {
throw new BadRequestException('邮箱未验证,无法使用验证码登录');
}
} else if (this.isPhoneNumber(identifier)) {
const users = await this.usersService.findAll();
user = users.find((u: Users) => u.phone === identifier) || null;
user = await this.findUserByPhone(identifier);
verificationType = VerificationCodeType.SMS_VERIFICATION;
} else {
throw new BadRequestException('请提供有效的邮箱或手机号');

View File

@@ -87,6 +87,25 @@ Zulip Core 是应用的核心聊天集成模块提供完整的Zulip聊天服
### getAllAccountLinks()
获取所有活跃的账号关联信息,用于系统管理和监控。
## 用户注册功能
### registerUser()
在Zulip服务器上注册新用户账号包含邮箱验证、密码生成和API Key获取。
## 用户管理功能
### checkUserExists()
检查指定邮箱的用户是否存在于Zulip服务器。
### getUserInfo()
根据邮箱获取用户的详细信息包含用户ID、状态和权限信息。
### validateUserCredentials()
验证用户的API Key是否有效用于登录认证。
### getAllUsers()
从Zulip服务器获取所有用户列表用于管理和统计。
## 配置管理功能
### getAllMapConfigs()
@@ -101,6 +120,67 @@ Zulip Core 是应用的核心聊天集成模块提供完整的Zulip聊天服
### validateConfig()
验证配置文件的完整性和正确性,确保系统正常运行。
## 动态配置管理功能
### testZulipConnection()
测试与Zulip服务器的连接状态用于健康检查和故障诊断。
### getZulipStreams()
从Zulip服务器获取所有Stream列表用于配置同步。
### getZulipTopics()
获取指定Stream的所有Topic列表用于交互对象配置。
### getConfig()
获取当前缓存的配置信息,优先使用内存缓存。
### syncConfig()
手动触发配置同步从Zulip服务器获取最新配置并更新本地文件。
### getConfigStatus()
获取配置管理器的状态信息,包含同步时间、配置来源等。
### getBackupFiles()
获取配置备份文件列表,用于配置恢复和版本管理。
### restoreFromBackup()
从指定的备份文件恢复配置,支持配置回滚。
## 错误处理功能
### handleZulipError()
处理Zulip API错误分析错误类型并决定处理策略。
### enableDegradedMode()
启用服务降级模式在Zulip服务不可用时提供基础功能。
### enableNormalMode()
恢复正常服务模式,从降级状态切换回正常状态。
### retryWithBackoff()
使用指数退避算法进行重试,避免对服务造成过大压力。
### handleConnectionError()
处理网络连接错误,决定是否重试和启用降级模式。
### executeWithTimeout()
带超时控制的操作执行,超时时自动取消并返回错误。
### scheduleReconnect()
调度自动重连,在连接断开时使用指数退避策略重连。
### cancelReconnect()
取消正在进行的重连尝试,清理重连状态。
### checkServiceHealth()
检查服务健康状态,返回综合健康报告。
### getServiceStatus()
获取当前服务状态(正常/降级/不可用)。
### getLoadStatus()
获取系统负载状态,用于连接限流决策。
## 安全管理功能
### encryptApiKey()
@@ -129,6 +209,32 @@ Zulip Core 是应用的核心聊天集成模块提供完整的Zulip聊天服
### getPerformanceMetrics()
获取系统性能指标,包含响应时间和吞吐量统计。
## 监控日志功能
### logConnection()
记录WebSocket连接事件日志包含连接、断开和错误事件。
### logApiCall()
记录Zulip API调用日志包含响应时间和结果状态。
### logMessageForward()
记录消息转发日志,包含成功率和延迟统计。
### confirmOperation()
记录操作确认信息,用于审计和追踪。
### sendAlert()
发送系统告警通知,支持不同严重级别。
### getStats()
获取监控统计信息包含连接、API调用和消息统计。
### getRecentAlerts()
获取最近的告警列表,用于问题排查。
### resetStats()
重置监控统计数据,用于周期性统计。
## 使用的项目内部依赖
### RedisModule (来自 ../redis/redis.module)
@@ -354,7 +460,7 @@ const newKey = await apiKeySecurityService.rotateApiKey('user123');
```
## 版本信息
- **版本**: 1.1.1
- **版本**: 1.2.0
- **作者**: moyin
- **创建时间**: 2025-12-25
- **最后修改**: 2026-01-07
- **最后修改**: 2026-01-15

View File

@@ -69,6 +69,7 @@ export interface CreateZulipAccountResult {
export interface GenerateApiKeyResult {
success: boolean;
apiKey?: string;
userId?: number; // 添加 userId从 profile 中获取
error?: string;
}
@@ -301,40 +302,44 @@ export class ZulipAccountService {
// 尝试获取已有用户的信息
const userInfo = await this.getExistingUserInfo(request.email);
if (userInfo.success) {
// 尝试为已有用户生成API Key
// 尝试为已有用户生成API Key(同时可以获取 userId
const apiKeyResult = await this.generateApiKeyForUser(request.email, request.password || '');
// 优先使用 userInfo 中的 userId其次使用 apiKeyResult 中的 userId
const finalUserId = userInfo.userId ?? apiKeyResult.userId;
if (finalUserId === undefined) {
this.logger.error('用户已存在但无法获取用户ID绑定失败', {
operation: 'handleExistingUser',
email: request.email,
getUserInfoError: userInfo.error,
apiKeyError: apiKeyResult.error,
});
return {
success: false,
error: `用户已存在但无法获取用户ID`,
errorCode: 'USER_ID_NOT_FOUND',
isExistingUser: true,
};
}
this.logger.log('Zulip账号绑定成功已存在', {
operation: 'handleExistingUser',
email: request.email,
userId: userInfo.userId,
userId: finalUserId,
hasApiKey: apiKeyResult.success,
apiKeyError: apiKeyResult.success ? undefined : apiKeyResult.error,
});
return {
success: true,
userId: userInfo.userId,
userId: finalUserId,
email: request.email,
apiKey: apiKeyResult.success ? apiKeyResult.apiKey : undefined,
isExistingUser: true,
};
} else {
this.logger.warn('用户已存在但无法获取详细信息,仍返回绑定成功', {
operation: 'handleExistingUser',
email: request.email,
getUserInfoError: userInfo.error,
});
return {
success: true,
userId: undefined,
email: request.email,
apiKey: undefined,
isExistingUser: true,
};
}
}
/**
@@ -421,39 +426,43 @@ export class ZulipAccountService {
// 尝试获取已有用户信息
const userInfo = await this.getExistingUserInfo(request.email);
if (userInfo.success) {
// 尝试为已有用户生成API Key
// 尝试为已有用户生成API Key(同时可以获取 userId
const apiKeyResult = await this.generateApiKeyForUser(request.email, password);
// 优先使用 userInfo 中的 userId其次使用 apiKeyResult 中的 userId
const finalUserId = userInfo.userId ?? apiKeyResult.userId;
if (finalUserId === undefined) {
this.logger.error('用户已存在但无法获取用户ID绑定失败', {
operation: 'handleCreateUserError',
email: request.email,
getUserInfoError: userInfo.error,
apiKeyError: apiKeyResult.error,
});
return {
success: false,
error: `用户已存在但无法获取用户ID`,
errorCode: 'USER_ID_NOT_FOUND',
isExistingUser: true,
};
}
this.logger.log('Zulip账号绑定成功API创建时发现已存在', {
operation: 'handleCreateUserError',
email: request.email,
userId: userInfo.userId,
userId: finalUserId,
hasApiKey: apiKeyResult.success,
});
return {
success: true,
userId: userInfo.userId,
userId: finalUserId,
email: request.email,
apiKey: apiKeyResult.success ? apiKeyResult.apiKey : undefined,
isExistingUser: true,
};
} else {
this.logger.warn('用户已存在但无法获取详细信息', {
operation: 'handleCreateUserError',
email: request.email,
getUserInfoError: userInfo.error,
});
return {
success: true,
userId: undefined,
email: request.email,
apiKey: undefined,
isExistingUser: true,
};
}
}
// 其他类型的错误
@@ -515,12 +524,14 @@ export class ZulipAccountService {
this.logger.log('API Key生成成功', {
operation: 'generateApiKeyForUser',
email,
userId: profile.user_id,
timestamp: new Date().toISOString(),
});
return {
success: true,
apiKey: apiKey,
userId: profile.user_id, // 返回从 profile 获取的 user_id
};
} catch (error) {

View File

@@ -169,6 +169,9 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha
case 'position':
await this.handlePosition(ws, message);
break;
case 'change_map':
await this.handleChangeMap(ws, message);
break;
default:
this.logger.warn(`未知消息类型: ${messageType}`);
this.sendError(ws, `未知消息类型: ${messageType}`);
@@ -254,7 +257,7 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha
* 处理聊天消息
*
* @param ws WebSocket 连接实例
* @param message 聊天消息(包含 content, scope
* @param message 聊天消息(包含 content, scope, mapId
*/
private async handleChat(ws: ExtendedWebSocket, message: any) {
if (!ws.authenticated) {
@@ -271,7 +274,8 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha
const result = await this.chatService.sendChatMessage({
socketId: ws.id,
content: message.content,
scope: message.scope || 'local'
scope: message.scope || 'local',
mapId: message.mapId || ws.currentMap, // 支持指定目标地图
});
if (result.success) {
@@ -335,6 +339,82 @@ export class ChatWebSocketGateway implements OnModuleInit, OnModuleDestroy, ICha
}
}
/**
* 处理切换地图
*
* @param ws WebSocket 连接实例
* @param message 切换地图消息(包含 mapId
*/
private async handleChangeMap(ws: ExtendedWebSocket, message: any) {
if (!ws.authenticated) {
this.sendError(ws, '请先登录');
return;
}
if (!message.mapId) {
this.sendError(ws, '地图ID不能为空');
return;
}
try {
const oldMapId = ws.currentMap;
const newMapId = message.mapId;
// 如果地图相同,直接返回成功
if (oldMapId === newMapId) {
this.sendMessage(ws, {
t: 'map_changed',
mapId: newMapId,
message: '已在当前地图'
});
return;
}
// 更新房间
this.leaveMapRoom(ws.id, oldMapId);
this.joinMapRoom(ws.id, newMapId);
ws.currentMap = newMapId;
// 更新会话中的地图信息(使用默认位置)
await this.chatService.updatePlayerPosition({
socketId: ws.id,
x: message.x || 400,
y: message.y || 300,
mapId: newMapId
});
// 通知客户端切换成功
this.sendMessage(ws, {
t: 'map_changed',
mapId: newMapId,
oldMapId: oldMapId,
message: '地图切换成功'
});
// 向旧地图广播玩家离开
this.broadcastToMap(oldMapId, {
t: 'player_left',
userId: ws.userId,
username: ws.username,
mapId: oldMapId
});
// 向新地图广播玩家加入
this.broadcastToMap(newMapId, {
t: 'player_joined',
userId: ws.userId,
username: ws.username,
mapId: newMapId
}, ws.id);
this.logger.log(`用户切换地图: ${ws.username} (${oldMapId} -> ${newMapId})`);
} catch (error) {
this.logger.error('切换地图处理失败', error);
this.sendError(ws, '切换地图处理失败');
}
}
/**
* 处理连接关闭
*