Skip to content

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 emial

email.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 user

2.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;
  }
}