From e54d5e3939a1530da3c19c4f188ddf6c48967563 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 13:38:36 +0800 Subject: [PATCH 1/7] =?UTF-8?q?style(users):=20=E4=BC=98=E5=8C=96Core?= =?UTF-8?q?=E5=B1=82users=E6=A8=A1=E5=9D=97=E4=BB=A3=E7=A0=81=E8=A7=84?= =?UTF-8?q?=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/core/db/users/ - base_users.service.ts: 为保护方法补充@example示例 - users.constants.ts: 补充职责分离描述 检查人员: moyin 检查日期: 2026-01-15 --- src/core/db/users/base_users.service.ts | 49 ++++++++++++++++++++++++- src/core/db/users/users.constants.ts | 10 ++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/core/db/users/base_users.service.ts b/src/core/db/users/base_users.service.ts index 3069406..b29b926 100644 --- a/src/core/db/users/base_users.service.ts +++ b/src/core/db/users/base_users.service.ts @@ -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): 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): 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, 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): 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): Record { const sanitized = { ...data }; diff --git a/src/core/db/users/users.constants.ts b/src/core/db/users/users.constants.ts index 3e59307..65f263f 100644 --- a/src/core/db/users/users.constants.ts +++ b/src/core/db/users/users.constants.ts @@ -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'; -- 2.25.1 From 223ba2abb80984b4f24a5d980feeeab8a6bca46b Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 13:46:24 +0800 Subject: [PATCH 2/7] =?UTF-8?q?style(zulip=5Faccounts):=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=A7=84=E8=8C=83=E4=BC=98=E5=8C=96=20-=20=E6=B8=85?= =?UTF-8?q?=E7=90=86=E6=9C=AA=E4=BD=BF=E7=94=A8=E5=AF=BC=E5=85=A5=E5=92=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围:src/core/db/zulip_accounts/ 涉及文件: - zulip_accounts.repository.ts - zulip_accounts.service.ts - zulip_accounts_memory.service.ts 主要改进: - 清理未使用的导入(FindOptionsWhere, NotFoundException, ConflictException) - 修复异常处理:确保catch块中正确抛出异常,避免异常吞没 - 更新文件头部修改记录和版本号 --- .../zulip_accounts.repository.ts | 9 ++++--- .../zulip_accounts/zulip_accounts.service.ts | 24 +++++++++---------- .../zulip_accounts_memory.service.ts | 21 ++++++++-------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/core/db/zulip_accounts/zulip_accounts.repository.ts b/src/core/db/zulip_accounts/zulip_accounts.repository.ts index 0690652..20b364b 100644 --- a/src/core/db/zulip_accounts/zulip_accounts.repository.ts +++ b/src/core/db/zulip_accounts/zulip_accounts.repository.ts @@ -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 { diff --git a/src/core/db/zulip_accounts/zulip_accounts.service.ts b/src/core/db/zulip_accounts/zulip_accounts.service.ts index 372d31f..3ed20d2 100644 --- a/src/core/db/zulip_accounts/zulip_accounts.service.ts +++ b/src/core/db/zulip_accounts/zulip_accounts.service.ts @@ -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; } } diff --git a/src/core/db/zulip_accounts/zulip_accounts_memory.service.ts b/src/core/db/zulip_accounts/zulip_accounts_memory.service.ts index c470ecb..b0322c0 100644 --- a/src/core/db/zulip_accounts/zulip_accounts_memory.service.ts +++ b/src/core/db/zulip_accounts/zulip_accounts_memory.service.ts @@ -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; } } -- 2.25.1 From 519394645a2f143e3054949e3a34cee881b8229f Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 13:53:56 +0800 Subject: [PATCH 3/7] =?UTF-8?q?docs(zulip=5Fcore):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=96=87=E6=A1=A3=E5=92=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=9C=8D=E5=8A=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围:src/core/zulip_core/ - 补充README.md缺失的服务文档(用户注册、用户管理、动态配置、错误处理、监控日志) - 优化zulip_account.service.ts中已存在用户的处理逻辑 - 增强userId获取的可靠性,优先使用userInfo,其次使用apiKeyResult - 版本更新:1.1.1 -> 1.2.0 --- src/core/zulip_core/README.md | 110 +++++++++++++++++- .../services/zulip_account.service.ts | 109 +++++++++-------- 2 files changed, 168 insertions(+), 51 deletions(-) diff --git a/src/core/zulip_core/README.md b/src/core/zulip_core/README.md index 1a29f81..8dc2766 100644 --- a/src/core/zulip_core/README.md +++ b/src/core/zulip_core/README.md @@ -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 \ No newline at end of file +- **最后修改**: 2026-01-15 \ No newline at end of file diff --git a/src/core/zulip_core/services/zulip_account.service.ts b/src/core/zulip_core/services/zulip_account.service.ts index f8fff45..fe795b8 100644 --- a/src/core/zulip_core/services/zulip_account.service.ts +++ b/src/core/zulip_core/services/zulip_account.service.ts @@ -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) { -- 2.25.1 From 4f18f0fec6bdba90f38439f3cf9a96455d54a12e Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 14:13:48 +0800 Subject: [PATCH 4/7] =?UTF-8?q?refactor(login=5Fcore):=20=E6=B6=88?= =?UTF-8?q?=E9=99=A4=E4=BB=A3=E7=A0=81=E9=87=8D=E5=A4=8D=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=9F=A5=E6=89=BE=E4=B8=BA?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围:src/core/login_core/ - 提取手机号查找逻辑为 findUserByPhone() 私有方法 - 添加 isPhoneExists() 私有方法检查手机号是否存在 - 消除 login、validateUserUniqueness、sendPasswordResetCode 等方法中的重复代码 - 测试文件添加文件头注释,清理未使用的 UsersService 导入 - 更新版本号 1.1.0 -> 1.1.1 --- src/core/login_core/login_core.module.spec.ts | 36 ++++++++++++- src/core/login_core/login_core.service.ts | 50 +++++++++++++------ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/core/login_core/login_core.module.spec.ts b/src/core/login_core/login_core.module.spec.ts index 2d7fb37..23d2c57 100644 --- a/src/core/login_core/login_core.module.spec.ts +++ b/src/core/login_core/login_core.module.spec.ts @@ -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'; diff --git a/src/core/login_core/login_core.service.ts b/src/core/login_core/login_core.service.ts index ab9ff21..1f8c10b 100644 --- a/src/core/login_core/login_core.service.ts +++ b/src/core/login_core/login_core.service.ts @@ -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 { + 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 { + 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('请提供有效的邮箱或手机号'); -- 2.25.1 From 9f4d291619cc30156d1b965ca86ce5b729fb274b Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 14:17:38 +0800 Subject: [PATCH 5/7] =?UTF-8?q?style(auth):=20=E4=BC=98=E5=8C=96auth?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 范围: src/business/auth/ - register.service.ts: 清理未使用的导入TokenPair,增强userId非空验证 - register.service.spec.ts: 清理未使用的变量apiKeySecurityService --- src/business/auth/register.service.spec.ts | 7 +++---- src/business/auth/register.service.ts | 16 +++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/business/auth/register.service.spec.ts b/src/business/auth/register.service.spec.ts index 9124237..8010d9b 100644 --- a/src/business/auth/register.service.spec.ts +++ b/src/business/auth/register.service.spec.ts @@ -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; let zulipAccountService: jest.Mocked; - let apiKeySecurityService: jest.Mocked; const mockUser = { id: BigInt(1), @@ -96,7 +96,6 @@ describe('RegisterService', () => { service = module.get(RegisterService); loginCoreService = module.get(LoginCoreService); zulipAccountService = module.get(ZulipAccountService); - apiKeySecurityService = module.get(ApiKeySecurityService); // 设置默认的mock返回值 const mockTokenPair = { diff --git a/src/business/auth/register.service.ts b/src/business/auth/register.service.ts index 78fbf43..1e88f27 100644 --- a/src/business/auth/register.service.ts +++ b/src/business/auth/register.service.ts @@ -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 ); -- 2.25.1 From a8de2564b6c90fa8f6c6e1b1da36ae56263bd007 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 14:20:55 +0800 Subject: [PATCH 6/7] =?UTF-8?q?docs=EF=BC=9A=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E5=AE=8C=E6=95=B4=E6=80=A7=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增异常吞没问题定义和检查规则 - 添加Service/Repository层异常传播规范 - 补充常见错误模式和修复示例 - 更新检查清单和执行步骤顺序 --- docs/ai-reading/step3-code-quality.md | 232 +++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 2 deletions(-) diff --git a/docs/ai-reading/step3-code-quality.md b/docs/ai-reading/step3-code-quality.md index 5336eb6..b06d718 100644 --- a/docs/ai-reading/step3-code-quality.md +++ b/docs/ai-reading/step3-code-quality.md @@ -177,6 +177,227 @@ private validateUserData(userData: CreateUserDto | UpdateUserDto): void { } ``` +## 🚨 异常处理完整性检查(关键规范) + +### 问题定义 +**异常吞没(Exception Swallowing)** 是指在 catch 块中捕获异常后,只记录日志但不重新抛出,导致: +- 调用方无法感知错误 +- 方法返回 undefined 而非声明的类型 +- 数据不一致或静默失败 +- 难以调试和定位问题 + +### 检查规则 + +#### 规则1:catch 块必须有明确的异常处理策略 +```typescript +// ❌ 严重错误:catch 块吞没异常 +async create(createDto: CreateDto): Promise { + try { + const result = await this.repository.create(createDto); + return this.toResponseDto(result); + } catch (error) { + this.logger.error('创建失败', error); + // 错误:没有 throw,方法返回 undefined + // 但声明返回 Promise + } +} + +// ❌ 错误:只记录日志不处理 +async findById(id: string): Promise { + try { + return await this.repository.findById(id); + } catch (error) { + monitor.error(error); + // 错误:异常被吞没,调用方无法感知 + } +} + +// ✅ 正确:重新抛出异常 +async create(createDto: CreateDto): Promise { + 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 { + 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> { + 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 { + 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 { + 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 { + 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 { + 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 但可能返回 undefined +async findById(id: string): Promise { + try { + return await this.repo.findById(id); + } catch (error) { + this.logger.error(error); + // 没有 throw,TypeScript 不会报错但运行时返回 undefined + } +} + +// ✅ 正确 +async findById(id: string): Promise { + 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连接管理完整性 - 双模式服务行为一致性 - 属性测试实现质量 -- 2.25.1 From cde20c6fd70848bb7d0d481d38fcfbbba3614e9c Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Thu, 15 Jan 2026 14:21:14 +0800 Subject: [PATCH 7/7] =?UTF-8?q?docs=EF=BC=9A=E8=A1=A5=E5=85=85=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E6=96=87=E6=A1=A3=E4=B8=8D=E7=BA=B3=E5=85=A5Git?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E7=9A=84=E8=A7=84=E8=8C=83=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加合并文档排除原因说明 - 补充操作规范和.gitignore配置建议 - 更新提交原则中的合并文档排除要求 --- docs/ai-reading/step7-code-commit.md | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/ai-reading/step7-code-commit.md b/docs/ai-reading/step7-code-commit.md index 2ba11e6..7bd0e75 100644 --- a/docs/ai-reading/step7-code-commit.md +++ b/docs/ai-reading/step7-code-commit.md @@ -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提交 ### 质量保证 - 提交前必须验证代码能正常运行 -- 2.25.1