Nest成长足迹(四):使用nodemailer发送html/ejs文件模版的邮箱验证码

1、前言

本文章记录自己学习Nest的过程,适于前端及对后端没有基础但对Nest感兴趣的同学,如有错误,欢迎各位大佬指正

2、Nest成长足迹系列:

3、正文

3.1、使用nodemailer

  • 安装nodemailer
sh 复制代码
npm install nodemailer --save

3.2、qq邮箱开启发邮件服务

  • 想要使用qq邮箱的发送邮件服务,需要开启POP3/SMTP/IMAP功能,在qq邮箱的邮箱设置中找到账号tab
  • 往下滑可以看到:(我这个是已开启的状态,你未开启的话需要自己开启)
  • 开启之后,可以获取授权码,点击之后会跳到验证页面,会需要发送一条短信,验证通过后即可获取到授权码
  • email.service.ts
ts 复制代码
import { Injectable } from '@nestjs/common';
import { createTransport, Transporter } from 'nodemailer';

@Injectable()
export class EmailService {
  transporter: Transporter;

  constructor() {
    this.transporter = createTransport({
      host: 'smtp.qq.com', // smtp服务的域名
      port: 587, // smtp服务的端口
      secure: false,
      auth: {
        user: process.env.EMAIL_USER, // 你的邮箱地址
        pass: process.env.EMAIL_PASS // 你的授权码
      }
    });
  }

  async sendMail({ to, subject, html }) {
    await this.transporter.sendMail({
      from: {
        name: 'xxx系统',
        address: process.env.EMAIL_USER // 你的邮箱地址
      },
      to,
      subject,
      html
    });
  }
}
  • email.controller.ts
    • 在这个文件中有两个路由写到了一起,只是url不一致
    • Nest项目中HTML文件通常不会被自动打包到 dist 文件夹中,因为Nest.js主要用于构建后端应用程序,而不是前端应用程序。dist 文件夹通常包含编译后的服务器代码和依赖项,而不包括前端资源。
    • 所以,我们需要将html/ejs的模版文件放到public文件夹中
ts 复制代码
import { Controller, Get, Inject, Query, Request } from '@nestjs/common';

import * as path from 'path';
import * as fs from 'fs';
import * as ejs from 'ejs';

import { EmailService } from '../services/email.service';
import { RedisService } from '../services/redis.service';

import {
  REGISTER_CAPTCHA,
  UPDATE_PASSWORD_CAPTCHA
} from '@/common/constant/common-constants';

import { BusinessException } from '@/common/exceptions/business.exception';

@Controller('captcha')
export class EmailController {
  @Inject(EmailService)
  private emailService: EmailService;

  @Inject(RedisService)
  private redisService: RedisService;

  @Get(['register', 'update_password'])
  async captcha(@Query('address') address: string, @Request() req) {
    // 生成一个长度为 6 的随机字符串
    const code: string = Math.random().toString().slice(2, 8);

    const url: string = req.route.path;

    const keyMap = {
      '/api/captcha/register': `${REGISTER_CAPTCHA}_${address}`,
      '/api/captcha/update_password': `${UPDATE_PASSWORD_CAPTCHA}_${address}`
    };

    const subjectMap = {
      '/api/captcha/register': '注册验证码',
      '/api/captcha/update_password': '修改密码验证码'
    };

    // 读取 HTML 模板文件
    try {
      /**
       * 在 Nest.js 项目中,HTML 文件通常不会被自动打包到 dist 文件夹中,因为 Nest.* js主要用于构建后端应用程序,而不是前端应用程序。dist 文件夹通常包含编译后的服务* 器代码和依赖项,而不包括前端资源。
       */
      // ejs || html
      const htmlPath: string = path.join(__dirname, '../../public/email.html');
      const emailTemplate = fs.readFileSync(htmlPath, 'utf-8');
      // 使用 EJS 替换验证码
      const validity: number = 5; // 有效期5min
      const emailConfig = {
        code,
        validity,
        name: '晚风予星'
      };
      const emailHtml = ejs.render(emailTemplate, emailConfig);

      await this.redisService.set(keyMap[url], code, validity * 60);

      await this.emailService.sendMail({
        to: address,
        subject: subjectMap[url],
        html: emailHtml
      });

      return '发送成功';
    } catch (e) {
      throw new BusinessException('读取文件失败!');
    }
  }
}

3.3、使用html模版

  • 而想要使用html/ejs的模版文件发送邮件验证码,需要安装ejs
sh 复制代码
yarn add ejs -D
  • 在添加html文件以及写好html后你会发现文件报错了,这表明ESLint不知道如何解析这种文件扩展名
html 复制代码
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="description" content="email code" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>验证码邮件</title>
  </head>
  <!--邮箱验证码模板-->
  <body>
    <div style="background-color: #ececec; padding: 35px">
      <table
        cellpadding="0"
        style="
          width: 800px;
          height: 100%;
          margin: 0px auto;
          text-align: left;
          position: relative;
          border-radius: 5px;
          font-size: 14px;
          font-family: 微软雅黑, 黑体;
          line-height: 1.5;
          box-shadow: rgb(153, 153, 153) 0px 0px 5px;
          border-collapse: collapse;
          background: #fff;
          background-position: initial initial;
          background-repeat: initial initial;
        "
      >
        <tbody>
          <tr>
            <th
              style="
                height: 25px;
                line-height: 25px;
                padding: 15px 35px;
                border-bottom: 1px solid rgb(148, 0, 211);
                background-color: RGB(148, 0, 211);
                border-top-left-radius: 5px;
                border-top-right-radius: 5px;
                border-bottom-right-radius: 0px;
                border-bottom-left-radius: 0px;
              "
            >
              <span style="font-size: 24px; color: rgb(255, 255, 255)"
                >xxxxxx系统</span
              >
            </th>
          </tr>
          <tr>
            <td style="word-break: break-all">
              <div
                style="
                  padding: 25px 35px 40px;
                  background-color: #fff;
                  opacity: 0.8;
                "
              >
                <h2 style="margin: 5px 0px">
                  <span style="color: #333333; line-height: 20px">
                    <span style="line-height: 22px; font-size: 18px">
                      尊敬的用户:</span
                    >
                  </span>
                </h2>
                <p>
                  您好!感谢您使用xxxxxx系统,您的账号正在进行邮箱验证,验证码为:<span
                    style="color: #ff8c00"
                    ><%= code %></span
                  >,有效期<%= validity %>分钟,请尽快填写验证码完成验证!
                </p>
                <br />

                <div style="width: 100%; margin: 0 auto">
                  <div
                    style="
                      padding: 10px 10px 0;
                      border-top: 1px solid #ccc;
                      color: #747474;
                      margin-bottom: 20px;
                      line-height: 1.3em;
                      font-size: 12px;
                      text-align: right;
                    "
                  >
                    <p>──<%= name %></p>
                    <br />
                    <p>
                      此为系统邮件,请勿回复<br />
                      Please do not reply to this system email
                    </p>
                    <p>©<%= name %></p>
                  </div>
                </div>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </body>
</html>
  • 可以检查一下,是否有安装eslint-plugin-html这个插件来支持html文件的检查和解析,没有的需要安装一下
sh 复制代码
yarn add eslint-plugin-html -D
  • 安装完成后,需要在.eslintrc.js文件中添加
js 复制代码
parserOptions: {
    extraFileExtensions: ['.html'] // 添加这一行
  },
  
plugins: [
    '@typescript-eslint/eslint-plugin',
    'eslint-plugin-html',
  ],

3.4、使用ejs模版

  • 想要使用ejs模版,用法和使用html模版也大差不差,需要安装ejs的解析插件
sh 复制代码
yarn add eslint-plugin-ejs -D
  • 安装完成后,需要在.eslintrc.js文件中添加
js 复制代码
parserOptions: {
    extraFileExtensions: ['.ejs'] // 添加这一行
  },
  
plugins: [
    '@typescript-eslint/eslint-plugin',
    'eslint-plugin-ejs',
  ],
extends: [
    'plugin:eslint-plugin-ejs'
  ],
  • ejs页面的代码和html页面的代码一致

  • 以上步骤完成之后,html/ejs页面的报错就会消失

  • postman中发送请求后,可以接收到qq邮箱发送来的验证码

相关推荐
Black蜡笔小新1 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_2 小时前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑2132 小时前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy2 小时前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪3 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞3 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-3 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与3 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun3 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇3 小时前
ES6进阶知识一
前端·ecmascript·es6