/** * 用户服务类 * * 功能描述: * - 提供用户数据的增删改查技术实现 * - 处理数据持久化和存储操作 * - 数据格式验证和约束检查 * - 支持完整的数据生命周期管理 * * 职责分离: * - 数据持久化:通过TypeORM操作MySQL数据库 * - 数据验证:数据格式和约束完整性检查 * - 异常处理:统一的错误处理和日志记录 * - 性能监控:操作耗时统计和性能优化 * * 最近修改: * - 2026-01-07: 代码规范优化 - 完善注释规范,添加完整的文件头和方法注释 * - 2026-01-07: 功能优化 - 添加完整的日志记录系统和详细的技术实现注释 * - 2026-01-07: 性能优化 - 优化异常处理和性能监控机制 * * @author moyin * @version 1.0.1 * @since 2025-12-17 * @lastModified 2026-01-07 */ import { Injectable, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, FindOptionsWhere } from 'typeorm'; import { Users } from './users.entity'; import { CreateUserDto } from './users.dto'; import { UserStatus } from './user_status.enum'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; import { BaseUsersService } from './base_users.service'; @Injectable() export class UsersService extends BaseUsersService { constructor( @InjectRepository(Users) private readonly usersRepository: Repository, ) { super(); // 调用基类构造函数 } /** * 创建新用户 * * 技术实现: * 1. 验证输入数据的格式和完整性 * 2. 使用class-validator进行DTO数据验证 * 3. 创建用户实体并设置默认值 * 4. 保存用户数据到数据库 * 5. 记录操作日志和性能指标 * 6. 返回创建成功的用户实体 * * @param createUserDto 创建用户的数据传输对象,包含用户基本信息 * @returns 创建成功的用户实体,包含自动生成的ID和时间戳 * @throws BadRequestException 当数据验证失败或输入格式错误时 * * @example * ```typescript * const newUser = await usersService.create({ * username: 'testuser', * email: 'test@example.com', * nickname: '测试用户', * password_hash: 'hashed_password' * }); * console.log(`用户创建成功,ID: ${newUser.id}`); * ``` */ async create(createUserDto: CreateUserDto): Promise { const startTime = Date.now(); this.logger.log('开始创建用户', { operation: 'create', username: createUserDto.username, email: createUserDto.email, timestamp: new Date().toISOString() }); try { // 验证DTO const dto = plainToClass(CreateUserDto, createUserDto); const validationErrors = await validate(dto); if (validationErrors.length > 0) { const errorMessages = validationErrors.map(error => Object.values(error.constraints || {}).join(', ') ).join('; '); this.logger.warn('用户创建失败:数据验证失败', { operation: 'create', username: createUserDto.username, email: createUserDto.email, validationErrors: errorMessages }); throw new BadRequestException(`数据验证失败: ${errorMessages}`); } // 创建用户实体 const user = new Users(); user.username = createUserDto.username; user.email = createUserDto.email || null; user.phone = createUserDto.phone || null; user.password_hash = createUserDto.password_hash || null; user.nickname = createUserDto.nickname; user.github_id = createUserDto.github_id || null; user.avatar_url = createUserDto.avatar_url || null; user.role = createUserDto.role || 1; user.email_verified = createUserDto.email_verified || false; user.status = createUserDto.status || UserStatus.ACTIVE; // 保存到数据库 const savedUser = await this.usersRepository.save(user); const duration = Date.now() - startTime; this.logger.log('用户创建成功', { operation: 'create', userId: savedUser.id.toString(), username: savedUser.username, email: savedUser.email, duration, timestamp: new Date().toISOString() }); return savedUser; } catch (error) { const duration = Date.now() - startTime; if (error instanceof BadRequestException) { throw error; } this.logger.error('用户创建系统异常', { operation: 'create', username: createUserDto.username, email: createUserDto.email, error: error instanceof Error ? error.message : String(error), duration, timestamp: new Date().toISOString() }, error instanceof Error ? error.stack : undefined); throw new BadRequestException('用户创建失败,请稍后重试'); } } /** * 创建新用户(带重复检查) * * 技术实现: * 1. 检查用户名、邮箱、手机号、GitHub ID的唯一性约束 * 2. 如果所有检查都通过,调用create方法创建用户 * 3. 记录操作日志和性能指标 * * @param createUserDto 创建用户的数据传输对象 * @returns 创建的用户实体 * @throws ConflictException 当用户名、邮箱、手机号或GitHub ID已存在时 * @throws BadRequestException 当数据验证失败时 * * @example * ```typescript * const newUser = await usersService.createWithDuplicateCheck({ * username: 'testuser', * email: 'test@example.com', * nickname: '测试用户' * }); * ``` */ async createWithDuplicateCheck(createUserDto: CreateUserDto): Promise { const startTime = Date.now(); this.logger.log('开始创建用户(带重复检查)', { operation: 'createWithDuplicateCheck', username: createUserDto.username, email: createUserDto.email, phone: createUserDto.phone, github_id: createUserDto.github_id, timestamp: new Date().toISOString() }); try { // 执行所有唯一性检查 await this.validateUniqueness(createUserDto); // 调用普通的创建方法 const user = await this.create(createUserDto); const duration = Date.now() - startTime; this.logger.log('用户创建成功(带重复检查)', { operation: 'createWithDuplicateCheck', userId: user.id.toString(), username: user.username, duration, timestamp: new Date().toISOString() }); return user; } catch (error) { const duration = Date.now() - startTime; if (error instanceof ConflictException || error instanceof BadRequestException) { throw error; } this.logger.error('用户创建系统异常(带重复检查)', { operation: 'createWithDuplicateCheck', username: createUserDto.username, email: createUserDto.email, error: error instanceof Error ? error.message : String(error), duration, timestamp: new Date().toISOString() }, error instanceof Error ? error.stack : undefined); throw new BadRequestException('用户创建失败,请稍后重试'); } } /** * 验证用户数据的唯一性 * * @param createUserDto 用户数据 * @throws ConflictException 当发现重复数据时 */ private async validateUniqueness(createUserDto: CreateUserDto): Promise { // 检查用户名是否已存在 if (createUserDto.username) { const existingUser = await this.usersRepository.findOne({ where: { username: createUserDto.username } }); if (existingUser) { this.logger.warn('用户创建失败:用户名已存在', { operation: 'createWithDuplicateCheck', username: createUserDto.username, existingUserId: existingUser.id.toString() }); throw new ConflictException('用户名已存在'); } } // 检查邮箱是否已存在 if (createUserDto.email) { const existingEmail = await this.usersRepository.findOne({ where: { email: createUserDto.email } }); if (existingEmail) { this.logger.warn('用户创建失败:邮箱已存在', { operation: 'createWithDuplicateCheck', email: createUserDto.email, existingUserId: existingEmail.id.toString() }); throw new ConflictException('邮箱已存在'); } } // 检查手机号是否已存在 if (createUserDto.phone) { const existingPhone = await this.usersRepository.findOne({ where: { phone: createUserDto.phone } }); if (existingPhone) { this.logger.warn('用户创建失败:手机号已存在', { operation: 'createWithDuplicateCheck', phone: createUserDto.phone, existingUserId: existingPhone.id.toString() }); throw new ConflictException('手机号已存在'); } } // 检查GitHub ID是否已存在 if (createUserDto.github_id) { const existingGithub = await this.usersRepository.findOne({ where: { github_id: createUserDto.github_id } }); if (existingGithub) { this.logger.warn('用户创建失败:GitHub ID已存在', { operation: 'createWithDuplicateCheck', github_id: createUserDto.github_id, existingUserId: existingGithub.id.toString() }); throw new ConflictException('GitHub ID已存在'); } } } /** * 查询所有用户 * * @param limit 限制返回数量,默认100 * @param offset 偏移量,默认0 * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户列表 */ async findAll(limit: number = 100, offset: number = 0, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = {}; return await this.usersRepository.find({ where: whereCondition, take: limit, skip: offset, order: { created_at: 'DESC' } }); } /** * 根据ID查询用户 * * @param id 用户ID * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户实体 * @throws NotFoundException 当用户不存在时 */ async findOne(id: bigint, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = { id }; const user = await this.usersRepository.findOne({ where: whereCondition }); if (!user) { throw new NotFoundException(`ID为 ${id} 的用户不存在`); } return user; } /** * 根据用户名查询用户 * * @param username 用户名 * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户实体或null */ async findByUsername(username: string, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = { username }; return await this.usersRepository.findOne({ where: whereCondition }); } /** * 根据邮箱查询用户 * * @param email 邮箱 * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户实体或null */ async findByEmail(email: string, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = { email }; return await this.usersRepository.findOne({ where: whereCondition }); } /** * 根据GitHub ID查询用户 * * @param githubId GitHub ID * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户实体或null */ async findByGithubId(githubId: string, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = { github_id: githubId }; return await this.usersRepository.findOne({ where: whereCondition }); } /** * 更新用户信息 * * 功能描述: * 更新指定用户的信息,包含完整的数据验证和唯一性检查 * * 业务逻辑: * 1. 验证用户是否存在 * 2. 检查更新字段的唯一性约束(用户名、邮箱、手机号、GitHub ID) * 3. 合并更新数据到现有用户实体 * 4. 保存更新后的用户信息 * 5. 记录操作日志 * * @param id 用户ID,必须是有效的已存在用户 * @param updateData 更新的数据,支持部分字段更新 * @returns 更新后的用户实体 * @throws NotFoundException 当用户不存在时 * @throws ConflictException 当更新的数据与其他用户冲突时 * * @example * ```typescript * const updatedUser = await usersService.update(BigInt(1), { * nickname: '新昵称', * email: 'new@example.com' * }); * ``` */ async update(id: bigint, updateData: Partial): Promise { const startTime = Date.now(); this.logger.log('开始更新用户信息', { operation: 'update', userId: id.toString(), updateFields: Object.keys(updateData), timestamp: new Date().toISOString() }); try { // 1. 检查用户是否存在 - 确保要更新的用户确实存在 const existingUser = await this.findOne(id); // 2. 检查更新数据的唯一性约束 - 防止违反数据库唯一约束 // 2.1 检查用户名唯一性 - 只有当用户名确实发生变化时才检查 if (updateData.username && updateData.username !== existingUser.username) { const usernameExists = await this.usersRepository.findOne({ where: { username: updateData.username } }); if (usernameExists) { this.logger.warn('用户更新失败:用户名已存在', { operation: 'update', userId: id.toString(), conflictUsername: updateData.username, existingUserId: usernameExists.id.toString() }); throw new ConflictException('用户名已存在'); } } // 2.2 检查邮箱唯一性 - 只有当邮箱确实发生变化时才检查 if (updateData.email && updateData.email !== existingUser.email) { const emailExists = await this.usersRepository.findOne({ where: { email: updateData.email } }); if (emailExists) { this.logger.warn('用户更新失败:邮箱已存在', { operation: 'update', userId: id.toString(), conflictEmail: updateData.email, existingUserId: emailExists.id.toString() }); throw new ConflictException('邮箱已存在'); } } // 2.3 检查手机号唯一性 - 只有当手机号确实发生变化时才检查 if (updateData.phone && updateData.phone !== existingUser.phone) { const phoneExists = await this.usersRepository.findOne({ where: { phone: updateData.phone } }); if (phoneExists) { this.logger.warn('用户更新失败:手机号已存在', { operation: 'update', userId: id.toString(), conflictPhone: updateData.phone, existingUserId: phoneExists.id.toString() }); throw new ConflictException('手机号已存在'); } } // 2.4 检查GitHub ID唯一性 - 只有当GitHub ID确实发生变化时才检查 if (updateData.github_id && updateData.github_id !== existingUser.github_id) { const githubExists = await this.usersRepository.findOne({ where: { github_id: updateData.github_id } }); if (githubExists) { this.logger.warn('用户更新失败:GitHub ID已存在', { operation: 'update', userId: id.toString(), conflictGithubId: updateData.github_id, existingUserId: githubExists.id.toString() }); throw new ConflictException('GitHub ID已存在'); } } // 3. 合并更新数据 - 使用Object.assign将新数据合并到现有实体 Object.assign(existingUser, updateData); // 4. 保存更新后的用户信息 - TypeORM会自动更新updated_at字段 const updatedUser = await this.usersRepository.save(existingUser); const duration = Date.now() - startTime; this.logger.log('用户信息更新成功', { operation: 'update', userId: id.toString(), updateFields: Object.keys(updateData), duration, timestamp: new Date().toISOString() }); return updatedUser; } catch (error) { const duration = Date.now() - startTime; if (error instanceof NotFoundException || error instanceof ConflictException) { throw error; } this.logger.error('用户更新系统异常', { operation: 'update', userId: id.toString(), updateData, error: error instanceof Error ? error.message : String(error), duration, timestamp: new Date().toISOString() }, error instanceof Error ? error.stack : undefined); throw new BadRequestException('用户更新失败,请稍后重试'); } } /** * 删除用户 * * 功能描述: * 物理删除指定的用户记录,数据将从数据库中永久移除 * * 业务逻辑: * 1. 验证用户是否存在 * 2. 执行物理删除操作 * 3. 返回删除结果统计 * 4. 记录删除操作日志 * * 注意事项: * - 这是物理删除,数据无法恢复 * - 如需保留数据,请使用 softRemove 方法 * - 删除前请确认用户没有关联的重要数据 * * @param id 用户ID,必须是有效的已存在用户 * @returns 删除操作结果,包含影响行数和操作消息 * @throws NotFoundException 当用户不存在时 * * @example * ```typescript * const result = await usersService.remove(BigInt(1)); * console.log(`删除了 ${result.affected} 个用户`); * ``` */ async remove(id: bigint): Promise<{ affected: number; message: string }> { const startTime = Date.now(); this.logger.log('开始删除用户', { operation: 'remove', userId: id.toString(), timestamp: new Date().toISOString() }); try { // 1. 检查用户是否存在 - 确保要删除的用户确实存在 await this.findOne(id); // 2. 执行删除操作 - 使用where条件来处理bigint类型 const result = await this.usersRepository.delete({ id }); const deleteResult = { affected: result.affected || 0, message: `成功删除ID为 ${id} 的用户` }; const duration = Date.now() - startTime; this.logger.log('用户删除成功', { operation: 'remove', userId: id.toString(), affected: deleteResult.affected, duration, timestamp: new Date().toISOString() }); return deleteResult; } catch (error) { const duration = Date.now() - startTime; if (error instanceof NotFoundException) { throw error; } this.logger.error('用户删除系统异常', { operation: 'remove', userId: id.toString(), error: error instanceof Error ? error.message : String(error), duration, timestamp: new Date().toISOString() }, error instanceof Error ? error.stack : undefined); throw new BadRequestException('用户删除失败,请稍后重试'); } } /** * 软删除用户 * * @param id 用户ID * @returns 软删除操作结果 */ async softRemove(id: bigint): Promise { const user = await this.findOne(id); // Temporarily disabled soft delete since deleted_at column doesn't exist // user.deleted_at = new Date(); // For now, just return the user without modification return user; } /** * 统计用户数量 * * @param conditions 查询条件 * @returns 用户数量 */ async count(conditions?: FindOptionsWhere): Promise { return await this.usersRepository.count({ where: conditions }); } /** * 检查用户是否存在 * * @param id 用户ID * @returns 是否存在 */ async exists(id: bigint): Promise { const count = await this.usersRepository.count({ where: { id } }); return count > 0; } /** * 批量创建用户 * * @param createUserDtos 用户数据数组 * @returns 创建的用户列表 */ async createBatch(createUserDtos: CreateUserDto[]): Promise { const users: Users[] = []; for (const dto of createUserDtos) { const user = await this.create(dto); users.push(user); } return users; } /** * 根据角色查询用户 * * @param role 角色值 * @param includeDeleted 是否包含已删除用户,默认false * @returns 用户列表 */ async findByRole(role: number, includeDeleted: boolean = false): Promise { // Temporarily removed deleted_at filtering since the column doesn't exist in the database const whereCondition = { role }; return await this.usersRepository.find({ where: whereCondition, order: { created_at: 'DESC' } }); } /** * 搜索用户(根据用户名或昵称) * * 功能描述: * 根据关键词在用户名和昵称字段中进行模糊搜索,支持部分匹配 * * 业务逻辑: * 1. 使用QueryBuilder构建复杂查询 * 2. 对用户名和昵称字段进行LIKE模糊匹配 * 3. 按创建时间倒序排列结果 * 4. 限制返回数量防止性能问题 * * 性能考虑: * - 使用数据库索引优化查询性能 * - 限制返回数量避免大数据量问题 * - 建议在用户名和昵称字段上建立索引 * * @param keyword 搜索关键词,支持中文、英文、数字等字符 * @param limit 限制数量,默认20条,建议不超过100 * @returns 匹配的用户列表,按创建时间倒序排列 * * @example * ```typescript * // 搜索包含"张三"的用户 * const users = await usersService.search('张三', 10); * * // 搜索包含"admin"的用户 * const adminUsers = await usersService.search('admin'); * ``` */ async search(keyword: string, limit: number = 20, includeDeleted: boolean = false): Promise { const startTime = Date.now(); this.logStart('搜索用户', { keyword, limit, includeDeleted }); try { // 1. 构建查询 - 使用QueryBuilder支持复杂的WHERE条件 const queryBuilder = this.usersRepository.createQueryBuilder('user'); // 2. 添加搜索条件 - 在用户名和昵称中进行模糊匹配 let whereClause = 'user.username LIKE :keyword OR user.nickname LIKE :keyword'; // 3. 添加软删除过滤条件 - temporarily disabled since deleted_at column doesn't exist // if (!includeDeleted) { // whereClause += ' AND user.deleted_at IS NULL'; // } const result = await queryBuilder .where(whereClause, { keyword: `%${keyword}%` // 前后加%实现模糊匹配 }) .orderBy('user.created_at', 'DESC') // 按创建时间倒序 .limit(limit) // 限制返回数量 .getMany(); const duration = Date.now() - startTime; this.logSuccess('搜索用户', { keyword, limit, includeDeleted, resultCount: result.length }, duration); return result; } catch (error) { const duration = Date.now() - startTime; // 搜索异常使用特殊处理,返回空数组而不抛出异常 return this.handleSearchError(error, '搜索用户', { keyword, limit, includeDeleted, duration }); } } }