Files
whale-town-end/src/core/db/users/users.service.ts
moyin c2a1c6862d refactor:重构核心模块架构
- 重构用户管理服务,优化内存服务实现
- 简化zulip_core模块结构,移除冗余配置和接口
- 更新用户状态枚举和实体定义
- 优化登录核心服务的测试覆盖
2026-01-08 23:04:49 +08:00

745 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 用户服务类
*
* 功能描述:
* - 提供用户数据的增删改查技术实现
* - 处理数据持久化和存储操作
* - 数据格式验证和约束检查
* - 支持完整的数据生命周期管理
*
* 职责分离:
* - 数据持久化通过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<Users>,
) {
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<Users> {
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<Users> {
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<void> {
// 检查用户名是否已存在
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<Users[]> {
// 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<Users> {
// 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<Users | null> {
// 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<Users | null> {
// 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<Users | null> {
// 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<CreateUserDto>): Promise<Users> {
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<Users> {
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<Users>): Promise<number> {
return await this.usersRepository.count({ where: conditions });
}
/**
* 检查用户是否存在
*
* @param id 用户ID
* @returns 是否存在
*/
async exists(id: bigint): Promise<boolean> {
const count = await this.usersRepository.count({ where: { id } });
return count > 0;
}
/**
* 批量创建用户
*
* @param createUserDtos 用户数据数组
* @returns 创建的用户列表
*/
async createBatch(createUserDtos: CreateUserDto[]): Promise<Users[]> {
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<Users[]> {
// 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<Users[]> {
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 });
}
}
}