forked from datawhale/whale-town-end
330 lines
8.9 KiB
TypeScript
330 lines
8.9 KiB
TypeScript
/**
|
||
* 用户服务类
|
||
*
|
||
* 功能描述:
|
||
* - 提供用户的增删改查操作
|
||
* - 处理用户数据的业务逻辑
|
||
* - 数据验证和错误处理
|
||
*
|
||
* @author moyin
|
||
* @version 1.0.0
|
||
* @since 2025-12-17
|
||
*/
|
||
|
||
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 { validate } from 'class-validator';
|
||
import { plainToClass } from 'class-transformer';
|
||
|
||
@Injectable()
|
||
export class UsersService {
|
||
constructor(
|
||
@InjectRepository(Users)
|
||
private readonly usersRepository: Repository<Users>,
|
||
) {}
|
||
|
||
/**
|
||
* 创建新用户
|
||
*
|
||
* @param createUserDto 创建用户的数据传输对象
|
||
* @returns 创建的用户实体
|
||
* @throws ConflictException 当用户名、邮箱或手机号已存在时
|
||
* @throws BadRequestException 当数据验证失败时
|
||
*/
|
||
async create(createUserDto: CreateUserDto): Promise<Users> {
|
||
// 验证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('; ');
|
||
throw new BadRequestException(`数据验证失败: ${errorMessages}`);
|
||
}
|
||
|
||
// 检查用户名是否已存在
|
||
if (createUserDto.username) {
|
||
const existingUser = await this.usersRepository.findOne({
|
||
where: { username: createUserDto.username }
|
||
});
|
||
if (existingUser) {
|
||
throw new ConflictException('用户名已存在');
|
||
}
|
||
}
|
||
|
||
// 检查邮箱是否已存在
|
||
if (createUserDto.email) {
|
||
const existingEmail = await this.usersRepository.findOne({
|
||
where: { email: createUserDto.email }
|
||
});
|
||
if (existingEmail) {
|
||
throw new ConflictException('邮箱已存在');
|
||
}
|
||
}
|
||
|
||
// 检查手机号是否已存在
|
||
if (createUserDto.phone) {
|
||
const existingPhone = await this.usersRepository.findOne({
|
||
where: { phone: createUserDto.phone }
|
||
});
|
||
if (existingPhone) {
|
||
throw new ConflictException('手机号已存在');
|
||
}
|
||
}
|
||
|
||
// 检查GitHub ID是否已存在
|
||
if (createUserDto.github_id) {
|
||
const existingGithub = await this.usersRepository.findOne({
|
||
where: { github_id: createUserDto.github_id }
|
||
});
|
||
if (existingGithub) {
|
||
throw new ConflictException('GitHub ID已存在');
|
||
}
|
||
}
|
||
|
||
// 创建用户实体
|
||
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;
|
||
|
||
// 保存到数据库
|
||
return await this.usersRepository.save(user);
|
||
}
|
||
|
||
/**
|
||
* 查询所有用户
|
||
*
|
||
* @param limit 限制返回数量,默认100
|
||
* @param offset 偏移量,默认0
|
||
* @returns 用户列表
|
||
*/
|
||
async findAll(limit: number = 100, offset: number = 0): Promise<Users[]> {
|
||
return await this.usersRepository.find({
|
||
take: limit,
|
||
skip: offset,
|
||
order: { created_at: 'DESC' }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 根据ID查询用户
|
||
*
|
||
* @param id 用户ID
|
||
* @returns 用户实体
|
||
* @throws NotFoundException 当用户不存在时
|
||
*/
|
||
async findOne(id: bigint): Promise<Users> {
|
||
const user = await this.usersRepository.findOne({
|
||
where: { id }
|
||
});
|
||
|
||
if (!user) {
|
||
throw new NotFoundException(`ID为 ${id} 的用户不存在`);
|
||
}
|
||
|
||
return user;
|
||
}
|
||
|
||
/**
|
||
* 根据用户名查询用户
|
||
*
|
||
* @param username 用户名
|
||
* @returns 用户实体或null
|
||
*/
|
||
async findByUsername(username: string): Promise<Users | null> {
|
||
return await this.usersRepository.findOne({
|
||
where: { username }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 根据邮箱查询用户
|
||
*
|
||
* @param email 邮箱
|
||
* @returns 用户实体或null
|
||
*/
|
||
async findByEmail(email: string): Promise<Users | null> {
|
||
return await this.usersRepository.findOne({
|
||
where: { email }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 根据GitHub ID查询用户
|
||
*
|
||
* @param githubId GitHub ID
|
||
* @returns 用户实体或null
|
||
*/
|
||
async findByGithubId(githubId: string): Promise<Users | null> {
|
||
return await this.usersRepository.findOne({
|
||
where: { github_id: githubId }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 更新用户信息
|
||
*
|
||
* @param id 用户ID
|
||
* @param updateData 更新的数据
|
||
* @returns 更新后的用户实体
|
||
* @throws NotFoundException 当用户不存在时
|
||
* @throws ConflictException 当更新的数据与其他用户冲突时
|
||
*/
|
||
async update(id: bigint, updateData: Partial<CreateUserDto>): Promise<Users> {
|
||
// 检查用户是否存在
|
||
const existingUser = await this.findOne(id);
|
||
|
||
// 检查更新数据的唯一性约束
|
||
if (updateData.username && updateData.username !== existingUser.username) {
|
||
const usernameExists = await this.usersRepository.findOne({
|
||
where: { username: updateData.username }
|
||
});
|
||
if (usernameExists) {
|
||
throw new ConflictException('用户名已存在');
|
||
}
|
||
}
|
||
|
||
if (updateData.email && updateData.email !== existingUser.email) {
|
||
const emailExists = await this.usersRepository.findOne({
|
||
where: { email: updateData.email }
|
||
});
|
||
if (emailExists) {
|
||
throw new ConflictException('邮箱已存在');
|
||
}
|
||
}
|
||
|
||
if (updateData.phone && updateData.phone !== existingUser.phone) {
|
||
const phoneExists = await this.usersRepository.findOne({
|
||
where: { phone: updateData.phone }
|
||
});
|
||
if (phoneExists) {
|
||
throw new ConflictException('手机号已存在');
|
||
}
|
||
}
|
||
|
||
if (updateData.github_id && updateData.github_id !== existingUser.github_id) {
|
||
const githubExists = await this.usersRepository.findOne({
|
||
where: { github_id: updateData.github_id }
|
||
});
|
||
if (githubExists) {
|
||
throw new ConflictException('GitHub ID已存在');
|
||
}
|
||
}
|
||
|
||
// 更新用户数据
|
||
Object.assign(existingUser, updateData);
|
||
|
||
return await this.usersRepository.save(existingUser);
|
||
}
|
||
|
||
/**
|
||
* 删除用户
|
||
*
|
||
* @param id 用户ID
|
||
* @returns 删除操作结果
|
||
* @throws NotFoundException 当用户不存在时
|
||
*/
|
||
async remove(id: bigint): Promise<{ affected: number; message: string }> {
|
||
// 检查用户是否存在
|
||
await this.findOne(id);
|
||
|
||
// 执行删除 - 使用where条件来处理bigint类型
|
||
const result = await this.usersRepository.delete({ id });
|
||
|
||
return {
|
||
affected: result.affected || 0,
|
||
message: `成功删除ID为 ${id} 的用户`
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 软删除用户(如果需要保留数据)
|
||
* 注意:需要在实体中添加 @DeleteDateColumn 装饰器
|
||
*
|
||
* @param id 用户ID
|
||
* @returns 软删除操作结果
|
||
*/
|
||
async softRemove(id: bigint): Promise<Users> {
|
||
const user = await this.findOne(id);
|
||
return await this.usersRepository.softRemove(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 角色值
|
||
* @returns 用户列表
|
||
*/
|
||
async findByRole(role: number): Promise<Users[]> {
|
||
return await this.usersRepository.find({
|
||
where: { role },
|
||
order: { created_at: 'DESC' }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 搜索用户(根据用户名或昵称)
|
||
*
|
||
* @param keyword 搜索关键词
|
||
* @param limit 限制数量
|
||
* @returns 用户列表
|
||
*/
|
||
async search(keyword: string, limit: number = 20): Promise<Users[]> {
|
||
return await this.usersRepository
|
||
.createQueryBuilder('user')
|
||
.where('user.username LIKE :keyword OR user.nickname LIKE :keyword', {
|
||
keyword: `%${keyword}%`
|
||
})
|
||
.orderBy('user.created_at', 'DESC')
|
||
.limit(limit)
|
||
.getMany();
|
||
}
|
||
} |