NestJS 用户注册与登录
流程图
User 模块
利用 emial 模块 发送验证码
利用 bcrypt 模块 加密密码 实现用户注册
利用 passport 模块 实现用户登录
利用 rbac 实现权限分级
创建 User 模块
- 前面已经按照
最终流程汇总完成架构设计
创建 user 模块
bash
nest g resource user修改 user.module.ts
ts
import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
// 增加Excel 动态模块
import { ExcelModule } from "../../commonModules/excel/excel.module";
// 增加上传模块
import { UploadModule } from "../../commonModules/upload/upload.module";
// 增加邮箱模块
import { EmailModule } from "../../commonModules/email/email.module";
// 增加加密解密模块
import { CryptoModule } from "../../commonModules/crypto/crypto.module";
@Module({
imports: [
ExcelModule.forRoot({ name: "testdemoexcel" }),
UploadModule,
EmailModule,
CryptoModule,
],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}user 模块下面的 dto
- user/dto/login-user.dto.ts
ts
import { IsNotEmpty } from "class-validator";
/**
* 登录用户数据传输对象(DTO)
* 用于验证和传输登录相关的用户数据
*/
export class LoginUserDto {
/**
* 用户名属性
* 使用class-validator进行验证,确保用户名不为空
* @type {string}
*/
@IsNotEmpty({
message: "用户名不能为空",
})
userName: string;
/**
* 用户ID属性
* 使用class-validator进行验证,确保用户ID不为空
* @type {string}
*/
@IsNotEmpty({
message: "id不能为空",
})
userId: string;
}- user/dto/register-user.dto.ts
ts
import { IsEmail, IsNotEmpty, MinLength, IsPhoneNumber } from "class-validator";
export class RegisterUserDto {
/**
* 用户名字段
* 使用class-validator进行验证,确保用户名不为空
*/
@IsNotEmpty({
message: "用户名不能为空",
})
username: string;
/**
* 昵称字段
* 使用class-validator进行验证,确保昵称不为空
*/
@IsNotEmpty({
message: "昵称不能为空",
})
nick_name: string;
/**
* 密码字段
* 使用class-validator进行验证,确保密码不为空且长度不少于6位
*/
@IsNotEmpty({
message: "密码不能为空",
})
@MinLength(6, {
message: "密码不能少于 6 位",
})
password: string;
/**
* 邮箱字段
* 使用class-validator进行验证,确保邮箱不为空且格式正确
*/
@IsNotEmpty({
message: "邮箱不能为空",
})
@IsEmail(
{},
{
message: "不是合法的邮箱格式",
}
)
email: string;
/**
* 验证码字段
* 必填字段,用于验证用户注册的真实性和安全性
* 使用class-validator进行验证,确保验证码不为空
*/
@IsNotEmpty({
message: "验证码不能为空",
})
captcha: string;
/**
* 手机号字段
* 使用class-validator进行验证,确保手机号不为空且格式正确
*/
@IsNotEmpty({
message: "手机号不能为空",
})
@IsPhoneNumber("CN", {
message: "请输入正确的手机号",
})
phone_number: string;
/**
* 头像字段
* 使用class-validator进行验证,确保头像不为空
*/
@IsNotEmpty({
message: "头像不能为空",
})
head_pic: string;
}
/**
* 发送邮件的数据传输对象(DTO)类
* 用于验证和传输发送邮件所需的数据
* 使用TypeScript类实现,结合class-validator进行数据验证
*/
export class SendEmailDto {
/**
* 邮箱地址
* @example user@example.com
*/
@IsNotEmpty({
message: "用户名不能为空",
})
@IsEmail(
{},
{
message: "请输入正确的邮箱",
}
)
email: string;
}user 模块下面的 entities
- user/entities/Loginuser.entity.ts
ts
export class LoginuserEntity {
userName: string;
userId: string;
}- user/entities/Permissions.entity.ts
ts
export class PermissionsEntity {
code: string;
description: string;
}- user/entities/Registeruser.entity.ts
ts
export class RegisteruserEntity {
username: string;
nick_name: string;
password: string;
email: string;
captcha: string;
head_pic: string;
phone_number: string;
is_admin?: number;
is_frozen?: number;
user_roles?: any;
}- user/entities/Rules.entity.ts
ts
export class RulesEntity {
name: string;
roles_permissions: any;
}user 模块下面的 guard
- user/guard/userregister.guard.ts (用于验证验证码)
ts
import {
Injectable,
CanActivate,
ExecutionContext,
Inject,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { GlobalGuardException } from "../../../common/globalguard.exception";
// 调用redis服务
import { RedisService } from "../../../commonModules/redis/redis.service";
@Injectable()
export class UserRegisterGuard implements CanActivate {
@Inject()
private readonly redisService: RedisService;
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// 获取到验证码 去 redis 中验证
const { captcha, email } = request.body;
if (captcha) {
// 有 就去redis中验证
const redisCaptcha = await this.redisService.getstr(`captcha_${captcha}`);
if (redisCaptcha === email) {
return true;
} else {
throw new GlobalGuardException("验证码无效");
}
} else {
throw new GlobalGuardException("请输入验证码");
}
}
}user 模块下面的 controller
- user/user.controller.ts
ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
Req,
} from "@nestjs/common";
import { UserService } from "./user.service";
// 引入验证机制
import { AuthGuard } from "@nestjs/passport";
// 引入DTO
import { RegisterUserDto, SendEmailDto } from "./dto/register-user.dto";
import { LoginUserDto } from "./dto/login-user.dto";
// 引入守卫
import { UserRegisterGuard } from "../user/guard/userregister.guard";
import { ByGuardpass } from "src/common/guard_pass.decorator";
import { RuleGuard } from "../../common/rule.guard";
// 引入装饰器
import { RBAC } from "../../common/rbac.decorator";
@Controller({
path: "user",
version: ["1"],
})
export class UserController {
constructor(private readonly userService: UserService) {}
// 发送邮件
/**
*
* @api {POST} /v1/user/sendEmail 发送邮件
* @apiName 发送邮件
* @apiGroup user
* @apiVersion 0.0.0
*
*
* @apiBody {String} email 邮箱地址
*
* @apiSuccess {Object[]} response 响应数据
* @apiSuccess {Number} response.code 200
* @apiSuccess {String} response.message 操作成功
* @apiSuccess {Object} response.data 发送成功
*
* @apiParamExample {type} Request-Example:
* {
* email : 26650599@qq.com
* }
*
*
* @apiSuccessExample {type} Success-Response:
* {
* code : 200
* message : 操作成功
* data : 发送成功
* }
*
*
*/
@Post("sendEmail")
@ByGuardpass()
sendEmail(@Body() body: SendEmailDto) {
const result = this.userService.sendEmail(body.email);
return result;
}
// 注册
/**
*
* @api {POST} /v1/user/register 注册
* @apiName 注册
* @apiGroup user
* @apiVersion 0.0.0
*
*
* @apiBody {String} username 用户名
* @apiBody {String} nick_name 昵称
* @apiBody {String} password 密码
* @apiBody {String} email 邮箱
* @apiBody {String} captcha 验证码
* @apiBody {String} phone_number 手机号
* @apiBody {String} head_pic 头像
* @apiSuccess {Object[]} response 响应数据
* @apiSuccess {Number} response.code 200
* @apiSuccess {String} response.message 操作成功
* @apiSuccess {Object} response.data 返回数据
* @apiSuccess {String} response.data.token 生成token
*
* @apiParamExample {type} Request-Example:
* {
* email : 26650599@qq.com
* }
*
*
* @apiSuccessExample {type} Success-Response:
* {
* token : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJOYW1lIjoiYWRtaW4iLCJpYXQiOjE3NjY5MTQxNTksImV4cCI6MTc2NjkxNzc1OX0.LUYrzPWzh6OXF-tCiV-euGvIjrHFxsbbE6092WMh894
* }
*
*
*/
@Post("register")
@UseGuards(UserRegisterGuard)
@ByGuardpass()
register(@Body() body: RegisterUserDto) {
const result = this.userService.register(body);
return result;
}
// 登录
@ByGuardpass()
@UseGuards(AuthGuard("local"))
@Post("login")
async login(@Req() req: any) {
//获取到user信息知道是谁了
console.log(req.user);
const result: LoginUserDto = {
userName: req.user.username,
userId: req.user.id,
};
return this.userService.login({
userName: result.userName,
userId: result.userId,
});
}
// 初始化数据
@ByGuardpass()
@Post("init")
async init() {
await this.userService.init();
return "初始化数据成功";
}
// 测试权限分级
@UseGuards(RuleGuard)
@RBAC(["guest"])
@Post("rbac")
async rbac(@Body() body: any) {
console.log("----");
console.log(body);
console.log("-------");
return "测试";
}
}user 模块下面的 service
- user/user.service.ts
注意
注册五部曲:
- 查询
- 密码加密
- 插入数据库
- 生成长短 token
- 删除 redis 中的验证码
- 第二部
注意
通过 passport 的 local 策略验证用户名和密码
- 第三部 权限
注意
通过 JWT 解析出来你是谁获取 userId(全局守卫)
先去 redis 中查找 --- 找不到就走数据库 ---- 数据库取出来存到 redis 中
先通过 userId 查出这个用户拥有的角色 ----- 查询出这个角色拥有的权限 ----- 查出这个角色 Id 对应的权限列表 --- 判断这个权限是否在权限列表中
- 代码如下
ts
import { Inject, Injectable } from "@nestjs/common";
import { PrismadbService } from "../../commonModules/prisma/prisma.service";
import { RedisService } from "../../commonModules/redis/redis.service";
import { JwtAllService } from "../../commonModules/jwt/jwt.service";
import { ExcelService } from "../../commonModules/excel/excel.service";
import { UploadService } from "../../commonModules/upload/upload.service";
// 邮件发送
import { EmailService } from "../../commonModules/email/email.service";
import { customAlphabet } from "nanoid";
// 加密解密
import { CryptoService } from "../../commonModules/crypto/crypto.service";
// 实例化
import { RegisteruserEntity } from "./entities/Registeruser.entity";
import { LoginuserEntity } from "./entities/Loginuser.entity";
import { PermissionsEntity } from "./entities/Permissions.entity";
import { RulesEntity } from "./entities/Rules.entity";
// 抛出异常
import { GlobalCheckException } from "../../common/globalcheck.exception";
@Injectable()
export class UserService {
// 数据库
@Inject()
private readonly prisma: PrismadbService;
// redis
@Inject()
private readonly redisService: RedisService;
// JWT
@Inject()
private readonly jwtAllService: JwtAllService;
// Excel
@Inject()
private readonly ExcelService: ExcelService;
// 上传
@Inject()
private readonly UploadService: UploadService;
// 邮件发送
@Inject()
private readonly emailService: EmailService;
// 加密解密
@Inject()
private readonly cryptoService: CryptoService;
// 初始化数据
async init() {
// 用户列表
const user1 = new RegisteruserEntity();
user1.username = "admin";
user1.password = this.cryptoService.md5Encrypt("123456");
user1.email = "admin@qq.com";
user1.phone_number = "19000000001";
user1.nick_name = "jsopy_admin";
user1.head_pic = "https://file.jsopy.com/IMAGES/avatarsmoke.jpg";
user1.is_admin = 1; // 超级管理员
user1.is_frozen = 2; // 未冻结
const user2 = new RegisteruserEntity();
user2.username = "guest";
user2.password = this.cryptoService.md5Encrypt("123456");
user2.email = "guest@qq.com";
user2.phone_number = "19000000001";
user2.head_pic = "https://file.jsopy.com/IMAGES/avatarsmoke.jpg";
user2.is_admin = 2; //普通用户
user2.is_frozen = 2; // 未冻结
user2.nick_name = "jsopy_guest";
// 权限
const role1 = new RulesEntity();
role1.name = "超级管理员";
const role2 = new RulesEntity();
role2.name = "普通用户";
// 权限列表
const permissions1 = new PermissionsEntity();
permissions1.code = "admin";
permissions1.description = "admin";
const permissions2 = new PermissionsEntity();
permissions2.code = "guest";
permissions2.description = "user";
// 添加角色哪个没有外连哪个优先插入 creatMany 不支持嵌套插入 所有有外联不适用
const permissionarr = [permissions1, permissions2];
const permissionresult: any[] = [];
for (let i = 0; i < permissionarr.length; i++) {
const item = permissionarr[i];
const result = await this.prisma.permissions.create({ data: item });
permissionresult.push(result);
}
// 角色
role1.roles_permissions = {
create: [
{
permission_id: permissionresult[0].id,
},
{
permission_id: permissionresult[1].id,
},
],
};
role2.roles_permissions = {
create: [
{
permission_id: permissionresult[1].id,
},
],
};
// 添加角色
const rolearr = [role1, role2];
const roleresult: any[] = [];
for (let i = 0; i < rolearr.length; i++) {
const item = rolearr[i];
const result = await this.prisma.roles.create({ data: item });
roleresult.push(result);
}
console.log(roleresult);
// 外链 1
user1.user_roles = {
create: [
{
role_id: roleresult[0].id,
},
],
};
// 外链 2
user2.user_roles = {
create: [
{
role_id: roleresult[1].id,
},
],
};
// 添加用户
const userarr = [user1, user2];
const userresult: any[] = [];
for (let i = 0; i < userarr.length; i++) {
const item = userarr[i];
const result = await this.prisma.users.create({ data: item });
userresult.push(result);
}
console.log(userresult);
return "初始化成功";
}
// 注册
async register(body: RegisteruserEntity) {
console.log(body.email);
// 先去查询数据库里面有没有
const result = await this.prisma.users.findFirst({
where: {
email: body.email,
},
});
if (!result) {
// 去掉验证码 直接插入
let { captcha, ...result } = body;
// 密码必须md5 加密
result.password = this.cryptoService.md5Encrypt(result.password);
// 默认普通用户
const resultdata = {
...result,
user_roles: {
create: [
{
role_id: 2,
},
],
},
};
// 注册
const userresult = await this.prisma.users.create({
data: resultdata,
});
// 短 token
const token = await this.jwtAllService.setToken({
userId: userresult.id,
userName: userresult.username,
});
// 长 token
const refreshToken = await this.jwtAllService.setRefreshToken({
userId: userresult.id,
userName: userresult.username,
});
// 用过一次就删除
await this.redisService.delkey(`captcha_${body.captcha}`);
// 生成jwt
return {
token,
refreshToken,
};
} else {
throw new GlobalCheckException("邮箱已经存在");
}
}
// 发送邮件
async sendEmail(emialAddress: string) {
// 验证码
const nanoid = customAlphabet(
"1234567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ",
6
);
const randomId = nanoid();
const result = await this.emailService.sendEmail({
to: emialAddress, // 你要发送的邮件账号
subject: "邮箱", // 标题
html: `<h1>这是邮箱验证码${randomId}</h1>`, // 邮件内容
});
if (result.messageId) {
// 存入redis
const result = await this.redisService.setstr({
key: `captcha_${randomId}`,
value: emialAddress,
});
return "发送成功";
} else {
return "发送失败";
}
}
// 登录
async login(body: LoginuserEntity) {
// 短 token
const token = await this.jwtAllService.setToken({
userId: body.userId,
userName: body.userName,
});
// 长 token
const refreshToken = await this.jwtAllService.setRefreshToken({
userId: body.userId,
userName: body.userName,
});
return {
token,
refreshToken,
};
}
}