feat:实现完整的用户管理系统

- 添加Users实体定义,包含完整的字段映射和约束
- 实现CreateUserDto数据验证,支持所有字段验证规则
- 创建UsersService服务,提供完整的CRUD操作
- 添加UsersModule模块配置
- 支持用户搜索、统计、批量操作等高级功能
This commit is contained in:
moyin
2025-12-17 11:03:17 +08:00
parent 508f9e8e5c
commit 418ecaa303
5 changed files with 1012 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
// src/user/dto/create-user.dto.ts
import {
IsString,
IsEmail,
IsPhoneNumber,
IsInt,
Min,
Max,
IsOptional,
Length,
IsNotEmpty
} from 'class-validator'
export class CreateUserDto {
// 用户名必填、字符串、长度1-50
@IsString()
@IsNotEmpty({ message: '用户名不能为空' })
@Length(1, 50, { message: '用户名长度需在1-50字符之间' })
username: string;
// 邮箱:可选、合法邮箱格式
@IsOptional()
@IsEmail({}, { message: '邮箱格式不正确' })
email?: string;
// 手机号:可选、合法手机号格式(支持全球号码)
@IsOptional()
@IsPhoneNumber(null, { message: '手机号格式不正确' })
phone?: string;
// 密码哈希可选OAuth登录为空
@IsOptional()
@IsString({ message: '密码哈希必须是字符串' })
password_hash?: string;
// 昵称必填、字符串、长度1-50
@IsString()
@IsNotEmpty({ message: '昵称不能为空' })
@Length(1, 50, { message: '昵称长度需在1-50字符之间' })
nickname: string;
// GitHub ID可选、字符串、长度1-100
@IsOptional()
@IsString({ message: 'GitHub ID必须是字符串' })
@Length(1, 100, { message: 'GitHub ID长度需在1-100字符之间' })
github_id?: string;
// 头像URL可选、字符串
@IsOptional()
@IsString({ message: '头像URL必须是字符串' })
avatar_url?: string;
// 角色可选、数字、1普通或9管理员
@IsOptional()
@IsInt({ message: '角色必须是数字' })
@Min(1, { message: '角色值最小为1' })
@Max(9, { message: '角色值最大为9' })
role?: number = 1; // 默认普通用户
}

View File

@@ -0,0 +1,106 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users') // 对应数据库表名
export class Users {
// idbigint、主键、非空、唯一、自增
@PrimaryGeneratedColumn({
type: 'bigint',
comment: '主键ID'
})
id: bigint;
// usernamevarchar(50)、非空、唯一
@Column({
type: 'varchar',
length: 50,
nullable: false,
unique: true,
comment: '唯一用户名/登录名'
})
username: string;
// emailvarchar(100)、允许空、唯一
@Column({
type: 'varchar',
length: 100,
nullable: true,
unique: true,
comment: '邮箱(用于找回/通知)'
})
email: string;
// phonevarchar(30)、允许空、唯一
@Column({
type: 'varchar',
length: 30,
nullable: true,
unique: true,
comment: '全球电话号码(用于找回/通知)'
})
phone: string;
// password_hashvarchar(255)、允许空
@Column({
type: 'varchar',
length: 255,
nullable: true,
comment: '密码哈希OAuth登录为空'
})
password_hash: string;
// nicknamevarchar(50)、非空
@Column({
type: 'varchar',
length: 50,
nullable: false,
comment: '显示昵称(头顶显示)'
})
nickname: string;
// github_idvarchar(100)、允许空、唯一
@Column({
type: 'varchar',
length: 100,
nullable: true,
unique: true,
comment: 'GitHub OpenID第三方登录用'
})
github_id: string;
// avatar_urlvarchar(255)、允许空
@Column({
type: 'varchar',
length: 255,
nullable: true,
comment: 'GitHub头像或自定义头像URL'
})
avatar_url: string;
// roletinyint、非空、默认1
@Column({
type: 'tinyint',
nullable: false,
default: 1,
comment: '角色1-普通9-管理员'
})
role: number;
// created_atdatetime、非空、默认当前时间
@CreateDateColumn({
type: 'datetime',
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
comment: '注册时间'
})
created_at: Date;
// updated_atdatetime、非空、默认当前时间
@UpdateDateColumn({
type: 'datetime',
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
onUpdate: 'CURRENT_TIMESTAMP', // 数据库更新时自动刷新时间
comment: '更新时间'
})
updated_at: Date;
}

View File

@@ -0,0 +1,26 @@
/**
* 用户模块
*
* 功能描述:
* - 整合用户相关的实体、服务和控制器
* - 配置TypeORM实体和Repository
* - 导出用户服务供其他模块使用
*
* @author moyin
* @version 1.0.0
* @since 2024-12-17
*/
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './users.entity';
import { UsersService } from './users.service';
@Module({
imports: [
TypeOrmModule.forFeature([Users])
],
providers: [UsersService],
exports: [UsersService, TypeOrmModule],
})
export class UsersModule {}

View File

@@ -0,0 +1,491 @@
/**
* 用户实体、DTO和服务的完整测试套件
*
* 功能:
* - 测试Users实体的结构和装饰器
* - 测试CreateUserDto的验证规则
* - 测试UsersService的所有CRUD操作
* - 验证数据类型和约束条件
*
* @author moyin
* @version 1.0.0
* @since 2024-12-17
*/
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { Users } from './users.entity';
import { CreateUserDto } from './users.dto';
import { UsersService } from './users.service';
describe('Users Entity, DTO and Service Tests', () => {
let service: UsersService;
let repository: Repository<Users>;
let module: TestingModule;
// 模拟的Repository方法
const mockRepository = {
save: jest.fn(),
find: jest.fn(),
findOne: jest.fn(),
delete: jest.fn(),
softRemove: jest.fn(),
count: jest.fn(),
createQueryBuilder: jest.fn(),
};
// 测试数据
const mockUser: Users = {
id: BigInt(1),
username: 'testuser',
email: 'test@example.com',
phone: '+8613800138000',
password_hash: 'hashed_password',
nickname: '测试用户',
github_id: 'github_123',
avatar_url: 'https://example.com/avatar.jpg',
role: 1,
created_at: new Date(),
updated_at: new Date(),
};
const createUserDto: CreateUserDto = {
username: 'testuser',
email: 'test@example.com',
phone: '+8613800138000',
password_hash: 'hashed_password',
nickname: '测试用户',
github_id: 'github_123',
avatar_url: 'https://example.com/avatar.jpg',
role: 1
};
beforeEach(async () => {
module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(Users),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get<Repository<Users>>(getRepositoryToken(Users));
// 清理所有mock
jest.clearAllMocks();
});
afterEach(async () => {
await module.close();
});
describe('Users Entity Tests', () => {
it('应该正确创建用户实体实例', () => {
const user = new Users();
user.username = 'testuser';
user.email = 'test@example.com';
user.phone = '+8613800138000';
user.password_hash = 'hashed_password';
user.nickname = '测试用户';
user.github_id = 'github_123';
user.avatar_url = 'https://example.com/avatar.jpg';
user.role = 1;
user.created_at = new Date();
user.updated_at = new Date();
expect(user.username).toBe('testuser');
expect(user.email).toBe('test@example.com');
expect(user.nickname).toBe('测试用户');
expect(user.role).toBe(1);
});
it('应该能够设置和获取所有属性', () => {
const user = new Users();
// 验证可以设置和获取所有属性
user.username = 'testuser';
user.email = 'test@example.com';
user.phone = '+8613800138000';
user.password_hash = 'hashed_password';
user.nickname = '测试用户';
user.github_id = 'github_123';
user.avatar_url = 'https://example.com/avatar.jpg';
user.role = 1;
user.created_at = new Date();
user.updated_at = new Date();
expect(user.username).toBe('testuser');
expect(user.email).toBe('test@example.com');
expect(user.phone).toBe('+8613800138000');
expect(user.password_hash).toBe('hashed_password');
expect(user.nickname).toBe('测试用户');
expect(user.github_id).toBe('github_123');
expect(user.avatar_url).toBe('https://example.com/avatar.jpg');
expect(user.role).toBe(1);
expect(user.created_at).toBeInstanceOf(Date);
expect(user.updated_at).toBeInstanceOf(Date);
});
});
describe('CreateUserDto Validation Tests', () => {
it('应该通过有效数据的验证', async () => {
const validData = {
username: 'testuser',
email: 'test@example.com',
phone: '+8613800138000',
password_hash: 'hashed_password',
nickname: '测试用户',
github_id: 'github_123',
avatar_url: 'https://example.com/avatar.jpg',
role: 1
};
const dto = plainToClass(CreateUserDto, validData);
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it('应该拒绝缺少必填字段的数据', async () => {
const invalidData = {
email: 'test@example.com'
// 缺少 username 和 nickname
};
const dto = plainToClass(CreateUserDto, invalidData);
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const usernameError = errors.find(error => error.property === 'username');
const nicknameError = errors.find(error => error.property === 'nickname');
expect(usernameError).toBeDefined();
expect(nicknameError).toBeDefined();
});
it('应该拒绝无效的邮箱格式', async () => {
const invalidData = {
username: 'testuser',
nickname: '测试用户',
email: 'invalid-email'
};
const dto = plainToClass(CreateUserDto, invalidData);
const errors = await validate(dto);
const emailError = errors.find(error => error.property === 'email');
expect(emailError).toBeDefined();
});
it('应该拒绝超长的用户名', async () => {
const invalidData = {
username: 'a'.repeat(51), // 超过50字符
nickname: '测试用户'
};
const dto = plainToClass(CreateUserDto, invalidData);
const errors = await validate(dto);
const usernameError = errors.find(error => error.property === 'username');
expect(usernameError).toBeDefined();
});
it('应该拒绝无效的角色值', async () => {
const invalidData = {
username: 'testuser',
nickname: '测试用户',
role: 10 // 超出1-9范围
};
const dto = plainToClass(CreateUserDto, invalidData);
const errors = await validate(dto);
const roleError = errors.find(error => error.property === 'role');
expect(roleError).toBeDefined();
});
});
describe('UsersService CRUD Tests', () => {
describe('create()', () => {
it('应该成功创建新用户', async () => {
mockRepository.findOne.mockResolvedValue(null); // 没有重复用户
mockRepository.save.mockResolvedValue(mockUser);
const result = await service.create(createUserDto);
expect(mockRepository.save).toHaveBeenCalled();
expect(result).toEqual(mockUser);
});
it('应该在用户名重复时抛出ConflictException', async () => {
mockRepository.findOne.mockResolvedValue(mockUser); // 模拟用户名已存在
await expect(service.create(createUserDto)).rejects.toThrow(ConflictException);
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('应该在数据验证失败时抛出BadRequestException', async () => {
const invalidDto = { username: '', nickname: '' }; // 无效数据
await expect(service.create(invalidDto as CreateUserDto)).rejects.toThrow(BadRequestException);
});
});
describe('findAll()', () => {
it('应该返回用户列表', async () => {
const mockUsers = [mockUser];
mockRepository.find.mockResolvedValue(mockUsers);
const result = await service.findAll();
expect(mockRepository.find).toHaveBeenCalledWith({
take: 100,
skip: 0,
order: { created_at: 'DESC' }
});
expect(result).toEqual(mockUsers);
});
it('应该支持分页参数', async () => {
mockRepository.find.mockResolvedValue([]);
await service.findAll(50, 10);
expect(mockRepository.find).toHaveBeenCalledWith({
take: 50,
skip: 10,
order: { created_at: 'DESC' }
});
});
});
describe('findOne()', () => {
it('应该根据ID返回用户', async () => {
mockRepository.findOne.mockResolvedValue(mockUser);
const result = await service.findOne(BigInt(1));
expect(mockRepository.findOne).toHaveBeenCalledWith({
where: { id: BigInt(1) }
});
expect(result).toEqual(mockUser);
});
it('应该在用户不存在时抛出NotFoundException', async () => {
mockRepository.findOne.mockResolvedValue(null);
await expect(service.findOne(BigInt(999))).rejects.toThrow(NotFoundException);
});
});
describe('findByUsername()', () => {
it('应该根据用户名返回用户', async () => {
mockRepository.findOne.mockResolvedValue(mockUser);
const result = await service.findByUsername('testuser');
expect(mockRepository.findOne).toHaveBeenCalledWith({
where: { username: 'testuser' }
});
expect(result).toEqual(mockUser);
});
it('应该在用户不存在时返回null', async () => {
mockRepository.findOne.mockResolvedValue(null);
const result = await service.findByUsername('nonexistent');
expect(result).toBeNull();
});
});
describe('findByEmail()', () => {
it('应该根据邮箱返回用户', async () => {
mockRepository.findOne.mockResolvedValue(mockUser);
const result = await service.findByEmail('test@example.com');
expect(mockRepository.findOne).toHaveBeenCalledWith({
where: { email: 'test@example.com' }
});
expect(result).toEqual(mockUser);
});
});
describe('update()', () => {
it('应该成功更新用户信息', async () => {
const updatedUser = { ...mockUser, nickname: '更新后的昵称' };
mockRepository.findOne
.mockResolvedValueOnce(mockUser) // findOne in update method
.mockResolvedValueOnce(null); // 检查昵称是否重复
mockRepository.save.mockResolvedValue(updatedUser);
const result = await service.update(BigInt(1), { nickname: '更新后的昵称' });
expect(mockRepository.save).toHaveBeenCalled();
expect(result.nickname).toBe('更新后的昵称');
});
it('应该在更新不存在的用户时抛出NotFoundException', async () => {
mockRepository.findOne.mockResolvedValue(null);
await expect(service.update(BigInt(999), { nickname: '新昵称' })).rejects.toThrow(NotFoundException);
});
it('应该在更新数据冲突时抛出ConflictException', async () => {
mockRepository.findOne
.mockResolvedValueOnce(mockUser) // 找到要更新的用户
.mockResolvedValueOnce(mockUser); // 发现用户名冲突
await expect(service.update(BigInt(1), { username: 'conflictuser' })).rejects.toThrow(ConflictException);
});
});
describe('remove()', () => {
it('应该成功删除用户', async () => {
mockRepository.findOne.mockResolvedValue(mockUser);
mockRepository.delete.mockResolvedValue({ affected: 1 });
const result = await service.remove(BigInt(1));
expect(mockRepository.delete).toHaveBeenCalledWith({ id: BigInt(1) });
expect(result.affected).toBe(1);
expect(result.message).toContain('成功删除');
});
it('应该在删除不存在的用户时抛出NotFoundException', async () => {
mockRepository.findOne.mockResolvedValue(null);
await expect(service.remove(BigInt(999))).rejects.toThrow(NotFoundException);
});
});
describe('count()', () => {
it('应该返回用户总数', async () => {
mockRepository.count.mockResolvedValue(10);
const result = await service.count();
expect(mockRepository.count).toHaveBeenCalled();
expect(result).toBe(10);
});
});
describe('exists()', () => {
it('应该在用户存在时返回true', async () => {
mockRepository.count.mockResolvedValue(1);
const result = await service.exists(BigInt(1));
expect(result).toBe(true);
});
it('应该在用户不存在时返回false', async () => {
mockRepository.count.mockResolvedValue(0);
const result = await service.exists(BigInt(999));
expect(result).toBe(false);
});
});
describe('search()', () => {
it('应该根据关键词搜索用户', async () => {
const mockQueryBuilder = {
where: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
getMany: jest.fn().mockResolvedValue([mockUser]),
};
mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
const result = await service.search('test');
expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('user');
expect(mockQueryBuilder.where).toHaveBeenCalled();
expect(result).toEqual([mockUser]);
});
});
describe('findByRole()', () => {
it('应该根据角色查询用户', async () => {
mockRepository.find.mockResolvedValue([mockUser]);
const result = await service.findByRole(1);
expect(mockRepository.find).toHaveBeenCalledWith({
where: { role: 1 },
order: { created_at: 'DESC' }
});
expect(result).toEqual([mockUser]);
});
});
});
describe('Integration Tests', () => {
it('应该完成从DTO到Entity的完整流程', async () => {
// 1. 验证DTO
const dto = plainToClass(CreateUserDto, createUserDto);
const validationErrors = await validate(dto);
expect(validationErrors).toHaveLength(0);
// 2. 创建匹配的mock用户数据
const expectedUser = { ...mockUser, nickname: dto.nickname };
// 3. 模拟服务创建用户
mockRepository.findOne.mockResolvedValue(null);
mockRepository.save.mockResolvedValue(expectedUser);
const result = await service.create(dto);
// 4. 验证结果
expect(result).toBeDefined();
expect(result.username).toBe(dto.username);
expect(result.nickname).toBe(dto.nickname);
});
it('应该正确处理可选字段', async () => {
const minimalDto: CreateUserDto = {
username: 'minimaluser',
nickname: '最小用户'
};
const dto = plainToClass(CreateUserDto, minimalDto);
const validationErrors = await validate(dto);
expect(validationErrors).toHaveLength(0);
// 验证可选字段的默认值处理
expect(dto.role).toBe(1); // 默认角色
expect(dto.email).toBeUndefined();
expect(dto.phone).toBeUndefined();
});
});
describe('Error Handling Tests', () => {
it('应该正确处理数据库连接错误', async () => {
mockRepository.save.mockRejectedValue(new Error('Database connection failed'));
await expect(service.create(createUserDto)).rejects.toThrow('Database connection failed');
});
it('应该正确处理并发创建冲突', async () => {
// 模拟并发情况:检查时不存在,保存时出现唯一约束错误
mockRepository.findOne.mockResolvedValue(null);
mockRepository.save.mockRejectedValue(new Error('Duplicate entry'));
await expect(service.create(createUserDto)).rejects.toThrow('Duplicate entry');
});
});
});

View File

@@ -0,0 +1,330 @@
/**
* 用户服务类
*
* 功能描述:
* - 提供用户的增删改查操作
* - 处理用户数据的业务逻辑
* - 数据验证和错误处理
*
* @author moyin
* @version 1.0.0
* @since 2024-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();
}
}