From dff0ac83257889fd6f4792ff8984a281d8b7dc12 Mon Sep 17 00:00:00 2001 From: moyin <244344649@qq.com> Date: Sat, 13 Dec 2025 16:18:33 +0800 Subject: [PATCH] =?UTF-8?q?docs=EF=BC=9A=E9=87=8D=E6=9E=84=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=BC=80=E5=8F=91=E6=8C=87=E5=8D=97=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将日志记录规范重构为日志系统使用指南 - 保留实用的使用方法和最佳实践 - 添加依赖注入和请求上下文绑定示例 - 简化内容,专注于实际使用方法 --- docs/backend_development_guide.md | 856 ++++++++++++++++++++++++++++++ 1 file changed, 856 insertions(+) create mode 100644 docs/backend_development_guide.md diff --git a/docs/backend_development_guide.md b/docs/backend_development_guide.md new file mode 100644 index 0000000..f6aae34 --- /dev/null +++ b/docs/backend_development_guide.md @@ -0,0 +1,856 @@ +# 后端开发规范指南 + +## 一、文档概述 + +### 1.1 文档目的 + +本文档定义了 Datawhale Town 后端开发的编码规范、注释标准、业务逻辑设计原则和日志记录要求,确保代码质量、可维护性和系统稳定性。 + +### 1.2 适用范围 + +- 所有后端开发人员 +- 代码审查人员 +- 系统维护人员 + +--- + +## 二、注释规范 + +### 2.1 模块注释 + +每个功能模块文件必须包含模块级注释,说明模块用途、主要功能和依赖关系。 + +**格式要求:** + +```typescript +/** + * 玩家管理模块 + * + * 功能描述: + * - 处理玩家注册、登录、信息更新等核心功能 + * - 管理玩家角色皮肤和个人资料 + * - 提供玩家数据的 CRUD 操作 + * + * 依赖模块: + * - AuthService: 身份验证服务 + * - DatabaseService: 数据库操作服务 + * - LoggerService: 日志记录服务 + * + * @author 开发者姓名 + * @version 1.0.0 + * @since 2024-12-13 + */ +``` + +### 2.2 类注释 + +每个类必须包含类级注释,说明类的职责、主要方法和使用场景。 + +**格式要求:** + +```typescript +/** + * 玩家服务类 + * + * 职责: + * - 处理玩家相关的业务逻辑 + * - 管理玩家状态和数据 + * - 提供玩家操作的统一接口 + * + * 主要方法: + * - createPlayer(): 创建新玩家 + * - updatePlayerInfo(): 更新玩家信息 + * - getPlayerById(): 根据ID获取玩家信息 + * + * 使用场景: + * - 玩家注册登录流程 + * - 个人陈列室数据管理 + * - 广场玩家状态同步 + */ +@Injectable() +export class PlayerService { + // 类实现 +} +``` + +### 2.3 方法注释 + +每个方法必须包含详细的方法注释,说明功能、参数、返回值、异常和业务逻辑。 + +**格式要求:** + +```typescript +/** + * 创建新玩家 + * + * 功能描述: + * 根据邮箱创建新的玩家账户,包含基础信息初始化和默认配置设置 + * + * 业务逻辑: + * 1. 验证邮箱格式和白名单 + * 2. 检查邮箱是否已存在 + * 3. 生成唯一玩家ID + * 4. 初始化默认角色皮肤和个人信息 + * 5. 创建对应的个人陈列室 + * 6. 记录创建日志 + * + * @param email 玩家邮箱地址,必须符合邮箱格式且在白名单中 + * @param nickname 玩家昵称,长度3-20字符,不能包含特殊字符 + * @param avatarSkin 角色皮肤ID,必须是1-8之间的有效值 + * @returns Promise 创建成功的玩家对象 + * + * @throws BadRequestException 当邮箱格式错误或不在白名单中 + * @throws ConflictException 当邮箱已存在时 + * @throws InternalServerErrorException 当数据库操作失败时 + * + * @example + * ```typescript + * const player = await playerService.createPlayer( + * 'user@datawhale.club', + * '数据鲸鱼', + * '1' + * ); + * ``` + */ +async createPlayer( + email: string, + nickname: string, + avatarSkin: string +): Promise { + // 方法实现 +} +``` + +### 2.4 复杂业务逻辑注释 + +对于复杂的业务逻辑,必须添加行内注释说明每个步骤的目的和处理逻辑。 + +**示例:** + +```typescript +async joinRoom(roomId: string, playerId: string): Promise { + // 1. 参数验证 - 确保房间ID和玩家ID格式正确 + if (!roomId || !playerId) { + this.logger.warn(`房间加入失败:参数无效`, { roomId, playerId }); + throw new BadRequestException('房间ID和玩家ID不能为空'); + } + + // 2. 获取房间信息 - 检查房间是否存在 + const room = await this.roomRepository.findById(roomId); + if (!room) { + this.logger.warn(`房间加入失败:房间不存在`, { roomId, playerId }); + throw new NotFoundException('房间不存在'); + } + + // 3. 检查房间状态 - 只有等待中的房间才能加入 + if (room.status !== RoomStatus.WAITING) { + this.logger.warn(`房间加入失败:房间状态不允许加入`, { + roomId, + playerId, + currentStatus: room.status + }); + throw new BadRequestException('游戏已开始,无法加入房间'); + } + + // 4. 检查房间容量 - 防止超过最大人数限制 + if (room.players.length >= room.maxPlayers) { + this.logger.warn(`房间加入失败:房间已满`, { + roomId, + playerId, + currentPlayers: room.players.length, + maxPlayers: room.maxPlayers + }); + throw new BadRequestException('房间已满'); + } + + // 5. 检查玩家是否已在房间中 - 防止重复加入 + if (room.players.includes(playerId)) { + this.logger.info(`玩家已在房间中,跳过加入操作`, { roomId, playerId }); + return room; + } + + // 6. 执行加入操作 - 更新房间玩家列表 + try { + room.players.push(playerId); + const updatedRoom = await this.roomRepository.save(room); + + // 7. 记录成功日志 + this.logger.info(`玩家成功加入房间`, { + roomId, + playerId, + currentPlayers: updatedRoom.players.length, + maxPlayers: updatedRoom.maxPlayers + }); + + return updatedRoom; + } catch (error) { + // 8. 异常处理 - 记录错误并抛出 + this.logger.error(`房间加入操作数据库错误`, { + roomId, + playerId, + error: error.message, + stack: error.stack + }); + throw new InternalServerErrorException('房间加入失败,请稍后重试'); + } +} +``` + +--- + +## 三、业务逻辑设计原则 + +### 3.1 全面性原则 + +每个业务方法必须考虑所有可能的情况,包括正常流程、异常情况和边界条件。 + +**必须考虑的情况:** + +| 类别 | 具体情况 | 处理方式 | +|------|---------|---------| +| **输入验证** | 参数为空、格式错误、超出范围 | 参数校验 + 异常抛出 | +| **权限检查** | 未登录、权限不足、令牌过期 | 身份验证 + 权限验证 | +| **资源状态** | 资源不存在、状态不正确、已被占用 | 状态检查 + 业务规则验证 | +| **并发控制** | 同时操作、数据竞争、锁冲突 | 事务处理 + 乐观锁/悲观锁 | +| **系统异常** | 数据库连接失败、网络超时、内存不足 | 异常捕获 + 降级处理 | +| **业务规则** | 违反业务约束、超出限制、状态冲突 | 业务规则验证 + 友好提示 | + +### 3.2 防御性编程 + +采用防御性编程思想,对所有外部输入和依赖进行验证和保护。 + +**实现要求:** + +```typescript +/** + * 更新玩家信息 - 防御性编程示例 + */ +async updatePlayerInfo( + playerId: string, + updateData: UpdatePlayerDto +): Promise { + // 1. 输入参数防御性检查 + if (!playerId) { + this.logger.warn('更新玩家信息失败:玩家ID为空'); + throw new BadRequestException('玩家ID不能为空'); + } + + if (!updateData || Object.keys(updateData).length === 0) { + this.logger.warn('更新玩家信息失败:更新数据为空', { playerId }); + throw new BadRequestException('更新数据不能为空'); + } + + // 2. 数据格式验证 + if (updateData.nickname) { + if (updateData.nickname.length < 3 || updateData.nickname.length > 20) { + this.logger.warn('更新玩家信息失败:昵称长度不符合要求', { + playerId, + nicknameLength: updateData.nickname.length + }); + throw new BadRequestException('昵称长度必须在3-20字符之间'); + } + } + + if (updateData.avatarSkin) { + const validSkins = ['1', '2', '3', '4', '5', '6', '7', '8']; + if (!validSkins.includes(updateData.avatarSkin)) { + this.logger.warn('更新玩家信息失败:角色皮肤ID无效', { + playerId, + avatarSkin: updateData.avatarSkin + }); + throw new BadRequestException('角色皮肤ID必须在1-8之间'); + } + } + + // 3. 玩家存在性检查 + const existingPlayer = await this.playerRepository.findById(playerId); + if (!existingPlayer) { + this.logger.warn('更新玩家信息失败:玩家不存在', { playerId }); + throw new NotFoundException('玩家不存在'); + } + + // 4. 昵称唯一性检查(如果更新昵称) + if (updateData.nickname && updateData.nickname !== existingPlayer.nickname) { + const nicknameExists = await this.playerRepository.findByNickname(updateData.nickname); + if (nicknameExists) { + this.logger.warn('更新玩家信息失败:昵称已存在', { + playerId, + nickname: updateData.nickname + }); + throw new ConflictException('昵称已被使用'); + } + } + + // 5. 执行更新操作(使用事务保证数据一致性) + try { + const updatedPlayer = await this.playerRepository.update(playerId, updateData); + + this.logger.info('玩家信息更新成功', { + playerId, + updatedFields: Object.keys(updateData), + timestamp: new Date().toISOString() + }); + + return updatedPlayer; + } catch (error) { + this.logger.error('更新玩家信息数据库操作失败', { + playerId, + updateData, + error: error.message, + stack: error.stack + }); + throw new InternalServerErrorException('更新失败,请稍后重试'); + } +} +``` + +### 3.3 异常处理策略 + +建立统一的异常处理策略,确保所有异常都能被正确捕获和处理。 + +**异常分类和处理:** + +| 异常类型 | HTTP状态码 | 处理策略 | 日志级别 | +|---------|-----------|---------|---------| +| **BadRequestException** | 400 | 参数验证失败,返回具体错误信息 | WARN | +| **UnauthorizedException** | 401 | 身份验证失败,要求重新登录 | WARN | +| **ForbiddenException** | 403 | 权限不足,拒绝访问 | WARN | +| **NotFoundException** | 404 | 资源不存在,返回友好提示 | WARN | +| **ConflictException** | 409 | 资源冲突,如重复创建 | WARN | +| **InternalServerErrorException** | 500 | 系统内部错误,记录详细日志 | ERROR | + +--- + +## 四、日志系统使用指南 + +### 4.1 日志服务简介 + +项目使用统一的 `AppLoggerService` 提供日志记录功能,集成了 Pino 高性能日志库,支持自动敏感信息过滤和请求上下文绑定。 + +### 4.2 在服务中使用日志 + +**依赖注入:** + +```typescript +import { Injectable } from '@nestjs/common'; +import { AppLoggerService } from '../core/utils/logger/logger.service'; + +@Injectable() +export class UserService { + constructor( + private readonly logger: AppLoggerService + ) {} +} +``` + +### 4.3 日志级别和使用场景 + +| 级别 | 使用场景 | 示例 | +|------|---------|------| +| **ERROR** | 系统错误、异常情况、数据库连接失败 | 数据库连接超时、第三方服务调用失败 | +| **WARN** | 业务警告、参数错误、权限不足 | 用户输入无效参数、尝试访问不存在的资源 | +| **INFO** | 重要业务操作、状态变更、关键流程 | 用户登录成功、房间创建、玩家加入广场 | +| **DEBUG** | 调试信息、详细执行流程 | 方法调用参数、中间计算结果 | +| **FATAL** | 致命错误、系统不可用 | 数据库完全不可用、关键服务宕机 | +| **TRACE** | 极细粒度调试信息 | 循环内的变量状态、算法执行步骤 | + +### 4.4 标准日志格式 + +**推荐的日志上下文格式:** + +```typescript +// 成功操作日志 +this.logger.info('操作描述', { + operation: '操作类型', + userId: '用户ID', + resourceId: '资源ID', + params: '关键参数', + result: '操作结果', + duration: '执行时间(ms)', + timestamp: new Date().toISOString() +}); + +// 警告日志 +this.logger.warn('警告描述', { + operation: '操作类型', + userId: '用户ID', + reason: '警告原因', + params: '相关参数', + timestamp: new Date().toISOString() +}); + +// 错误日志 +this.logger.error('错误描述', { + operation: '操作类型', + userId: '用户ID', + error: error.message, + params: '相关参数', + timestamp: new Date().toISOString() +}, error.stack); +``` + +### 4.5 请求上下文绑定 + +**在 Controller 中使用:** + +```typescript +@Controller('users') +export class UserController { + constructor(private readonly logger: AppLoggerService) {} + + @Get(':id') + async getUser(@Param('id') id: string, @Req() req: Request) { + // 绑定请求上下文 + const requestLogger = this.logger.bindRequest(req, 'UserController'); + + requestLogger.info('开始获取用户信息', { userId: id }); + + try { + const user = await this.userService.findById(id); + requestLogger.info('用户信息获取成功', { userId: id }); + return user; + } catch (error) { + requestLogger.error('用户信息获取失败', error.stack, { + userId: id, + reason: error.message + }); + throw error; + } + } +} +``` + +### 4.6 业务方法日志记录最佳实践 + +**完整的业务方法日志记录示例:** + +```typescript +async createPlayer(email: string, nickname: string): Promise { + const startTime = Date.now(); + + this.logger.info('开始创建玩家', { + operation: 'createPlayer', + email, + nickname, + timestamp: new Date().toISOString() + }); + + try { + // 1. 参数验证 + if (!email || !nickname) { + this.logger.warn('创建玩家失败:参数无效', { + operation: 'createPlayer', + email, + nickname, + reason: 'invalid_parameters' + }); + throw new BadRequestException('邮箱和昵称不能为空'); + } + + // 2. 邮箱格式验证 + if (!this.isValidEmail(email)) { + this.logger.warn('创建玩家失败:邮箱格式无效', { + operation: 'createPlayer', + email, + nickname + }); + throw new BadRequestException('邮箱格式不正确'); + } + + // 3. 检查邮箱是否已存在 + const existingPlayer = await this.playerRepository.findByEmail(email); + if (existingPlayer) { + this.logger.warn('创建玩家失败:邮箱已存在', { + operation: 'createPlayer', + email, + nickname, + existingPlayerId: existingPlayer.id + }); + throw new ConflictException('邮箱已被使用'); + } + + // 4. 创建玩家 + const player = await this.playerRepository.create({ + email, + nickname, + avatarSkin: '1', // 默认皮肤 + createTime: new Date() + }); + + const duration = Date.now() - startTime; + + this.logger.info('玩家创建成功', { + operation: 'createPlayer', + playerId: player.id, + email, + nickname, + duration, + timestamp: new Date().toISOString() + }); + + return player; + + } catch (error) { + const duration = Date.now() - startTime; + + if (error instanceof BadRequestException || + error instanceof ConflictException) { + // 业务异常,重新抛出 + throw error; + } + + // 系统异常,记录详细日志 + this.logger.error('创建玩家系统异常', { + operation: 'createPlayer', + email, + nickname, + error: error.message, + duration, + timestamp: new Date().toISOString() + }, error.stack); + + throw new InternalServerErrorException('创建玩家失败,请稍后重试'); + } +} +``` + +### 4.7 必须记录日志的操作 + +| 操作类型 | 日志级别 | 记录内容 | +|---------|---------|---------| +| **用户认证** | INFO/WARN | 登录成功/失败、令牌生成/验证 | +| **数据变更** | INFO | 创建、更新、删除操作 | +| **权限检查** | WARN | 权限验证失败、非法访问尝试 | +| **系统异常** | ERROR | 异常堆栈、错误上下文、影响范围 | +| **性能监控** | INFO | 慢查询、高并发操作、资源使用 | +| **安全事件** | WARN/ERROR | 恶意请求、频繁操作、异常行为 | + +### 4.8 敏感信息保护 + +日志系统会自动过滤以下敏感字段: +- `password` - 密码 +- `token` - 令牌 +- `secret` - 密钥 +- `authorization` - 授权信息 +- `cardNo` - 卡号 + +**注意:** 包含这些关键词的字段会被自动替换为 `[REDACTED]` + +--- + +## 五、代码审查检查清单 + +### 5.1 注释检查 + +- [ ] 模块文件包含完整的模块级注释 +- [ ] 每个类都有详细的类级注释 +- [ ] 每个公共方法都有完整的方法注释 +- [ ] 复杂业务逻辑有行内注释说明 +- [ ] 注释内容准确,与代码实现一致 + +### 5.2 业务逻辑检查 + +- [ ] 考虑了所有可能的输入情况 +- [ ] 包含完整的参数验证 +- [ ] 处理了所有可能的异常情况 +- [ ] 实现了适当的权限检查 +- [ ] 考虑了并发和竞态条件 + +### 5.3 日志记录检查 + +- [ ] 关键业务操作都有日志记录 +- [ ] 日志级别使用正确 +- [ ] 日志格式符合规范 +- [ ] 包含足够的上下文信息 +- [ ] 敏感信息已脱敏处理 + +### 5.4 异常处理检查 + +- [ ] 所有异常都被正确捕获 +- [ ] 异常类型选择合适 +- [ ] 异常信息对用户友好 +- [ ] 系统异常有详细的错误日志 +- [ ] 不会泄露敏感的系统信息 + +--- + +## 六、最佳实践示例 + +### 6.1 完整的服务类示例 + +```typescript +/** + * 广场管理服务 + * + * 功能描述: + * - 管理中央广场的玩家状态和位置同步 + * - 处理玩家进入和离开广场的逻辑 + * - 维护广场在线玩家列表(最多50人) + * + * 依赖模块: + * - PlayerService: 玩家信息服务 + * - WebSocketGateway: WebSocket通信网关 + * - RedisService: 缓存服务 + * - LoggerService: 日志记录服务 + * + * @author 开发团队 + * @version 1.0.0 + * @since 2024-12-13 + */ +@Injectable() +export class PlazaService { + private readonly logger = new Logger(PlazaService.name); + private readonly MAX_PLAYERS = 50; + + constructor( + private readonly playerService: PlayerService, + private readonly redisService: RedisService, + private readonly webSocketGateway: WebSocketGateway + ) {} + + /** + * 玩家进入广场 + * + * 功能描述: + * 处理玩家进入中央广场的逻辑,包括人数限制检查、位置分配和状态同步 + * + * 业务逻辑: + * 1. 验证玩家身份和权限 + * 2. 检查广场当前人数是否超限 + * 3. 为玩家分配初始位置 + * 4. 更新Redis中的在线玩家列表 + * 5. 向其他玩家广播新玩家进入消息 + * 6. 向新玩家发送当前广场状态 + * + * @param playerId 玩家ID,必须是有效的已注册玩家 + * @param socketId WebSocket连接ID,用于消息推送 + * @returns Promise 玩家在广场的信息 + * + * @throws UnauthorizedException 当玩家身份验证失败时 + * @throws BadRequestException 当广场人数已满时 + * @throws InternalServerErrorException 当系统操作失败时 + */ + async enterPlaza(playerId: string, socketId: string): Promise { + const startTime = Date.now(); + + this.logger.info('玩家尝试进入广场', { + operation: 'enterPlaza', + playerId, + socketId, + timestamp: new Date().toISOString() + }); + + try { + // 1. 验证玩家身份 + const player = await this.playerService.getPlayerById(playerId); + if (!player) { + this.logger.warn('进入广场失败:玩家不存在', { + operation: 'enterPlaza', + playerId, + socketId + }); + throw new UnauthorizedException('玩家身份验证失败'); + } + + // 2. 检查广场人数限制 + const currentPlayers = await this.redisService.scard('plaza:online_players'); + if (currentPlayers >= this.MAX_PLAYERS) { + this.logger.warn('进入广场失败:人数已满', { + operation: 'enterPlaza', + playerId, + currentPlayers, + maxPlayers: this.MAX_PLAYERS + }); + throw new BadRequestException('广场人数已满,请稍后再试'); + } + + // 3. 检查玩家是否已在广场中 + const isAlreadyInPlaza = await this.redisService.sismember('plaza:online_players', playerId); + if (isAlreadyInPlaza) { + this.logger.info('玩家已在广场中,更新连接信息', { + operation: 'enterPlaza', + playerId, + socketId + }); + + // 更新Socket连接映射 + await this.redisService.hset('plaza:player_sockets', playerId, socketId); + + // 获取当前位置信息 + const existingInfo = await this.redisService.hget('plaza:player_positions', playerId); + return JSON.parse(existingInfo); + } + + // 4. 为玩家分配初始位置(广场中心附近随机位置) + const initialPosition = this.generateInitialPosition(); + + const playerInfo: PlazaPlayerInfo = { + playerId: player.id, + nickname: player.nickname, + avatarSkin: player.avatarSkin, + position: initialPosition, + lastUpdate: new Date(), + socketId + }; + + // 5. 更新Redis中的玩家状态 + await Promise.all([ + this.redisService.sadd('plaza:online_players', playerId), + this.redisService.hset('plaza:player_positions', playerId, JSON.stringify(playerInfo)), + this.redisService.hset('plaza:player_sockets', playerId, socketId), + this.redisService.expire('plaza:player_positions', 3600), // 1小时过期 + this.redisService.expire('plaza:player_sockets', 3600) + ]); + + // 6. 向其他玩家广播新玩家进入消息 + this.webSocketGateway.broadcastToPlaza('player_entered', { + playerId: player.id, + nickname: player.nickname, + avatarSkin: player.avatarSkin, + position: initialPosition + }, socketId); // 排除新进入的玩家 + + // 7. 向新玩家发送当前广场状态 + const allPlayers = await this.getAllPlazaPlayers(); + this.webSocketGateway.sendToPlayer(socketId, 'plaza_state', { + players: allPlayers.filter(p => p.playerId !== playerId), + totalPlayers: allPlayers.length + }); + + const duration = Date.now() - startTime; + + this.logger.info('玩家成功进入广场', { + operation: 'enterPlaza', + playerId, + socketId, + position: initialPosition, + totalPlayers: currentPlayers + 1, + duration, + timestamp: new Date().toISOString() + }); + + return playerInfo; + + } catch (error) { + const duration = Date.now() - startTime; + + if (error instanceof UnauthorizedException || + error instanceof BadRequestException) { + throw error; + } + + this.logger.error('玩家进入广场系统异常', { + operation: 'enterPlaza', + playerId, + socketId, + error: error.message, + stack: error.stack, + duration, + timestamp: new Date().toISOString() + }); + + throw new InternalServerErrorException('进入广场失败,请稍后重试'); + } + } + + /** + * 生成初始位置 + * + * 功能描述: + * 在广场中心附近生成随机的初始位置,避免玩家重叠 + * + * @returns Position 包含x、y坐标的位置对象 + * @private + */ + private generateInitialPosition(): Position { + // 广场中心坐标 (400, 300),在半径100像素范围内随机分配 + const centerX = 400; + const centerY = 300; + const radius = 100; + + const angle = Math.random() * 2 * Math.PI; + const distance = Math.random() * radius; + + const x = Math.round(centerX + distance * Math.cos(angle)); + const y = Math.round(centerY + distance * Math.sin(angle)); + + return { x, y }; + } + + /** + * 获取所有广场玩家信息 + * + * @returns Promise 广场中所有玩家的信息列表 + * @private + */ + private async getAllPlazaPlayers(): Promise { + try { + const playerIds = await this.redisService.smembers('plaza:online_players'); + const playerInfos = await Promise.all( + playerIds.map(async (playerId) => { + const info = await this.redisService.hget('plaza:player_positions', playerId); + return info ? JSON.parse(info) : null; + }) + ); + + return playerInfos.filter(info => info !== null); + } catch (error) { + this.logger.error('获取广场玩家列表失败', { + operation: 'getAllPlazaPlayers', + error: error.message + }); + return []; + } + } +} +``` + +--- + +## 七、工具和配置 + +### 7.1 推荐的开发工具 + +| 工具 | 用途 | 配置说明 | +|------|------|---------| +| **ESLint** | 代码规范检查 | 配置注释规范检查规则 | +| **Prettier** | 代码格式化 | 统一代码格式 | +| **TSDoc** | 文档生成 | 从注释生成API文档 | +| **SonarQube** | 代码质量分析 | 检查代码覆盖率和复杂度 | + +### 7.2 日志配置示例 + +```typescript +// logger.config.ts +export const loggerConfig = { + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + transports: [ + new winston.transports.Console(), + new winston.transports.File({ + filename: 'logs/error.log', + level: 'error' + }), + new winston.transports.File({ + filename: 'logs/combined.log' + }) + ] +}; +``` + +--- + +## 八、总结 + +本规范文档定义了后端开发的核心要求: + +1. **完整的注释体系**:模块、类、方法三级注释,确保代码可读性 +2. **全面的业务逻辑**:考虑所有可能情况,实现防御性编程 +3. **规范的日志记录**:关键操作必须记录,便于问题排查和系统监控 +4. **统一的异常处理**:分类处理不同类型异常,提供友好的用户体验 + +遵循本规范将显著提高代码质量、系统稳定性和团队协作效率。所有开发人员必须严格按照本规范进行开发,代码审查时将重点检查这些方面的实现。 \ No newline at end of file