Merge pull request 'docs/ai-reading-guide-20260115' (#50) from docs/ai-reading-guide-20260115 into main
Reviewed-on: #50
This commit was merged in pull request #50.
This commit is contained in:
@@ -177,6 +177,227 @@ private validateUserData(userData: CreateUserDto | UpdateUserDto): void {
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 异常处理完整性检查(关键规范)
|
||||
|
||||
### 问题定义
|
||||
**异常吞没(Exception Swallowing)** 是指在 catch 块中捕获异常后,只记录日志但不重新抛出,导致:
|
||||
- 调用方无法感知错误
|
||||
- 方法返回 undefined 而非声明的类型
|
||||
- 数据不一致或静默失败
|
||||
- 难以调试和定位问题
|
||||
|
||||
### 检查规则
|
||||
|
||||
#### 规则1:catch 块必须有明确的异常处理策略
|
||||
```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可以返回错误响应
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 规则2:Service 层方法必须传播异常
|
||||
```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; // 传播给调用方处理
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 规则3:Repository 层必须传播数据库异常
|
||||
```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);
|
||||
// 没有 throw,TypeScript 不会报错但运行时返回 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连接管理完整性
|
||||
- 双模式服务行为一致性
|
||||
- 属性测试实现质量
|
||||
|
||||
@@ -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提交
|
||||
|
||||
### 质量保证
|
||||
- 提交前必须验证代码能正常运行
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,7 +526,7 @@ 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' : '',
|
||||
@@ -531,7 +537,7 @@ export class RegisterService {
|
||||
if (finalApiKey) {
|
||||
await this.zulipAccountService.linkGameAccount(
|
||||
gameUser.id.toString(),
|
||||
createResult.userId!,
|
||||
createResult.userId, // 已在上面验证不为 undefined
|
||||
createResult.email!,
|
||||
finalApiKey
|
||||
);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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('请提供有效的邮箱或手机号');
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
const apiKeyResult = await this.generateApiKeyForUser(request.email, request.password || '');
|
||||
|
||||
this.logger.log('Zulip账号绑定成功(已存在)', {
|
||||
operation: 'handleExistingUser',
|
||||
email: request.email,
|
||||
userId: userInfo.userId,
|
||||
hasApiKey: apiKeyResult.success,
|
||||
apiKeyError: apiKeyResult.success ? undefined : apiKeyResult.error,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userId: userInfo.userId,
|
||||
email: request.email,
|
||||
apiKey: apiKeyResult.success ? apiKeyResult.apiKey : undefined,
|
||||
isExistingUser: true,
|
||||
};
|
||||
} else {
|
||||
this.logger.warn('用户已存在但无法获取详细信息,仍返回绑定成功', {
|
||||
|
||||
// 尝试为已有用户生成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: true,
|
||||
userId: undefined,
|
||||
email: request.email,
|
||||
apiKey: undefined,
|
||||
success: false,
|
||||
error: `用户已存在但无法获取用户ID`,
|
||||
errorCode: 'USER_ID_NOT_FOUND',
|
||||
isExistingUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.log('Zulip账号绑定成功(已存在)', {
|
||||
operation: 'handleExistingUser',
|
||||
email: request.email,
|
||||
userId: finalUserId,
|
||||
hasApiKey: apiKeyResult.success,
|
||||
apiKeyError: apiKeyResult.success ? undefined : apiKeyResult.error,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userId: finalUserId,
|
||||
email: request.email,
|
||||
apiKey: apiKeyResult.success ? apiKeyResult.apiKey : undefined,
|
||||
isExistingUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,39 +426,43 @@ export class ZulipAccountService {
|
||||
|
||||
// 尝试获取已有用户信息
|
||||
const userInfo = await this.getExistingUserInfo(request.email);
|
||||
if (userInfo.success) {
|
||||
// 尝试为已有用户生成API Key
|
||||
const apiKeyResult = await this.generateApiKeyForUser(request.email, password);
|
||||
|
||||
this.logger.log('Zulip账号绑定成功(API创建时发现已存在)', {
|
||||
operation: 'handleCreateUserError',
|
||||
email: request.email,
|
||||
userId: userInfo.userId,
|
||||
hasApiKey: apiKeyResult.success,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userId: userInfo.userId,
|
||||
email: request.email,
|
||||
apiKey: apiKeyResult.success ? apiKeyResult.apiKey : undefined,
|
||||
isExistingUser: true,
|
||||
};
|
||||
} else {
|
||||
this.logger.warn('用户已存在但无法获取详细信息', {
|
||||
|
||||
// 尝试为已有用户生成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: true,
|
||||
userId: undefined,
|
||||
email: request.email,
|
||||
apiKey: undefined,
|
||||
success: false,
|
||||
error: `用户已存在但无法获取用户ID`,
|
||||
errorCode: 'USER_ID_NOT_FOUND',
|
||||
isExistingUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
this.logger.log('Zulip账号绑定成功(API创建时发现已存在)', {
|
||||
operation: 'handleCreateUserError',
|
||||
email: request.email,
|
||||
userId: finalUserId,
|
||||
hasApiKey: apiKeyResult.success,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userId: finalUserId,
|
||||
email: request.email,
|
||||
apiKey: apiKeyResult.success ? apiKeyResult.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) {
|
||||
|
||||
Reference in New Issue
Block a user