NestJS实战-后端开发-用户及权限模块
本文介绍 NestJS 实战的用户及权限模块:用户表和权限角色表的构建、权限路由守卫、Roles装饰器、用户接口CRUD操作、服务中如何操作数据库
供自己以后查漏补缺,也欢迎同道朋友交流学习。
引言
项目的后端全局配置安装,上一章已经介绍过了,本章主要介绍下最基础的模块:用户管理、权限管理。
账号管理
我们先做最基础的账号功能,有了账号就可以进行登录了。
用户表 user
我们先进行用户账号表结构设计:
- id (主键)
- account (账号)
- username (用户名)
- passwordHash (密码哈希)
- roleId (权限id 外键 关联role表)
- roleType (权限类型 外键 关联role表)
- roleWeight (权限重量 外键 关联role表)
- roleName (权限名称 外键 关联role表)
- createdBy (创建人id)
- createdByAccount (创建人账号)
- createdTime (创建时间)
- updatedBy (更新人Id)
- updatedByAccount (更新人账号)
- updatedTime (更新时间)
- isDeleted (是否删除 0未删除 1已删除)
用户模块开发
使用快捷键新建 user 模块:
nest g res user
# 默认选择 REST API
根据用户表结构配置 user.entity.ts:
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
import { InternalServerErrorException } from '@nestjs/common';
@Entity()
export class User {
// 主键 唯一且自增长
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 50, unique: true })
account: string;
@Column({ type: 'varchar', length: 50 })
username: string;
@Column()
passwordHash: string; // 存储密码哈希
@Column({ default: 4 }) // 默认访客
roleId: number; // 外键,关联到角色表的id
@Column({ default: 'viewer' }) // 默认viewer
roleType: string; // 外键,关联到角色表的type
@Column({ default: 3 }) // 默认权重 3
roleWeight: number; // 外键,关联到角色表的weight
@Column({ default: '访客' }) // 默认访客
roleName: string; // 外键,关联到角色表的weight
@Column({ default: 1 })
createdBy: number; // 创建人id
@Column()
createdByAccount: string;
@CreateDateColumn()
createdTime: Date;
@Column({ default: 1 })
updatedBy: number; // 更新人Id
@Column()
updatedByAccount: string;
@UpdateDateColumn()
updatedTime: Date;
@Column({ default: 0 })
isDeleted: number; // 是否删除,0表示未删除,1表示已删除
// 可以添加一个方法来设置密码哈希
async setPasswordHash(password: string): Promise<string> {
try {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
} catch (error) {
throw new InternalServerErrorException('设置密码失败');
}
}
// 可以添加一个方法来验证密码
async validatePassword(password: string): Promise<boolean> {
return bcrypt.compare(password, this.passwordHash);
}
}
从上面代码中可以看到,我们设置密码需要安装 bcrypt 来进行设置密码哈希
npm install bcrypt
同时还要关联角色权限表,角色权限表我不想扩展了,就写死4个角色来管理账号,我们的设计如下:
- 系统管理员:
systemAdmin控制系统的一切 - 管理员:
admin除了不能删除修改systemAdmin,目前其他操作都可以 - 用户:
user不能对账号管理进行增删改操作,其他都可以 - 访客:
viewer不能访问账号管理页面,其他页面也仅可以查看,不能增删改
权限角色表 role
表结构如下
- id (主键)
- type (类型)
- name (名称)
- weight (权重)
role.entity.ts的代码如下:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Role {
@PrimaryGeneratedColumn()
id: number;
@Column()
type: string;
@Column()
name: string;
@Column()
weight: number;
}
现在我们使用 SQL 语言进行 4 个角色的插入以供后面使用:
INSERT INTO role (type, name, weight)
VALUES
('systemAdmin', '系统管理员', 0),
('admin', '管理员', 1),
('user', '用户', 2),
('viewer', '访客', 3);
权限角色守卫 RolesGuard
在 src/user 目录下新建一个角色守卫roles.guard.ts去监听路由对用户的角色进行判断他有没有操作权限:
// src/user/roles.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
Logger,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Repository } from 'typeorm';
import { Role } from './entities/role.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class RolesGuard implements CanActivate {
private logger = new Logger('RolesGuard');
constructor(
private reflector: Reflector,
@InjectRepository(Role) private readonly roleRepository: Repository<Role>,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.get<string[]>(
'roles',
context.getHandler(),
);
this.logger.log('@@@@ 路由需要的权限', requiredRoles);
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
// 查询用户的权限
if (user.roleId) {
const roles = await this.roleRepository.findOneBy({ id: user.roleId });
this.logger.log('@@@@ 用户权限', roles);
if (requiredRoles.includes(roles.type)) {
return true;
} else {
throw new ForbiddenException('用户没有权限进行操作');
return false;
}
} else {
this.logger.log('@@@@ 没有用户roleId', user);
throw new ForbiddenException('没有用户roleId');
return false;
}
}
}
Roles装饰器
再新建一个装饰器配合传递权限数据:
// src/user/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
使用方法如下:
// 其他代码...
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
@Controller('user')
@UseGuards(RolesGuard)
@ApiTags('用户及角色管理')
export class UserController {
// 其他代码...
@Post()
@Roles('systemAdmin', 'admin')
create(@Body() createUserDto: CreateUserDto, @Req() req) {
// 这里就表示只允许系统管理员和管理员新建账户
// 其他代码...
}
// 其他代码...
}
定义用户数据传输对象
新建用户数据传输对象用于控制器中进行类型定义、格式校验,实现如下功能:
- 安装
class-validator和class-transformer用于后续格式校验 - 提供用户的增删改查相关数据传输对象
创建用户对象 CreateUserDto
create-user.dto.ts 代码如下:
import { ApiProperty } from '@nestjs/swagger';
import {
IsNotEmpty,
IsString,
IsEmail,
IsNumber,
IsOptional,
Matches,
} from 'class-validator';
export class CreateUserDto {
@IsEmail({}, { message: '账号必须是邮箱格式' })
@IsString({ message: '账号必须是字符串' })
@IsNotEmpty({ message: '账号不能为空' })
@ApiProperty({
description: '账号(邮箱格式)',
example: 'niunai@niunai.com',
})
account: string;
@IsString({ message: '用户名必须是字符串' })
@IsNotEmpty({ message: '用户名不能为空' })
@ApiProperty({ description: '用户名', example: '牛奶' })
username: string;
@Matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,16}$/, {
message: '请输入8-16位数字+字母的密码',
})
@IsString({ message: '密码必须是字符串' })
@IsNotEmpty({ message: '密码不能为空' })
@ApiProperty({ description: '密码', example: 'admin123' })
password: string;
@ApiProperty({
description: '角色id',
example: 4,
required: false,
})
@IsNumber()
@IsOptional()
roleId?: number = 4;
}
查询用户对象 ListUserDto
list-user.dto.ts 代码如下:
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsOptional } from 'class-validator';
export class ListUserDto {
@ApiProperty({ description: '角色id', example: 4 })
@IsOptional()
roleId?: null | number = 4;
@ApiProperty({
description: '账号(邮箱格式)',
example: 'niunai@niunai.com',
})
@IsOptional()
account?: null | string;
@ApiProperty({ description: '用户名', example: '牛奶' })
@IsOptional()
username?: null | string;
@ApiProperty({ description: '账号id', example: 1 })
@IsOptional()
id?: null | number;
@ApiProperty({ description: '页码', example: 1 })
@IsNotEmpty({ message: 'pageNum不能为空' })
pageNum: number = 1;
@ApiProperty({ description: '每页查询数量', example: 10 })
@IsNotEmpty({ message: 'pageSize不能为空' })
pageSize: number = 10;
}
更新用户对象 UpdateUserDto
update-user.dto.ts 代码如下:
import { OmitType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends OmitType(CreateUserDto, [
'account', // account不允许修改
'password', // password不允许修改
] as const) {}
重置密码对象 ResetPasswordDto
reset-password.dto.ts 代码如下:
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Matches } from 'class-validator';
export class ResetPasswordDto {
@IsNotEmpty({ message: 'id不能为空' })
@ApiProperty({ description: '账号id', example: 5 })
id: number;
@Matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,16}$/, {
message: '请输入8-16位数字+字母的密码',
})
@IsNotEmpty({ message: '新密码不能为空' })
@ApiProperty({ description: '新密码', example: 'admin123' })
newPassword: string;
@IsNotEmpty({ message: '确认密码不能为空' })
@ApiProperty({ description: '确认密码', example: 'admin123' })
confirmPassword: string;
}
更新密码 UpdatePasswordDto
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Matches } from 'class-validator';
export class UpdatePasswordDto {
@Matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,16}$/, {
message: '请输入8-16位数字+字母的密码',
})
@IsNotEmpty({ message: '旧密码不能为空' })
@ApiProperty({ description: '旧密码', example: 'admin123' })
oldPassword: string;
@Matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,16}$/, {
message: '请输入8-16位数字+字母的密码',
})
@IsNotEmpty({ message: '新密码不能为空' })
@ApiProperty({ description: '新密码', example: 'admin123' })
newPassword: string;
@IsNotEmpty({ message: '确认密码不能为空' })
@ApiProperty({ description: '确认密码', example: 'admin123' })
confirmPassword: string;
}
用户CRUD相关操作 UserController
在控制器中编写用户增删改查相关操作,这个也是给前端的接口定义,我们主要实现如下几个接口:
| 接口 | 请求 | 定义 | 描述 | 备注 |
|---|---|---|---|---|
/user/roleList | GET | 查询角色列表 | 获取所有角色列表,用来做角色权限管理 | 写死的4个角色,后期不会增改 |
/user | Post | 创建用户 | 创建用户 | account是唯一值,不可修改 |
/user | Get | 查询用户列表 | 分页查询用户列表 | 根据query、pageSize、PageNum分页查询用户列表 |
/user/{id} | Get | 查询用户 | 根据id查询用户 | - |
/user/{id} | Patch | 更新用户 | 根据id更新用户 | account不能修改 |
/user/{id} | Delete | 删除用户 | 根据id删除用户 | - |
/user | Delete | 删除用户 | 根据ids删除用户 | - |
/user/userSearchExport | Post | 用户表查询导出 | 用户表查询导出 | 根据查询条件导出excel |
/user/userSelectExport | Post | 用户表勾选导出 | 用户表勾选导出 | 根据勾选的ids导出excel |
/user/resetPassword | Post | 用户重置密码 | 用户重置密码 | 重置密码 |
/user/userUpdatePassword | Post | 用户修改密码 | 用户修改密码 | 用户修改密码 |
user.controller.ts 代码如下:
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
Res,
BadRequestException,
HttpStatus,
Logger,
Req,
UseGuards,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import {
ApiBody,
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { ListUserDto } from './dto/list-user.dto';
import { ExcelService } from 'src/common/excel/excel.service';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { MetricsService } from 'src/common/metrics/metrics.service';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
import { UpdatePasswordDto } from './dto/update-password.dto';
@Controller('user')
@UseGuards(RolesGuard)
@ApiTags('用户及角色管理')
export class UserController {
private logger = new Logger('UserController');
constructor(
private readonly userService: UserService,
private readonly excelService: ExcelService,
private readonly metricsService: MetricsService,
) {}
@Get('roleList')
@ApiOperation({
summary: '查询角色列表',
description: '获取所有角色列表,用来做角色权限管理',
})
roleList() {
return this.userService.roleList();
}
@Post()
@Roles('systemAdmin', 'admin')
@ApiOperation({
summary: '创建用户',
description: '创建用户',
})
@ApiBody({ type: CreateUserDto })
@ApiResponse({ status: 200, description: '创建成功' })
create(@Body() createUserDto: CreateUserDto, @Req() req) {
// 服务监控代码-与业务无关
this.metricsService.incrementRequestCount('Post', '/user');
return this.userService.create(createUserDto, req);
}
@Get()
@ApiOperation({
summary: '查询用户列表',
description: '查询用户列表',
})
@ApiQuery({ name: 'query', type: ListUserDto })
@ApiResponse({ status: 200, description: '查询用户成功' })
findAll(@Query() query: ListUserDto) {
return this.userService.findAllByPage(query);
}
@Get(':id')
@ApiOperation({
summary: '查询用户',
description: '根据id查询用户',
})
@ApiParam({ name: 'id', description: '账号id' })
@ApiResponse({ status: 200, description: '查询用户成功' })
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id')
@Roles('systemAdmin', 'admin')
@ApiOperation({
summary: '更新用户',
description: '根据id更新用户',
})
@ApiParam({ name: 'id', description: '账号id' })
@ApiBody({ description: '账号信息', type: UpdateUserDto })
@ApiResponse({ status: 200, description: '更新成功' })
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
@Req() req,
) {
return this.userService.update(+id, updateUserDto, req);
}
@Delete(':id')
@Roles('systemAdmin', 'admin')
@ApiOperation({
summary: '删除用户',
description: '根据id删除用户',
})
@ApiParam({ name: 'id', description: '账号id' })
@ApiResponse({ status: 200, description: '删除成功' })
remove(@Param('id') id: string, @Req() req) {
return this.userService.softDeleteUser(+id, req);
}
@Delete()
@Roles('systemAdmin', 'admin')
@ApiOperation({
summary: '批量删除用户',
description: '根据ids删除用户',
})
@ApiBody({ description: '账号id列表' })
@ApiResponse({ status: 200, description: '批量删除用户成功' })
removeUsers(@Body('ids') ids: number[], @Req() req) {
this.logger.log('@@@ ids', ids);
return this.userService.softDeleteUsers(ids, req);
}
@Post('/userSearchExport')
@Roles('systemAdmin', 'admin', 'user')
@ApiOperation({
summary: '用户表查询导出',
description: '用户表查询导出',
})
@ApiBody({ type: ListUserDto })
@ApiResponse({ status: 200, description: '用户表查询导出成功' })
async userSearchExport(@Body() body: ListUserDto, @Res() res): Promise<void> {
const userList = await this.userService.findAll(body);
if (!userList?.length) {
throw new BadRequestException('导出数据为空');
}
try {
// 导出为 Excel 文件
const filename = encodeURIComponent('用户表.xlsx');
const buffer = this.excelService.exportAsExcelFile(userList);
this.logger.log('@@@ buffer', buffer);
this.logger.log('@@@ filename', filename);
// 设置响应头
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
// 发送文件
res.status(HttpStatus.OK).end(buffer);
} catch (error) {
this.logger.error('@@@ 用户表查询导出接口调用失败:', error);
throw new BadRequestException('用户表查询导出接口调用失败');
}
}
@Post('/userSelectExport')
@Roles('systemAdmin', 'admin', 'user')
@ApiOperation({
summary: '用户表勾选导出',
description: '用户表勾选导出',
})
@ApiBody({ description: '账号id列表' })
@ApiResponse({ status: 200, description: '用户表勾选导出成功' })
async userSelectExport(
@Body('ids') ids: number[],
@Res() res,
): Promise<void> {
const userList = await this.userService.findByIds(ids);
if (!userList?.length) {
throw new BadRequestException('导出数据为空');
}
try {
// 导出为 Excel 文件
const filename = encodeURIComponent('用户勾选表.xlsx');
const buffer = this.excelService.exportAsExcelFile(userList);
this.logger.log('@@@ buffer', buffer);
this.logger.log('@@@ filename', filename);
// 设置响应头
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
res.setHeader('Content-Disposition', `attachment; filename=${filename}`);
// 发送文件
res.status(HttpStatus.OK).end(buffer);
} catch (error) {
this.logger.error('@@@ 用户表勾选导出接口调用失败:', error);
throw new BadRequestException('用户表勾选导出接口调用失败');
}
}
@Post('/resetPassword')
@Roles('systemAdmin', 'admin')
@ApiOperation({
summary: '用户重置密码',
description: '用户重置密码',
})
@ApiBody({ type: ResetPasswordDto })
@ApiResponse({ status: 200, description: '用户重置密码成功' })
async resetPassword(@Body() body: ResetPasswordDto, @Req() req) {
return this.userService.resetPassword(body, req);
}
@Post('/userUpdatePassword')
@ApiOperation({
summary: '用户修改密码',
description: '用户修改密码',
})
@ApiBody({ type: UpdatePasswordDto })
@ApiResponse({ status: 200, description: '用户修改密码成功' })
async userUpdatePassword(@Body() body: UpdatePasswordDto, @Req() req) {
return this.userService.userUpdatePassword(body, req);
}
}
用户服务 UserService
数据库的操作主要是在 Service 里做,UserService 主要是给 UserController 提供服务,也可以提供给其他模块调用:
import {
BadRequestException,
Injectable,
InternalServerErrorException,
Logger,
Req,
} from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Like, Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { Role } from './entities/role.entity';
import { ListUserDto } from './dto/list-user.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import {
removeUnnecessaryData,
setCreatedUser,
setDeletedUser,
setUpdatedUser,
} from 'src/utils';
import { UpdatePasswordDto } from './dto/update-password.dto';
@Injectable()
export class UserService {
private logger = new Logger('UserService');
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
@InjectRepository(Role) private readonly roleRepository: Repository<Role>,
) {}
// 获取用户权限列表
roleList() {
return this.roleRepository.find();
}
async findRoleById(id: number): Promise<Role> {
if (!id) {
throw new BadRequestException('id必填');
}
return await this.roleRepository.findOne({
where: { id },
});
}
async create(createUserDto: CreateUserDto, @Req() req) {
this.logger.log('@@@@ 创建用户参数:', createUserDto);
// 检查roleId是否存在
const role = await this.findRoleById(createUserDto.roleId || 4);
if (!role) {
this.logger.warn(`id为${createUserDto.roleId}的角色没有找到`);
throw new BadRequestException(
`id为${createUserDto.roleId}的角色没有找到`,
);
}
// 检查account是否已存在
const account = await this.userRepository.findOne({
where: { account: createUserDto.account },
});
if (account) {
this.logger.warn(`账号${createUserDto.account}已存在,不能重复创建`);
throw new BadRequestException(
`账号${createUserDto.account}已存在,不能重复创建`,
);
}
// 创建用户实体
const user = this.userRepository.create(createUserDto);
user.roleType = role.type;
user.roleWeight = role.weight;
user.roleName = role.name;
// 设置密码哈希
const passwordHash: string = await user.setPasswordHash(
createUserDto.password,
);
user.passwordHash = passwordHash;
const createdUser = setCreatedUser(req, user);
this.logger.log('@@@@ 创建用户实体 createdUser', createdUser);
try {
return await this.userRepository.save(createdUser);
} catch (error) {
this.logger.error('@@@@ 新建账号失败:', error);
throw new InternalServerErrorException('新建账号失败');
}
}
async findAll(query: ListUserDto) {
try {
const queryParams = {
account: query?.account || null,
username: query.username ? Like(`%${query.username}%`) : null,
roleId: query?.roleId || null,
isDeleted: 0,
};
const data = await this.userRepository.find({
where: queryParams,
});
return removeUnnecessaryData(data);
} catch (error) {
this.logger.error('@@@@ 账号列表查询失败:', error);
throw new InternalServerErrorException('账号列表查询失败');
}
}
async findAllByPage(query: ListUserDto) {
try {
const queryParams = {
id: query?.id || null,
account: query?.account ? Like(`%${query.account}%`) : null,
username: query.username ? Like(`%${query.username}%`) : null,
roleId: query?.roleId || null,
isDeleted: 0,
};
const [data, total] = await this.userRepository.findAndCount({
where: queryParams,
order: {
id: 'DESC',
},
skip: (query.pageNum - 1) * query.pageSize,
take: query.pageSize,
});
return {
list: removeUnnecessaryData(data),
pageNum: Number(query.pageNum),
pageSize: Number(query.pageSize),
total,
};
} catch (error) {
this.logger.error('@@@@ 账号列表查询失败:', error);
throw new InternalServerErrorException('账号列表查询失败');
}
}
async findOne(id: number) {
if (!id) {
throw new BadRequestException('id必填');
}
try {
const data = await this.userRepository.findOne({
where: { id, isDeleted: 0 },
});
delete data.passwordHash;
delete data.isDeleted;
return data;
} catch (error) {
this.logger.error('@@@@ 账号查询失败:', error);
throw new InternalServerErrorException('账号查询失败');
}
}
async findByIds(ids: number[]): Promise<User[]> {
try {
const data = await this.userRepository.find({
where: { id: In(ids), isDeleted: 0 },
});
return removeUnnecessaryData(data);
} catch (error) {
this.logger.error('@@@@ 账号批量查询失败:', error);
throw new InternalServerErrorException('账号批量查询失败');
}
}
// 更新用户
async update(id: number, updateUserDto: UpdateUserDto, @Req() req) {
if (!id) {
throw new BadRequestException('id必填');
}
const user = await this.userRepository.findOneBy({
id,
});
if (req.user.roleWeight >= user.roleWeight && req.user.roleWeight !== 0) {
throw new BadRequestException(
`当前角色权限过低,无法对${user.account}进行该操作`,
);
}
const role = await this.roleRepository.findOneBy({
id: updateUserDto.roleId,
});
user.roleType = role.type;
user.roleWeight = role.weight;
user.roleName = role.name;
try {
// 设置更新用户信息
const updatedUser = setUpdatedUser(req, user);
this.logger.log('@@@ 更新用户 updatedUser', updatedUser);
return await this.userRepository.update(id, updatedUser);
} catch (error) {
this.logger.error('@@@@ 更新账号失败:', error);
throw new InternalServerErrorException('更新账号失败');
}
}
// 重置登录密码
async resetPassword(body: ResetPasswordDto, @Req() req) {
const user = await this.userRepository.findOneBy({
id: body.id,
});
if (req.user.roleWeight >= user.roleWeight && req.user.roleWeight !== 0) {
throw new BadRequestException(
`当前角色权限过低,无法对${user.account}进行该操作`,
);
}
if (body.confirmPassword !== body.newPassword) {
throw new BadRequestException('新密码与确认密码不一致');
}
try {
// 设置密码哈希
const passwordHash: string = await user.setPasswordHash(body.newPassword);
user.passwordHash = passwordHash;
// 设置更新用户信息
const updatedUser = setUpdatedUser(req, user);
this.logger.log('@@@ 重置登录密码 updatedUser', updatedUser);
return await this.userRepository.update(body.id, updatedUser);
} catch (error) {
this.logger.error('@@@@ 更新密码失败:', error);
throw new InternalServerErrorException('更新密码失败');
}
}
// 更新密码
async userUpdatePassword(body: UpdatePasswordDto, @Req() req) {
// 旧密码和新密码比较
if (body.oldPassword === body.newPassword) {
throw new BadRequestException('旧密码不能和新密码相同');
}
// 新密码和确认密码比较
if (body.confirmPassword !== body.newPassword) {
throw new BadRequestException('新密码与确认密码不一致');
}
// 调用数据库验证用户旧密码
const user: User = await this.userRepository.findOneBy({
id: req.user.userId,
});
const isPassword = await user.validatePassword(body.oldPassword);
if (!isPassword) {
throw new BadRequestException('旧密码错误,请确认后重新输入');
}
try {
this.logger.log('@@@@ 修改账户密码 body', body);
// 修改账户密码
const passwordHash: string = await user.setPasswordHash(body.newPassword);
user.passwordHash = passwordHash;
// 设置更新用户信息
const updatedUser = setUpdatedUser(req, user);
return await this.userRepository.update(user.id, updatedUser);
} catch (error) {
this.logger.error('@@@@ 修改账户密码失败:', error);
throw new InternalServerErrorException('修改账户密码失败');
}
}
// 软删除单个用户
async softDeleteUser(id: number, @Req() req) {
if (!id) {
throw new BadRequestException('id必填');
}
const user = await this.userRepository.findOneBy({
id,
});
if (req.user.roleWeight >= user.roleWeight && req.user.roleWeight !== 0) {
throw new BadRequestException(
`当前角色权限过低,无法对${user.account}进行该操作`,
);
}
try {
const removedUser = setDeletedUser(req, user);
this.logger.log('@@@ 软删除单个用户 removedUser', removedUser);
return await this.userRepository.update(id, removedUser);
} catch (error) {
this.logger.error('@@@@ 删除账号失败:', error);
throw new InternalServerErrorException('删除账号失败');
}
}
// 软删除批量用户
async softDeleteUsers(ids: number[], @Req() req) {
for (const id of ids) {
const user = await this.userRepository.findOneBy({
id,
});
if (req.user.roleWeight >= user.roleWeight && req.user.roleWeight !== 0) {
throw new BadRequestException(
`当前角色权限过低,无法对${user.account}进行该操作`,
);
break;
}
}
try {
const user = new User();
const removedUser = setDeletedUser(req, user);
this.logger.log('@@@ 软删除批量用户 removedUser', removedUser);
return await this.userRepository.update(ids, removedUser);
} catch (error) {
this.logger.error('@@@@ 批量删除账号失败:', error);
throw new InternalServerErrorException('批量删除账号失败');
}
}
}
用户模型 UserModule
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Role } from './entities/role.entity';
import { ExcelModule } from 'src/common/excel/excel.module';
import { MetricsService } from 'src/common/metrics/metrics.service';
@Module({
imports: [TypeOrmModule.forFeature([User, Role]), ExcelModule],
controllers: [UserController],
providers: [UserService, MetricsService],
exports: [UserService], // 导出UserService以便其他模块可以使用
})
export class UserModule {}
用户管理功能接口完成
目前完成了11个用户相关的接口
实战合集地址
- NestJS实战-产品需求规划
- NestJS实战-前端搭建
- NestJS实战-后端开发-全局配置
- NestJS实战-后端开发-用户及权限模块
- NestJS实战-后端开发-文章专栏功能模块
- NestJS实战-前后端联调
- NestJS实战-系统总结