Files
whale-town-end/src/core/db/users/users.service.ts
moyin 8816b29b0a chore: 更新项目配置和核心服务
- 更新package.json和jest配置
- 更新main.ts启动配置
- 完善用户管理和数据库服务
- 更新安全核心模块
- 优化Zulip核心服务

配置改进:
- 统一项目依赖管理
- 优化测试配置
- 完善服务模块化架构
2026-01-09 17:03:57 +08:00

714 lines
22 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';
import { USER_ROLES, QUERY_LIMITS, ERROR_MESSAGES, DATABASE_CONSTANTS, ValidationUtils, PerformanceMonitor } from './users.constants';
@Injectable()
export class UsersService extends BaseUsersService {
constructor(
@InjectRepository(Users)
private readonly usersRepository: Repository<Users>,
) {
super(); // 调用基类构造函数
}
/**
* 创建新用户
*
* 技术实现:
* 1. 验证输入数据的格式和完整性
* 2. 创建用户实体并设置默认值
* 3. 保存用户数据到数据库
* 4. 记录操作日志和性能指标
*
* @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 monitor = PerformanceMonitor.create();
this.logger.log('开始创建用户', {
operation: 'create',
username: createUserDto.username,
email: createUserDto.email,
timestamp: new Date().toISOString()
});
try {
// 验证DTO
await this.validateCreateUserDto(createUserDto);
// 创建用户实体
const user = this.buildUserEntity(createUserDto);
// 保存到数据库
const savedUser = await this.usersRepository.save(user);
this.logger.log('用户创建成功', {
operation: 'create',
userId: savedUser.id.toString(),
username: savedUser.username,
email: savedUser.email,
duration: monitor.getDuration(),
timestamp: new Date().toISOString()
});
return savedUser;
} catch (error) {
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: monitor.getDuration(),
timestamp: new Date().toISOString()
}, error instanceof Error ? error.stack : undefined);
throw new BadRequestException(ERROR_MESSAGES.USER_CREATE_FAILED);
}
}
/**
* 验证创建用户DTO
*
* @param createUserDto 用户数据
* @throws BadRequestException 当数据验证失败时
*/
private async validateCreateUserDto(createUserDto: CreateUserDto): Promise<void> {
const dto = plainToClass(CreateUserDto, createUserDto);
const validationErrors = await validate(dto);
if (validationErrors.length > 0) {
const errorMessages = ValidationUtils.formatValidationErrors(validationErrors);
this.logger.warn('用户创建失败:数据验证失败', {
operation: 'create',
username: createUserDto.username,
email: createUserDto.email,
validationErrors: errorMessages
});
throw new BadRequestException(`${ERROR_MESSAGES.VALIDATION_FAILED}: ${errorMessages}`);
}
}
/**
* 构建用户实体
*
* @param createUserDto 用户数据
* @returns 用户实体
*/
private buildUserEntity(createUserDto: CreateUserDto): Users {
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 || USER_ROLES.NORMAL_USER;
user.email_verified = createUserDto.email_verified || false;
user.status = createUserDto.status || UserStatus.ACTIVE;
return user;
}
/**
* 创建新用户(带重复检查)
*
* 技术实现:
* 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 monitor = PerformanceMonitor.create();
this.logStart('创建用户(带重复检查)', {
username: createUserDto.username,
email: createUserDto.email,
phone: createUserDto.phone,
github_id: createUserDto.github_id
});
try {
// 执行所有唯一性检查
await this.validateUniqueness(createUserDto);
// 调用普通的创建方法
const user = await this.create(createUserDto);
this.logSuccess('创建用户(带重复检查)', {
userId: user.id.toString(),
username: user.username
}, monitor.getDuration());
return user;
} catch (error) {
this.handleServiceError(error, '创建用户(带重复检查)', {
username: createUserDto.username,
duration: monitor.getDuration()
});
}
}
/**
* 验证用户数据的唯一性
*
* @param createUserDto 用户数据
* @throws ConflictException 当发现重复数据时
*/
private async validateUniqueness(createUserDto: CreateUserDto): Promise<void> {
await this.checkUsernameUniqueness(createUserDto.username);
await this.checkEmailUniqueness(createUserDto.email);
await this.checkPhoneUniqueness(createUserDto.phone);
await this.checkGithubIdUniqueness(createUserDto.github_id);
}
/**
* 检查用户名唯一性
*/
private async checkUsernameUniqueness(username?: string): Promise<void> {
if (username) {
const existingUser = await this.usersRepository.findOne({
where: { username }
});
if (existingUser) {
this.logger.warn('用户创建失败:用户名已存在', {
operation: 'uniqueness_check',
username,
existingUserId: existingUser.id.toString()
});
throw new ConflictException(ERROR_MESSAGES.USERNAME_EXISTS);
}
}
}
/**
* 检查邮箱唯一性
*/
private async checkEmailUniqueness(email?: string): Promise<void> {
if (email) {
const existingEmail = await this.usersRepository.findOne({
where: { email }
});
if (existingEmail) {
this.logger.warn('用户创建失败:邮箱已存在', {
operation: 'uniqueness_check',
email,
existingUserId: existingEmail.id.toString()
});
throw new ConflictException(ERROR_MESSAGES.EMAIL_EXISTS);
}
}
}
/**
* 检查手机号唯一性
*/
private async checkPhoneUniqueness(phone?: string): Promise<void> {
if (phone) {
const existingPhone = await this.usersRepository.findOne({
where: { phone }
});
if (existingPhone) {
this.logger.warn('用户创建失败:手机号已存在', {
operation: 'uniqueness_check',
phone,
existingUserId: existingPhone.id.toString()
});
throw new ConflictException(ERROR_MESSAGES.PHONE_EXISTS);
}
}
}
/**
* 检查GitHub ID唯一性
*/
private async checkGithubIdUniqueness(githubId?: string): Promise<void> {
if (githubId) {
const existingGithub = await this.usersRepository.findOne({
where: { github_id: githubId }
});
if (existingGithub) {
this.logger.warn('用户创建失败GitHub ID已存在', {
operation: 'uniqueness_check',
github_id: githubId,
existingUserId: existingGithub.id.toString()
});
throw new ConflictException(ERROR_MESSAGES.GITHUB_ID_EXISTS);
}
}
}
/**
* 查询所有用户
*
* @param limit 限制返回数量默认100
* @param offset 偏移量默认0
* @param includeDeleted 是否包含已删除用户默认false
* @returns 用户列表
*/
async findAll(limit: number = QUERY_LIMITS.DEFAULT_LIMIT, offset: number = 0, includeDeleted: boolean = false): Promise<Users[]> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
const whereCondition = {};
return await this.usersRepository.find({
where: whereCondition,
take: limit,
skip: offset,
order: { created_at: DATABASE_CONSTANTS.ORDER_DESC }
});
}
/**
* 根据ID查询用户
*
* @param id 用户ID
* @param includeDeleted 是否包含已删除用户默认false
* @returns 用户实体
* @throws NotFoundException 当用户不存在时
*/
async findOne(id: bigint, includeDeleted: boolean = false): Promise<Users> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
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> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
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> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
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> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
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 monitor = PerformanceMonitor.create();
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. 检查更新数据的唯一性约束 - 防止违反数据库唯一约束
await this.checkUpdateUniqueness(id, updateData);
// 3. 合并更新数据 - 使用Object.assign将新数据合并到现有实体
Object.assign(existingUser, updateData);
// 4. 保存更新后的用户信息 - TypeORM会自动更新updated_at字段
const updatedUser = await this.usersRepository.save(existingUser);
this.logger.log('用户信息更新成功', {
operation: 'update',
userId: id.toString(),
updateFields: Object.keys(updateData),
duration: monitor.getDuration(),
timestamp: new Date().toISOString()
});
return updatedUser;
} catch (error) {
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: monitor.getDuration(),
timestamp: new Date().toISOString()
}, error instanceof Error ? error.stack : undefined);
throw new BadRequestException(ERROR_MESSAGES.USER_UPDATE_FAILED);
}
}
/**
* 删除用户
*
* 功能描述:
* 物理删除指定的用户记录,数据将从数据库中永久移除
*
* 业务逻辑:
* 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 monitor = PerformanceMonitor.create();
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} 的用户`
};
this.logger.log('用户删除成功', {
operation: 'remove',
userId: id.toString(),
affected: deleteResult.affected,
duration: monitor.getDuration(),
timestamp: new Date().toISOString()
});
return deleteResult;
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
this.logger.error('用户删除系统异常', {
operation: 'remove',
userId: id.toString(),
error: error instanceof Error ? error.message : String(error),
duration: monitor.getDuration(),
timestamp: new Date().toISOString()
}, error instanceof Error ? error.stack : undefined);
throw new BadRequestException(ERROR_MESSAGES.USER_DELETE_FAILED);
}
}
/**
* 检查更新数据的唯一性约束
*
* @param id 用户ID
* @param updateData 更新数据
* @throws ConflictException 当发现冲突时
*/
private async checkUpdateUniqueness(id: bigint, updateData: Partial<CreateUserDto>): Promise<void> {
const existingUser = await this.findOne(id);
if (updateData.username && updateData.username !== existingUser.username) {
await this.checkUsernameUniqueness(updateData.username);
}
if (updateData.email && updateData.email !== existingUser.email) {
await this.checkEmailUniqueness(updateData.email);
}
if (updateData.phone && updateData.phone !== existingUser.phone) {
await this.checkPhoneUniqueness(updateData.phone);
}
if (updateData.github_id && updateData.github_id !== existingUser.github_id) {
await this.checkGithubIdUniqueness(updateData.github_id);
}
}
/**
* 软删除用户
*
* @param id 用户ID
* @returns 软删除操作结果
*/
async softRemove(id: bigint): Promise<Users> {
const user = await this.findOne(id);
// 注意:软删除功能暂未实现,当前仅返回用户实体
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[]> {
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
const whereCondition = { role };
return await this.usersRepository.find({
where: whereCondition,
order: { created_at: DATABASE_CONSTANTS.ORDER_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 = QUERY_LIMITS.DEFAULT_SEARCH_LIMIT, includeDeleted: boolean = false): Promise<Users[]> {
const monitor = PerformanceMonitor.create();
this.logStart('搜索用户', { keyword, limit, includeDeleted });
try {
// 1. 构建查询 - 使用QueryBuilder支持复杂的WHERE条件
const queryBuilder = this.usersRepository.createQueryBuilder('user');
// 添加搜索条件 - 在用户名和昵称中进行模糊匹配
let whereClause = 'user.username LIKE :keyword OR user.nickname LIKE :keyword';
// 注意软删除功能暂未实现includeDeleted参数预留用于未来扩展
const result = await queryBuilder
.where(whereClause, {
keyword: `%${keyword}%` // 前后加%实现模糊匹配
})
.orderBy('user.created_at', DATABASE_CONSTANTS.ORDER_DESC) // 按创建时间倒序
.limit(limit) // 限制返回数量
.getMany();
this.logSuccess('搜索用户', {
keyword,
limit,
includeDeleted,
resultCount: result.length
}, monitor.getDuration());
return result;
} catch (error) {
// 搜索异常使用特殊处理,返回空数组而不抛出异常
return this.handleSearchError(error, '搜索用户', {
keyword,
limit,
includeDeleted,
duration: monitor.getDuration()
});
}
}
}