引言
大家好,我是elk。在之前的文章中,我们已经完成了菜单管理和角色管理模块的开发。今天,我们将聚焦整个权限管理系统的核心------用户管理 。用户管理作为系统权限控制的最终载体,承担着连接角色、菜单和组织架构的重要功能。
项目结构与模块创建
模块快速生成
使用Nest CLI快速生成用户模块的基础结构:
bash
# 创建标准CRUD模板
nest g res user
该命令会自动生成控制器、服务、DTO等文件,位于 src/module/system/user
目录下,形成如下结构:
text
user/
├── dto/
│ ├── create-user.dto.ts
│ ├── update-user.dto.ts
│ └── list-user-dto.ts
├── entities/
│ └── user.entity.ts
├── user.controller.ts
└── user.service.ts
DTO设计与实体映射
DTO定义 (create-user.dto.ts):
typescript
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
export class CreateUserDto {
@ApiProperty({ description: '用户ID', required: false })
userId?: number;
@ApiProperty({ description: '用户名', required: true })
userName: string;
@ApiProperty({ description: '密码', required: true })
password: string;
@ApiProperty({ description: '部门ID' })
deptId?: string;
// 其他字段...
nickName?: string;
phone?: string;
email?: string;
avatar?: string;
sex?: number;
status?: number;
remark?: string;
}
实体映射 (user.entity.ts):
typescript
import { sys_user as User } from '@prisma/client';
export class UserEntity implements User {
user_id: number;
user_name: string;
password: string;
dept_id: string;
nick_name: string;
phone: string;
email: string;
avatar: string;
sex: number;
status: number;
remark: string;
created_at: Date;
updated_at: Date;
}
用户管理核心实现
接口设计全景
用户管理模块需要实现以下核心功能接口:
功能 | 接口类型 | 路径 | 参数 | 技术点 |
---|---|---|---|---|
新增用户 | POST | /create | CreateUserDto | 事务处理、关联创建 |
用户列表 | GET | /list | pageNum, pageSize | 分页优化、关联查询、敏感字段过滤、数据转换 |
用户详情 | GET | /:id | id | 关联加载、按需过滤、数据格式化 |
修改用户 | PUT | / | UpdateUserDto | 复杂事务、关联更新策略、部分更新 |
删除用户 | DELETE | /:id | id | 级联删除、原子操作 |
控制器实现
typescript
import {
Controller,
Get,
Post,
Body,
Put,
Param,
Delete,
Query,
} from '@nestjs/common';
import { UserService } from './user.service';
import {
ApiTags,
ApiOperation,
ApiBody,
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
@ApiTags('用户管理')
@Controller('/system/user')
export class UserController {
constructor(private readonly userService: UserService) {}
// 新增用户
@Post('create')
@ApiOperation({
summary: '用户管理-新增用户',
description: '创建新用户并分配角色/部门'
})
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
// 获取用户列表(分页)
@Get('/list')
@ApiOperation({
summary: '用户管理-获取用户列表',
description: '获取分页用户列表,包含关联角色和部门信息',
})
list(@Query() params: ListUserDto) {
return this.userService.findAll(params);
}
// 其他接口实现...
}
服务层核心逻辑
用户创建(事务处理)
typescript
async create(createUserDto: CreateUserDto) {
// 创建用户主记录
const user = await this.prisma.sys_user.create({
data: {
user_name: createUserDto.userName,
password: createUserDto.password, // 实际项目需加密存储
dept_id: createUserDto.deptId,
// 其他字段...
},
});
// 创建用户角色关联
const rolePromise = this.prisma.sys_user_role.create({
data: {
user_id: user.user_id,
role_id: createUserDto.roleIds,
},
});
// 创建用户部门关联(多对多)
const deptIds = createUserDto.deptId.split(',') || ['0'];
const deptPromise = this.prisma.sys_user_dept.createMany({
data: deptIds.map(deptId => ({
user_id: user.user_id,
dept_id: Number(deptId),
})),
});
// 事务处理 - 确保所有操作原子性
try {
await this.prisma.$transaction([rolePromise, deptPromise]);
return '用户创建成功';
} catch (error) {
throw new Error('用户创建失败: ' + error.message);
}
}
用户列表查询(关联数据处理)
typescript
async findAll({ pageNum, pageSize }: ListUserDto) {
const users = await this.prisma.sys_user.findMany({
skip: (pageNum - 1) * pageSize,
take: Number(pageSize),
omit: { password: true }, // 关键:排除密码字段
include: {
roles: {
select: { role: { select: { role_id: true, role_name: true } } }
},
depts: {
select: { dept: { select: { dept_id: true, dept_name: true } } }
},
},
});
// 数据结构转换
return users.map(user => ({
...user,
roles: user.roles.map(r => r.role),
depts: user.depts.map(d => d.dept)
}));
}
用户更新(复杂事务处理)
typescript
async update(updateUserDto: UpdateUserDto) {
// 更新用户角色关联
const rolePromise = this.prisma.sys_user_role.updateMany({
where: { user_id: updateUserDto.userId },
data: { role_id: updateUserDto.roleIds },
});
// 更新用户部门关联(先删后增)
const deptIds = updateUserDto.deptId.split(',');
const deleteDeptPromise = this.prisma.sys_user_dept.deleteMany({
where: { user_id: updateUserDto.userId },
});
const createDeptPromise = this.prisma.sys_user_dept.createMany({
data: deptIds.map(deptId => ({
user_id: updateUserDto.userId,
dept_id: Number(deptId),
})),
});
// 更新用户基本信息
const userPromise = this.prisma.sys_user.update({
where: { user_id: updateUserDto.userId },
data: { ...updateUserDto, updated_at: new Date() },
});
// 执行事务
try {
await this.prisma.$transaction([
rolePromise,
deleteDeptPromise,
createDeptPromise,
userPromise
]);
return '用户更新成功';
} catch (error) {
throw new Error('用户更新失败: ' + error.message);
}
}
开发经验与最佳实践
事务处理的正确姿势
在用户管理模块中,多个表(用户表、角色关联表、部门关联表)需要保持数据一致性。使用Prisma的事务处理($transaction
)是关键:
typescript
// 正确的事务处理方式
const transaction = this.prisma.$transaction([
operation1,
operation2,
operation3
]);
// 错误示例(会导致事务失效)
const transaction = this.prisma.$transaction([
await operation1, // 错误:不能使用await
operation2,
operation3
]);
经验总结:
- 事务内操作不能单独使用await
- 将需要事务保证的操作放入数组传递
- 事务失败时进行回滚
关联数据处理技巧
处理多对多关系时,Prisma的include
功能非常强大:
typescript
// 包含关联的角色和部门信息
include: {
roles: {
select: { role: true } // 获取关联的完整角色对象
},
depts: {
select: { dept: true } // 获取关联的完整部门对象
}
}
数据结构优化技巧:
typescript
// 转换前
{
roles: [{ role: { id: 1, name: 'admin' } }],
depts: [{ dept: { id: 2, name: '研发部' } }]
}
// 转换后(更简洁的结构)
{
roles: [{ id: 1, name: 'admin' }],
depts: [{ id: 2, name: '研发部' }]
}
安全注意事项
密码安全:
bash
- 永远不要在响应中包含密码字段
- 使用`omit: { password: true }`排除密码
- 存储时使用bcrypt等算法加密
权限控制:
less
``` typescript
// 在控制器中添加权限守卫
@UseGuards(JwtAuthGuard, PermissionsGuard)
@Permissions(PermissionContant.USER_MANAGE)
@Post('create')
createUser(@Body() dto: CreateUserDto) {
// ...
}
```
输入验证:
typescrit
// 使用class-validator增强DTO验证
import { IsEmail, IsStrongPassword } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsStrongPassword({
minLength: 8,
minLowercase: 1,
minUppercase: 1,
minNumbers: 1
})
password: string;
}
性能优化点
分页查询优化:
typescript
// 使用cursor-based分页提升性能
async findAll(cursor: number, limit: number) {
return this.prisma.sys_user.findMany({
take: limit,
skip: cursor ? 1 : 0,
cursor: cursor ? { user_id: cursor } : undefined,
});
}
批量操作优化:
typescript
// 使用createMany优化批量插入
await this.prisma.sys_user_dept.createMany({
data: deptIds.map(deptId => ({
user_id: userId,
dept_id: Number(deptId)
})),
});
Redis缓存应用:
typescript
// 用户详情加入缓存
async findOne(id: number) {
const cacheKey = `user:${id}`;
const cached = await this.redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await this.prisma.sys_user.findUnique({
where: { user_id: id }
});
await this.redis.set(cacheKey, JSON.stringify(user), 'EX', 300);
return user;
}
踩坑与解决方案
多对多关系更新陷阱
问题场景 :
更新用户部门关系时,需要先删除旧关系再创建新关系
解决方案:
typescript
// 1. 删除所有旧关联
const deleteOp = this.prisma.sys_user_dept.deleteMany({
where: { user_id: userId }
});
// 2. 创建所有新关联
const createOp = this.prisma.sys_user_dept.createMany({
data: newDeptIds.map(deptId => ({
user_id: userId,
dept_id: deptId
}))
});
// 3. 在事务中执行
await this.prisma.$transaction([deleteOp, createOp]);
密码字段处理
问题场景:
更新用户信息时,密码字段需要特殊处理
解决方案:
typescript
async updateUser(dto: UpdateUserDto) {
const data: any = { ...dto };
// 如果密码字段存在且不为空,则处理
if (dto.password) {
data.password = await bcrypt.hash(dto.password, 10);
} else {
delete data.password; // 不更新密码
}
return this.prisma.sys_user.update({
where: { user_id: dto.userId },
data
});
}
数据一致性挑战
问题场景:
删除用户时需要同步删除所有关联数据
解决方案:
typescript
async remove(id: number) {
return this.prisma.$transaction([
// 1. 删除用户角色关联
this.prisma.sys_user_role.deleteMany({ where: { user_id: id } }),
// 2. 删除用户部门关联
this.prisma.sys_user_dept.deleteMany({ where: { user_id: id } }),
// 3. 删除用户本身
this.prisma.sys_user.delete({ where: { user_id: id } })
]);
}
总结与展望
通过本文,我们系统性地实现了用户管理模块的核心功能,涵盖了:
- 模块化结构设计与代码组织
- RESTful接口规范实现
- 复杂关联数据处理技巧
- 数据库事务的合理应用
- 安全性与性能优化实践
值得注意的最佳实践:
- 使用DTO进行数据验证和转换
- 敏感字段(如密码)特殊处理
- 事务处理保证数据一致性
- 合理的关联数据加载策略
- 分页查询的性能优化