NestJS 会议 User 模块
流程图
User 模块
分成两部分
利用邮箱发送验证码
利用验证码注册用户
1. 邮箱发送验证码
1.1. 安装依赖
bash
pnpm add --save nodemailer
pnpm add --save-dev @types/nodemailer创建模块
bash
nest g module emial
nest g service emialemail.module.ts
ts
import { Module } from "@nestjs/common";
import { EmailService } from "./email.service";
@Module({
providers: [EmailService],
exports: [EmailService],
})
export class EmailModule {}email.service.ts
ts
import { Injectable } from "@nestjs/common";
import { createTransport, Transporter } from "nodemailer";
@Injectable()
export class EmailService {
transporter: Transporter;
// 不同的邮箱 不同的设置
/*
host: 'smtp-mail.outlook.com',
port: 587,
secure: false
*/
// QQ邮箱
/*
host: 'smtp.qq.com',
port: 465,
secure: true
*/
// 网易163邮箱
/*
host: 'smtp.163.com',
port: 465,
secure: true
*/
constructor() {
this.transporter = createTransport({
host: "smtp.qq.com",
port: 587,
secure: false,
auth: {
user: "xxx@qq.com", // 发送邮箱的账号
pass: "xxxx", // 获取发送邮箱的授权码
},
});
}
// 发送邮件
async sendEmail({ to, subject, html }) {
const result = await this.transporter.sendMail({
from: {
name: "验证发送邮件请勿回复", // 这个就是右下脚提示的语言
address: "xxx@qq.com", // 发送邮箱的账号
},
to,
subject,
html,
});
return result;
}
// 发送带附件的
async sendEmailfujian({ to, subject, html, attachments }) {
const result = await this.transporter.sendMail({
from: {
name: "验证发送邮件请勿回复", // 这个就是右下脚提示的语言
address: "xxxx@qq.com", // 发送邮箱的账号
},
to,
subject,
html,
attachments,
});
return result;
}
}发送邮件
- 控制器
ts
// 发送密码到邮箱接口
@Post('sendEmail')
async sendEmail(@Body() body: any) {
console.log(body.email);
const result = await this.userSendEmailService.sendEmail(body.email);
if (result.messageId) {
return '发送成功';
} else {
return '发送失败';
}
}- service
ts
import { Inject, Injectable } from "@nestjs/common";
// 引入redis模块
import { RedisService } from "../../redis/redis.service";
// 引入异常模块
import { GlobalCheckException } from "../../../common/globalcheck.exception";
// 引入邮件模块
import { EmailService } from "../../email/email.service";
@Injectable()
export class UserSendEmailService {
@Inject()
private readonly redisService: RedisService;
@Inject()
private readonly emailService: EmailService;
async sendEmail(email: string) {
if (!email) {
throw new GlobalCheckException("请输入邮箱");
}
// 写一个生成6位的随机数不要以0开头
const emailcode = Math.floor(Math.random() * 900000) + 100000;
// 将随机数存入redis中,设置过期时间为60分钟
await this.redisService.setstr({ key: email, value: emailcode });
// 把这个数字发送走
const result = await this.emailService.sendEmail({
to: email,
subject: "验证码",
html: `<p>${emailcode}</p>`,
});
return result;
}
}2. 利用验证码注册用户
2.1. 创建模块
bash
nest g module user
nest g service user2.2. user.module.ts
ts
import { Module } from "@nestjs/common";
import { UserRegisterService } from "./services/user.register.service";
import { UserSendEmailService } from "./services/user.sendEmail.service";
import { UserController } from "./user.controller";
import { EmailModule } from "../email/email.module";
import { UserLoginService } from "./services/user.login.service";
@Module({
imports: [EmailModule],
controllers: [UserController],
providers: [UserRegisterService, UserSendEmailService, UserLoginService],
})
export class UserModule {}2.3. user.controller.ts
我这里利用 jwt 拖底了
两种登录方式 1. 手机号 2. 邮箱
一种注册方式 1. 邮箱,输入密码即可
ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
Req,
} from "@nestjs/common";
import { UserRegisterService } from "./services/user.register.service";
import { UserSendEmailService } from "./services/user.sendEmail.service";
import { UserLoginService } from "./services/user.login.service";
import { RegisterUserDto } from "./dto/register-user.dto";
import { AuthGuard } from "@nestjs/passport";
@Controller("user")
export class UserController {
constructor(
private readonly userRegisterService: UserRegisterService,
private readonly userSendEmailService: UserSendEmailService,
private readonly userLoginService: UserLoginService
) {}
// 注册用户接口
@Post("register")
async register(@Body() register: RegisterUserDto) {
console.log(register);
const result = await this.userRegisterService.registerUser(register);
if (result.status) {
return {
code: 200,
message: "注册成功",
data: {
token: result.token,
},
};
} else {
return {
code: 403,
message: "注册失败",
};
}
}
// 发送密码到邮箱接口
@Post("sendEmail")
async sendEmail(@Body() body: any) {
console.log(body.email);
const { result, code } = await this.userSendEmailService.sendEmail(
body.email
);
if (result.messageId) {
return {
code: 200,
message: "发送成功",
data: code,
};
} else {
return {
code: 403,
message: "发送失败",
};
}
}
// 邮箱登录用户
@UseGuards(AuthGuard("local"))
@Post("login")
async login(@Req() req: any) {
const result = await this.userLoginService.loginUser(req.user.id);
return {
code: result.status,
token: result.token,
};
}
// 手机登录
@UseGuards(AuthGuard("custom"))
@Post("customlogin")
async customlogin(@Req() req: any) {
const result = await this.userLoginService.loginUser(req.user.id);
return {
code: result.status,
token: result.token,
};
}
}2.4 user.register.service.ts
ts
import { Inject, Injectable } from "@nestjs/common";
// 引入数据库模块
import { PrismadbService } from "../../prisma/prisma.service";
// 引入redis模块
import { RedisService } from "../../redis/redis.service";
import { RegisterUser } from "../entities/user.entity";
// 引入异常模块
import { GlobalCheckException } from "../../../common/globalcheck.exception";
// 引入加密模块
import * as crypto from "crypto";
// 引入jwt 模块
import { JwtAllService } from "../../jwt/jwt.service";
@Injectable()
export class UserRegisterService {
// 数据库模块
@Inject()
private readonly prisma: PrismadbService;
// redis 模块
@Inject()
private readonly redis: RedisService;
// jwt 模块
@Inject()
private readonly jwt: JwtAllService;
// 密码加密
async passwordmd5(str) {
const hash = crypto.createHash("md5");
hash.update(str);
return hash.digest("hex");
}
// 注册用户
async registerUser(data: RegisterUser) {
// 判断验证码失效没失效
const email_code = await this.redis.get(`${data.email}`);
if (!email_code) {
throw new GlobalCheckException("验证码失效");
}
if (email_code !== data.captcha) {
throw new GlobalCheckException("验证码错误");
}
// 先去查询有没有这个用户
const user = await this.prisma.users.findFirst({
where: {
email: data.email,
},
});
//
if (user) {
throw new GlobalCheckException("用户已存在");
}
// 注册用户
const RegisterUserObj = new RegisterUser();
RegisterUserObj.username = data.username;
RegisterUserObj.password = await this.passwordmd5(data.password);
RegisterUserObj.nick_name = data.nick_name;
RegisterUserObj.email = data.email;
RegisterUserObj.head_pic = data.head_pic;
RegisterUserObj.phone_number = data.phone_number;
RegisterUserObj.is_admin = data.is_admin;
RegisterUserObj.is_frozen = data.is_frozen;
// 插入数据库
const res = await this.prisma.users.create({
data: RegisterUserObj,
});
if (res) {
// 删除redis中的验证码
await this.redis.delkey(`${data.email}`);
// 生成token
const payload = {
useId: res.id,
};
const token = await this.jwt.setToken(payload);
// 存到redis中
const redisresult = await this.redis.setstr({
key: `userId_${res.id}`,
value: token,
});
return {
status: true,
token,
};
} else {
return {
status: false,
};
}
}
}2.5 user.sendEmail.service.ts
ts
import { Inject, Injectable } from "@nestjs/common";
// 引入redis模块
import { RedisService } from "../../redis/redis.service";
// 引入异常模块
import { GlobalCheckException } from "../../../common/globalcheck.exception";
// 引入邮件模块
import { EmailService } from "../../email/email.service";
@Injectable()
export class UserSendEmailService {
@Inject()
private readonly redisService: RedisService;
@Inject()
private readonly emailService: EmailService;
async sendEmail(email: string) {
if (!email) {
throw new GlobalCheckException("请输入邮箱");
}
// 写一个生成6位的随机数不要以0开头
const emailcode = Math.floor(Math.random() * 900000) + 100000;
// 将随机数存入redis中,设置过期时间为60分钟
await this.redisService.setstr({ key: email, value: emailcode });
// 把这个数字发送走
const result = await this.emailService.sendEmail({
to: email,
subject: "验证码",
html: `<p>${emailcode}</p>`,
});
return {
result,
code: emailcode,
};
}
}2.6 user.login.service.ts
ts
import { Inject, Injectable } from "@nestjs/common";
// 引入redis模块
import { RedisService } from "../../redis/redis.service";
// 引入加密模块
import * as crypto from "crypto";
// 引入jwt 模块
import { JwtAllService } from "../../jwt/jwt.service";
@Injectable()
export class UserLoginService {
// redis 模块
@Inject()
private readonly redis: RedisService;
// jwt 模块
@Inject()
private readonly jwt: JwtAllService;
// 密码加密
async passwordmd5(str) {
const hash = crypto.createHash("md5");
hash.update(str);
return hash.digest("hex");
}
// 登录用户
async loginUser(id: string) {
// 生成token
const payload = {
useId: id,
};
const token = await this.jwt.setToken(payload);
// 存到redis中
await this.redis.setstr(
{
key: `userId_${id}`,
value: token,
},
60 * 60 * 24 * 7
);
return {
status: true,
token,
};
}
}策略
3.1 local.strategy.ts
- 邮箱和验证码
ts
import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import { PrismadbService } from "../prisma/prisma.service";
import { GlobalCheckException } from "../../common/globalcheck.exception";
// 引入加密模块
import * as crypto from "crypto";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
@Inject()
private prisma: PrismadbService;
constructor() {
super();
}
// 希望执行什么验证方法
async validate(username: string, password: string) {
let user: any = "";
const hash = crypto.createHash("md5");
hash.update(password);
const passwordhash = hash.digest("hex");
user = await this.prisma.users.findFirst({
where: {
email: username,
},
});
if (!user) {
throw new GlobalCheckException("用户名或密码错误");
}
if (user.password != passwordhash) {
throw new GlobalCheckException("密码错误");
}
return user;
}
}3.2 jwt.strategy.ts
ts
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor(private readonly configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get("JWT_SECRETKEY"),
});
}
async validate(payload: any) {
// 这里换成你自己生成jwt那个 或者全局搜索useId
console.log(payload);
return { userId: payload.userId };
}
}3.3 custom.strategy.ts
- 手机号
ts
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-custom";
import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import { PrismadbService } from "../prisma/prisma.service";
import { GlobalCheckException } from "../../common/globalcheck.exception";
import { RedisService } from "../redis/redis.service";
@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy, "custom") {
@Inject()
private readonly prisma: PrismadbService;
@Inject()
private readonly redis: RedisService;
constructor() {
super();
}
// 手机号登录验证
async validate(req: any) {
console.log(req.body);
// 先去redis里面查询验证码,前提是用户点击发送验证码后存到redis里面了
if (!req.body.phone || !req.body.code) {
throw new GlobalCheckException("手机或者验证码不能为空");
}
const phone_code = await this.redis.get("phone_" + req.body.phone);
if (!phone_code) {
throw new GlobalCheckException("手机或者验证码不对");
}
if (phone_code !== req.body.code) {
throw new GlobalCheckException("验证码错误");
}
let user;
user = await this.prisma.users.findFirst({
where: {
phone_number: req.body.phone,
},
});
return user;
}
}