feat(task1): restructure directories for turborepo monorepo
- Move backend/ to apps/server/ via git mv - Move frontend/ to apps/admin/ via git mv - Create packages/typescript-config/ with base, nestjs, and react-vite presets
This commit is contained in:
68
apps/server/src/rbac/dto/rbac.dto.ts
Normal file
68
apps/server/src/rbac/dto/rbac.dto.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { IsString, MinLength, IsOptional, IsArray, IsBoolean } from 'class-validator';
|
||||
|
||||
export class CreateRoleDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
permissionIds?: number[];
|
||||
}
|
||||
|
||||
export class UpdateRoleDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
permissionIds?: number[];
|
||||
}
|
||||
|
||||
export class CreateUserDto {
|
||||
@IsString()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(4)
|
||||
password: string;
|
||||
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
roleIds?: number[];
|
||||
}
|
||||
|
||||
export class UpdateUserDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
username?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
roleIds?: number[];
|
||||
}
|
||||
|
||||
export class ResetPasswordDto {
|
||||
@IsString()
|
||||
@MinLength(4)
|
||||
password: string;
|
||||
}
|
||||
141
apps/server/src/rbac/rbac.controller.ts
Normal file
141
apps/server/src/rbac/rbac.controller.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import {
|
||||
Controller, Get, Post, Put, Delete, Body, Param, UseGuards, Request, BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { RbacService } from './rbac.service';
|
||||
import { CreateRoleDto, UpdateRoleDto, CreateUserDto, UpdateUserDto, ResetPasswordDto } from './dto/rbac.dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||||
import { extractRequestInfo } from '../common/request-utils';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('rbac')
|
||||
export class RbacController {
|
||||
constructor(
|
||||
private rbacService: RbacService,
|
||||
private logService: OperationLogsService,
|
||||
) {}
|
||||
|
||||
// ==================== 角色管理 ====================
|
||||
|
||||
@Get('roles')
|
||||
@RequirePermission('role:view')
|
||||
findAllRoles() {
|
||||
return this.rbacService.findAllRoles();
|
||||
}
|
||||
|
||||
@Get('roles/:id')
|
||||
@RequirePermission('role:view')
|
||||
findRoleById(@Param('id') id: string) {
|
||||
return this.rbacService.findRoleById(+id);
|
||||
}
|
||||
|
||||
@Post('roles')
|
||||
@RequirePermission('role:create')
|
||||
async createRole(@Body() dto: CreateRoleDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.rbacService.createRole(dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '创建角色', detail: `角色: ${dto.name}`, ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put('roles/:id')
|
||||
@RequirePermission('role:edit')
|
||||
async updateRole(@Param('id') id: string, @Body() dto: UpdateRoleDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.updateRole(+id, dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '编辑角色', targetId: +id, targetType: 'role', detail: JSON.stringify(dto), ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete('roles/:id')
|
||||
@RequirePermission('role:delete')
|
||||
async deleteRole(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.deleteRole(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: 'RBAC', action: '删除角色', targetId: +id, targetType: 'role', ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 权限管理 ====================
|
||||
|
||||
@Get('permissions')
|
||||
@RequirePermission('role:view')
|
||||
findAllPermissions() {
|
||||
return this.rbacService.findAllPermissions();
|
||||
}
|
||||
|
||||
@Get('permissions/tree')
|
||||
@RequirePermission('role:view')
|
||||
getPermissionTree() {
|
||||
return this.rbacService.getPermissionTree();
|
||||
}
|
||||
|
||||
// ==================== 用户管理 ====================
|
||||
|
||||
@Get('users')
|
||||
@RequirePermission('user:view')
|
||||
getUsers() {
|
||||
return this.rbacService.findAllUsers();
|
||||
}
|
||||
|
||||
@Post('users')
|
||||
@RequirePermission('user:create')
|
||||
async createUser(@Body() dto: CreateUserDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.createUser(dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '创建账号', detail: `用户名: ${dto.username}`, ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Put('users/:id')
|
||||
@RequirePermission('user:edit')
|
||||
async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.updateUser(+id, dto);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '更新账号', targetId: +id, targetType: 'user', detail: JSON.stringify(dto), ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Put('users/:id/password')
|
||||
@RequirePermission('user:reset-password')
|
||||
async resetPassword(@Param('id') id: string, @Body() dto: ResetPasswordDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.resetPassword(+id, dto.password);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '重置密码', targetId: +id, targetType: 'user', ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete('users/:id')
|
||||
@RequirePermission('user:delete')
|
||||
async deleteUser(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
try {
|
||||
const result = await this.rbacService.deleteUser(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '删除账号', targetId: +id, targetType: 'user', ipAddress, userAgent });
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
apps/server/src/rbac/rbac.module.ts
Normal file
22
apps/server/src/rbac/rbac.module.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Module, OnModuleInit, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Permission, Role, User } from '../entities';
|
||||
import { RbacService } from './rbac.service';
|
||||
import { RbacController } from './rbac.controller';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Permission, Role, User]),
|
||||
forwardRef(() => AuthModule),
|
||||
],
|
||||
controllers: [RbacController],
|
||||
providers: [RbacService],
|
||||
exports: [RbacService],
|
||||
})
|
||||
export class RbacModule implements OnModuleInit {
|
||||
constructor(private rbacService: RbacService) {}
|
||||
async onModuleInit() {
|
||||
await this.rbacService.seedData();
|
||||
}
|
||||
}
|
||||
328
apps/server/src/rbac/rbac.service.ts
Normal file
328
apps/server/src/rbac/rbac.service.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { Permission, Role, User } from '../entities';
|
||||
|
||||
const PRESET_PERMISSIONS: Array<{ code: string; name: string; group: string }> = [
|
||||
{ code: 'dashboard:view', name: '查看数据面板', group: 'dashboard' },
|
||||
{ code: 'student:view', name: '查看学生', group: 'student' },
|
||||
{ code: 'student:create', name: '新增学生', group: 'student' },
|
||||
{ code: 'student:edit', name: '编辑学生', group: 'student' },
|
||||
{ code: 'student:delete', name: '删除学生', group: 'student' },
|
||||
{ code: 'student:import', name: '导入学生', group: 'student' },
|
||||
{ code: 'student:export', name: '导出学生', group: 'student' },
|
||||
{ code: 'room:view', name: '查看宿舍', group: 'room' },
|
||||
{ code: 'room:create', name: '新增宿舍', group: 'room' },
|
||||
{ code: 'room:edit', name: '编辑宿舍', group: 'room' },
|
||||
{ code: 'room:delete', name: '删除宿舍', group: 'room' },
|
||||
{ code: 'occupancy:view', name: '查看入住', group: 'occupancy' },
|
||||
{ code: 'occupancy:checkin', name: '办理入住', group: 'occupancy' },
|
||||
{ code: 'occupancy:checkout', name: '办理退宿', group: 'occupancy' },
|
||||
{ code: 'occupancy:transfer', name: '调换宿舍', group: 'occupancy' },
|
||||
{ code: 'occupancy:delete', name: '删除入住记录', group: 'occupancy' },
|
||||
{ code: 'expense:view', name: '查看费用', group: 'expense' },
|
||||
{ code: 'expense:create', name: '录入费用', group: 'expense' },
|
||||
{ code: 'expense:edit', name: '编辑费用', group: 'expense' },
|
||||
{ code: 'expense:delete', name: '删除费用', group: 'expense' },
|
||||
{ code: 'bill:view', name: '查看账单', group: 'bill' },
|
||||
{ code: 'bill:generate', name: '生成账单', group: 'bill' },
|
||||
{ code: 'bill:confirm', name: '确认账单', group: 'bill' },
|
||||
{ code: 'bill:delete', name: '删除账单', group: 'bill' },
|
||||
{ code: 'bill:export-excel', name: '导出 Excel', group: 'bill' },
|
||||
{ code: 'bill:export-pdf', name: '导出 PDF', group: 'bill' },
|
||||
{ code: 'deposit:view', name: '查看押金', group: 'deposit' },
|
||||
{ code: 'deposit:create', name: '新增押金', group: 'deposit' },
|
||||
{ code: 'deposit:edit', name: '编辑押金', group: 'deposit' },
|
||||
{ code: 'deposit:delete', name: '删除押金', group: 'deposit' },
|
||||
{ code: 'classroom:view', name: '查看教室', group: 'classroom' },
|
||||
{ code: 'classroom:create', name: '新增教室', group: 'classroom' },
|
||||
{ code: 'classroom:edit', name: '编辑教室', group: 'classroom' },
|
||||
{ code: 'classroom:delete', name: '删除教室', group: 'classroom' },
|
||||
{ code: 'tenant:view', name: '查看租赁方', group: 'tenant' },
|
||||
{ code: 'tenant:create', name: '新增租赁方', group: 'tenant' },
|
||||
{ code: 'tenant:edit', name: '编辑租赁方', group: 'tenant' },
|
||||
{ code: 'tenant:delete', name: '删除租赁方', group: 'tenant' },
|
||||
{ code: 'rental:view', name: '查看租赁订单', group: 'rental' },
|
||||
{ code: 'rental:create', name: '新增租赁订单', group: 'rental' },
|
||||
{ code: 'rental:edit', name: '编辑租赁订单', group: 'rental' },
|
||||
{ code: 'rental:delete', name: '删除租赁订单', group: 'rental' },
|
||||
{ code: 'log:view', name: '查看操作日志', group: 'log' },
|
||||
{ code: 'user:view', name: '查看用户', group: 'user' },
|
||||
{ code: 'user:create', name: '创建用户', group: 'user' },
|
||||
{ code: 'user:edit', name: '编辑用户', group: 'user' },
|
||||
{ code: 'user:delete', name: '删除用户', group: 'user' },
|
||||
{ code: 'user:reset-password', name: '重置密码', group: 'user' },
|
||||
{ code: 'role:view', name: '查看角色', group: 'role' },
|
||||
{ code: 'role:create', name: '创建角色', group: 'role' },
|
||||
{ code: 'role:edit', name: '编辑角色', group: 'role' },
|
||||
{ code: 'role:delete', name: '删除角色', group: 'role' },
|
||||
];
|
||||
|
||||
const PRESET_ROLES: Array<{
|
||||
name: string;
|
||||
code: string;
|
||||
description: string;
|
||||
isSystem: boolean;
|
||||
permissionGroups: string[];
|
||||
extraPermissions?: string[];
|
||||
}> = [
|
||||
{
|
||||
name: '超管',
|
||||
code: 'super_admin',
|
||||
description: '系统超级管理员,拥有全部权限',
|
||||
isSystem: true,
|
||||
permissionGroups: [],
|
||||
},
|
||||
{
|
||||
name: '宿管老师',
|
||||
code: 'dormitory_supervisor',
|
||||
description: '管理宿舍相关业务',
|
||||
isSystem: true,
|
||||
permissionGroups: ['student', 'room', 'occupancy', 'expense', 'bill', 'deposit', 'log', 'dashboard'],
|
||||
},
|
||||
{
|
||||
name: '老师',
|
||||
code: 'teacher',
|
||||
description: '查看和管理本班学生',
|
||||
isSystem: true,
|
||||
permissionGroups: ['student'],
|
||||
extraPermissions: ['student:view'],
|
||||
},
|
||||
{
|
||||
name: '机构负责人',
|
||||
code: 'institution_head',
|
||||
description: '管理机构教室和课程',
|
||||
isSystem: true,
|
||||
permissionGroups: ['classroom', 'rental', 'tenant'],
|
||||
},
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class RbacService {
|
||||
private readonly logger = new Logger(RbacService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Permission) private permRepo: Repository<Permission>,
|
||||
@InjectRepository(Role) private roleRepo: Repository<Role>,
|
||||
@InjectRepository(User) private userRepo: Repository<User>,
|
||||
) {}
|
||||
|
||||
async seedData(): Promise<void> {
|
||||
// Step 1: 幂等插入所有权限点(先查后插,兼容 SQLite/MySQL)
|
||||
for (const p of PRESET_PERMISSIONS) {
|
||||
const exists = await this.permRepo.findOne({ where: { code: p.code } });
|
||||
if (!exists) {
|
||||
await this.permRepo.save(this.permRepo.create(p));
|
||||
}
|
||||
}
|
||||
const allPerms = await this.permRepo.find();
|
||||
|
||||
// Step 2: 幂等插入预置角色
|
||||
for (const r of PRESET_ROLES) {
|
||||
const exists = await this.roleRepo.findOne({ where: { name: r.name } });
|
||||
if (!exists) {
|
||||
await this.roleRepo.save(
|
||||
this.roleRepo.create({ name: r.name, description: r.description, isSystem: r.isSystem }),
|
||||
);
|
||||
}
|
||||
}
|
||||
const allRoles = await this.roleRepo.find({ relations: ['permissions'] });
|
||||
|
||||
// Step 3: 构建角色-权限关联
|
||||
for (const preset of PRESET_ROLES) {
|
||||
const role = allRoles.find((r) => r.name === preset.name);
|
||||
if (!role) continue;
|
||||
|
||||
let perms: Permission[];
|
||||
if (preset.permissionGroups.length === 0) {
|
||||
// 超管:全部权限
|
||||
perms = allPerms;
|
||||
} else {
|
||||
// 按 group 匹配 + 额外权限(如老师的 student:view)
|
||||
const byGroup = allPerms.filter((p) => preset.permissionGroups.includes(p.group));
|
||||
const byExtra = preset.extraPermissions
|
||||
? allPerms.filter((p) => preset.extraPermissions!.includes(p.code))
|
||||
: [];
|
||||
perms = [...byGroup, ...byExtra].filter(
|
||||
(p, i, arr) => arr.findIndex((x) => x.id === p.id) === i,
|
||||
);
|
||||
}
|
||||
|
||||
// 幂等:只插入尚未关联的
|
||||
const existingIds = new Set(role.permissions.map((p) => p.id));
|
||||
const toAdd = perms.filter((p) => !existingIds.has(p.id));
|
||||
if (toAdd.length > 0) {
|
||||
role.permissions = [...role.permissions, ...toAdd];
|
||||
await this.roleRepo.save(role);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: 初始化 admin 用户
|
||||
const count = await this.userRepo.count();
|
||||
if (count === 0) {
|
||||
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
||||
const hash = await bcrypt.hash(adminPassword, 10);
|
||||
const adminUser = this.userRepo.create({
|
||||
username: 'admin',
|
||||
passwordHash: hash,
|
||||
name: '管理员',
|
||||
});
|
||||
const superAdminRole = allRoles.find((r) => r.name === '超管');
|
||||
if (superAdminRole) {
|
||||
adminUser.roles = [superAdminRole];
|
||||
}
|
||||
await this.userRepo.save(adminUser);
|
||||
this.logger.log(
|
||||
`已创建默认管理员: admin / ${adminPassword === 'admin123' ? 'admin123 (请尽快修改!)' : '******'}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(`种子数据初始化完成: ${allPerms.length} 权限点, ${allRoles.length} 角色`);
|
||||
}
|
||||
|
||||
async findAllRoles(): Promise<Role[]> {
|
||||
return this.roleRepo.find({
|
||||
relations: ['permissions'],
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findRoleById(id: number): Promise<Role> {
|
||||
return this.roleRepo.findOneOrFail({ where: { id }, relations: ['permissions'] });
|
||||
}
|
||||
|
||||
async createRole(dto: {
|
||||
name: string;
|
||||
description?: string;
|
||||
permissionIds?: number[];
|
||||
}): Promise<Role> {
|
||||
const role = this.roleRepo.create({ name: dto.name, description: dto.description });
|
||||
if (dto.permissionIds && dto.permissionIds.length > 0) {
|
||||
role.permissions = await this.permRepo.findByIds(dto.permissionIds);
|
||||
}
|
||||
return this.roleRepo.save(role);
|
||||
}
|
||||
|
||||
async updateRole(
|
||||
id: number,
|
||||
dto: { name?: string; description?: string; permissionIds?: number[] },
|
||||
): Promise<Role> {
|
||||
const role = await this.roleRepo.findOneOrFail({
|
||||
where: { id },
|
||||
relations: ['permissions'],
|
||||
});
|
||||
if (dto.name !== undefined && !role.isSystem) role.name = dto.name;
|
||||
if (dto.description !== undefined) role.description = dto.description;
|
||||
if (dto.permissionIds !== undefined) {
|
||||
role.permissions =
|
||||
dto.permissionIds.length > 0
|
||||
? await this.permRepo.findByIds(dto.permissionIds)
|
||||
: [];
|
||||
}
|
||||
return this.roleRepo.save(role);
|
||||
}
|
||||
|
||||
async deleteRole(id: number): Promise<{ message: string }> {
|
||||
const role = await this.roleRepo.findOneOrFail({ where: { id } });
|
||||
if (role.isSystem) throw new Error('系统角色不可删除');
|
||||
await this.roleRepo.remove(role);
|
||||
return { message: '角色已删除' };
|
||||
}
|
||||
|
||||
async findAllPermissions(): Promise<Permission[]> {
|
||||
return this.permRepo.find({ order: { group: 'ASC', code: 'ASC' } });
|
||||
}
|
||||
|
||||
async getPermissionTree(): Promise<{ group: string; permissions: Permission[] }[]> {
|
||||
const all = await this.findAllPermissions();
|
||||
const map = new Map<string, Permission[]>();
|
||||
for (const p of all) {
|
||||
if (!map.has(p.group)) map.set(p.group, []);
|
||||
map.get(p.group)!.push(p);
|
||||
}
|
||||
return Array.from(map.entries()).map(([group, permissions]) => ({ group, permissions }));
|
||||
}
|
||||
|
||||
async getUserPermissions(userId: number): Promise<string[]> {
|
||||
const user = await this.userRepo.findOne({
|
||||
where: { id: userId },
|
||||
relations: ['roles', 'roles.permissions'],
|
||||
});
|
||||
if (!user || !user.roles) return [];
|
||||
const codes = new Set<string>();
|
||||
for (const role of user.roles) {
|
||||
if (role.status !== 1) continue;
|
||||
for (const perm of role.permissions) {
|
||||
codes.add(perm.code);
|
||||
}
|
||||
}
|
||||
return Array.from(codes);
|
||||
}
|
||||
|
||||
// ---- 用户管理 ----
|
||||
|
||||
async findAllUsers() {
|
||||
const users = await this.userRepo.find({
|
||||
relations: ['roles'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
return users.map(u => ({
|
||||
id: u.id,
|
||||
username: u.username,
|
||||
name: u.name,
|
||||
isActive: u.isActive,
|
||||
lastLoginAt: u.lastLoginAt,
|
||||
createdAt: u.createdAt,
|
||||
updatedAt: u.updatedAt,
|
||||
roles: u.roles?.map(r => ({ id: r.id, name: r.name })) || [],
|
||||
}));
|
||||
}
|
||||
|
||||
async createUser(dto: { username: string; password: string; name: string; roleIds?: number[] }) {
|
||||
const exists = await this.userRepo.findOne({ where: { username: dto.username } });
|
||||
if (exists) throw new Error('用户名已存在');
|
||||
const hash = await bcrypt.hash(dto.password, 10);
|
||||
const user = this.userRepo.create({ username: dto.username, passwordHash: hash, name: dto.name });
|
||||
if (dto.roleIds && dto.roleIds.length > 0) {
|
||||
user.roles = await this.roleRepo.findByIds(dto.roleIds);
|
||||
}
|
||||
await this.userRepo.save(user);
|
||||
return { message: '用户创建成功' };
|
||||
}
|
||||
|
||||
async updateUser(id: number, dto: { username?: string; name?: string; isActive?: boolean; roleIds?: number[] }) {
|
||||
const user = await this.userRepo.findOne({ where: { id }, relations: ['roles'] });
|
||||
if (!user) throw new Error('用户不存在');
|
||||
if (dto.username !== undefined && dto.username !== user.username) {
|
||||
const exists = await this.userRepo.findOne({ where: { username: dto.username } });
|
||||
if (exists) throw new Error('用户名已存在');
|
||||
user.username = dto.username;
|
||||
}
|
||||
if (dto.name !== undefined) user.name = dto.name;
|
||||
if (dto.isActive !== undefined) user.isActive = dto.isActive;
|
||||
if (dto.roleIds !== undefined) {
|
||||
user.roles = dto.roleIds.length > 0
|
||||
? await this.roleRepo.findByIds(dto.roleIds)
|
||||
: [];
|
||||
}
|
||||
await this.userRepo.save(user);
|
||||
return { message: '更新成功' };
|
||||
}
|
||||
|
||||
async resetPassword(id: number, newPassword: string) {
|
||||
const user = await this.userRepo.findOne({ where: { id } });
|
||||
if (!user) throw new Error('用户不存在');
|
||||
user.passwordHash = await bcrypt.hash(newPassword, 10);
|
||||
await this.userRepo.save(user);
|
||||
return { message: '密码已重置' };
|
||||
}
|
||||
|
||||
async deleteUser(id: number) {
|
||||
const user = await this.userRepo.findOne({ where: { id } });
|
||||
if (!user) throw new Error('用户不存在');
|
||||
if (user.username === 'admin') throw new Error('不能删除默认管理员');
|
||||
await this.userRepo.remove(user);
|
||||
return { message: '用户已删除' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user