LogoLogo
github
  • 💪Upupup
  • React
    • hook
    • redux
    • Router
    • umimax+nest.js 实现 权限管理系统
    • umimax + Nest.js 实现权限管理系统
  • Vue
    • effectScope 是干啥的
    • Object.assign()
    • 响应式理解
    • @babel/preset-env 问题
    • 自定义指令
    • 问题及解决
    • 🧐权限管理(动态路由)
  • docker
    • Docker 常用命令
    • Docker部署遇到的问题
    • Docker Compose 常用命令
    • docker修改daemon.json
    • jenkins
    • Jenkinsfile 语法进阶
    • nginx配置
    • 问题
    • 玩转Nginx:轻松为Docker部署的服务添加域名的完美指南
    • Docker部署前后端项目:经验分享与问题解决
  • git
    • command
    • problem
    • rebase实践
  • 前端开发面试题集
    • CSS 面试题
    • 前端工程化面试题
    • HTML 面试题
    • JavaScript 面试题
    • NestJS 面试题
    • Node.js 面试题
    • 性能优化面试题
    • React 面试题
    • 安全面试题
    • Vue 面试题
  • interviewer
    • 计算机网络
    • 性能优化
  • leetcode
    • 算法
      • 分治算法
      • 滑动窗口与双指针
        • 🦸定长滑动窗口
        • 🚴不定长滑动窗口
        • 🚴‍♂️单序列双指针
      • 回溯
      • 二分法
  • nestjs
    • mail
    • mini-order
    • nestjs
    • prisma
    • 登录注册
  • nextjs
    • 用 V0 和 Cursor 实现全栈开发:从小白到高手的蜕变
  • tauri
    • 思路
    • 自动通知应用升级
  • vite
    • vite实现原理
  • webpack
    • 资料
  • 工具
    • Eslint
    • jenkins
    • 关于cicd
  • 微信小程序
    • ScoreDeck
    • h5跳转小程序问题
  • 思路
    • carTool
  • 操作系统学习
    • Linux命令
    • 计算机是如何计数的
    • nginx
      • location
      • try_files
  • 浏览器
    • session、location
    • web crypto
    • 性能监控和错误收集与上报
    • 预请求
  • 知识点整理
    • 知识点整理
  • 面试
    • Promise
    • 备战
    • 数码3
    • 腾娱
    • 腾讯云智
    • 重复请求合并
  • 前端工程化
    • 在 pnpm Monorepo 中使用公共方法包
由 GitBook 提供支持
在本页
  • Nestjs发送验证码实现指南
  • 1. 问题描述与解决方案
  • 1.1 遇到的问题
  • 1.2 解决方案
  • 2. 邮件发送配置详解
  • 2. 邮件发送配置详解
  • 2.1 MailerModule配置
  • 3. 验证码系统实现
  • 3.1 数据模型设计
  • 3.2 核心功能实现
  • 4. 项目演示

这有帮助吗?

在GitHub上编辑
  1. nestjs

mail

上一页nestjs下一页mini-order

最后更新于5个月前

这有帮助吗?

Nestjs发送验证码实现指南

本文详细介绍了在Nestjs中实现邮件验证码系统的完整解决方案,包括问题处理、配置说明和核心实现。

1. 问题描述与解决方案

1.1 遇到的问题

使用@nestjs-modules/mailer发送验证码时,在打包后出现模板文件找不到的问题

问题原因:打包过程中template文件未被正确包含在构建输出中

)

但是本地有啊,齐了怪了

https://cdn.liboqiao.top/markdown/image-20241224150603887.png

好吧 我本地打个包看看

好吧 确实没有,猜想:是不是这个文件被引用 所以被干掉了,看下文档怎么办,了解到可以打包的时候 把该template文件夹 打包到 该email目录下

so 修改nest-cli.json中的compilerOptions

  "compilerOptions": {
    "assets": [
      {
        "include": "email/template/**/*.{ejs,html}",
        "outDir": "dist/src/"
      }
    ],
    "deleteOutDir": true
  }

确实这样就可以了

1.2 解决方案

通过修改nest-cli.json配置文件,确保模板文件被正确打包到目标目录:

{
  "compilerOptions": {
    "assets": [
      {
        "include": "email/template/**/*.{ejs,html}",
        "outDir": "dist/src/"
      }
    ],
    "deleteOutDir": true
  }
}

2. 邮件发送配置详解

2. 邮件发送配置详解

2.1 MailerModule配置

使用MailerModule进行邮件服务配置,支持以下关键特性:

  • ✉️ 支持QQ邮箱服务

  • 📝 使用EJS模板系统

  • 🔒 支持安全传输配置

nest 邮箱发送配置

环境变量在.env文件夹中配置

MailerModule.forRootAsync({
      useFactory: () => ({
        transport: {
          host: process.env.EMAIL_HOST,
          port: process.env.EMAIL_PORT,
          ignoreTLS: true,
          secure: true,
          service: 'qq',
          auth: {
            user: process.env.EMAIL_USER,
            pass: process.env.EMAIL_PASSWORD,
          },
          tls: { rejectUnauthorized: false },
        },
        defaults: {
          from: process.env.EMAIL_FROM,
        },
        preview: false,
        template: {
          dir: path.join(process.cwd(), './src/email/template'),
          adapter: new EjsAdapter(), // or new PugAdapter() or new EjsAdapter()
          options: {
            strict: true,
          },
        },
      }),
    }),
 this.mailerService.sendMail({
      to,
      from: 'liboq@qq.com',
      subject: subject,
      template: './validate.code.ejs', //这里写你的模板名称,如果你的模板名称的单名如 validate.ejs ,直接写validate即可 系统会自动追加模板的后缀名,如果是多个,那就最好写全。
      //内容部分都是自定义的
      context: {
        code, //验证码
      },
    });

3. 验证码系统实现

3.1 数据模型设计

使用Prisma定义验证码相关的数据模型:

model EmailVerification {
  id               Int       @id @default(autoincrement())
  admin            Admin?    @relation(fields: [adminId], references: [id])
  adminId          Int?      @unique
  email            String    @unique
  verificationCode String    @unique
  expiresAt        DateTime
  isVerified       Boolean   @default(false)
  dailySendCount   Int       @default(0) // 每日发送次数限制
  lastSentAt       DateTime? // 最后发送时间记录
  createdAt        DateTime  @default(now())
}

3.2 核心功能实现

验证码系统包含以下核心特性:

  • ⏰ 每日发送限制(最多5次)

  • ⌛️ 验证码5分钟有效期

  • 🔄 防重复发送机制

  • ⚡️ 完整的错误处理

//prisma数据库配置
model Admin {
  id                Int                @id @default(autoincrement())
  name              String
  email             String             @unique
  password          String
  createdAt         DateTime           @default(now())
  updatedAt         DateTime           @updatedAt
  emailVerification EmailVerification?
}

model EmailVerification {
  id               Int       @id @default(autoincrement())
  admin            Admin?    @relation(fields: [adminId], references: [id])
  adminId          Int?      @unique
  email            String    @unique
  verificationCode String    @unique
  expiresAt        DateTime
  isVerified       Boolean   @default(false)
  dailySendCount   Int       @default(0) // 每日发送次数
  lastSentAt       DateTime? // 最后一次发送时间
  createdAt        DateTime  @default(now())
}

email.service.ts

  /**
   * 向管理员发送验证码
   *
   * @param email 管理员的邮箱地址
   * @returns 如果邮箱为 '7758258@qq.com',则返回 'No need send';否则返回 'Verification code sent successfully!'
   * @throws 如果邮箱未注册,则抛出 HttpException 异常,状态码为 500,消息为 'Email not registered'
   * @throws 如果当天发送次数超过 5 次,则抛出 HttpException 异常,状态码为 500,消息为 'You have reached the maximum number of verification attempts for today.'
   */
  public async sendAdminVerificationCode(email: string) {
    if (email === '7758258@qq.com') {
      return 'No need send';
    }
    // 1. 查找或创建验证码记录
    const currentDate = new Date();
    currentDate.setHours(0, 0, 0, 0); // 设定时间为当天的 00:00:00

    const verificationRecord = await this.prisma.emailVerification.upsert({
      where: { email },
      update: {},
      create: {
        email,
        verificationCode: '',
        expiresAt: new Date(),
        isVerified: false,
        dailySendCount: 0,
        lastSentAt: new Date(0), // 初始值
      },
    });
    // 2. 检查发送次数限制
    const lastSentAt = verificationRecord.lastSentAt
      ? new Date(verificationRecord.lastSentAt)
      : null;
    const isSameDay =
      lastSentAt && lastSentAt.toDateString() === currentDate.toDateString();

    if (isSameDay && verificationRecord.dailySendCount >= 5) {
      throw new HttpException(
        'You have reached the maximum number of verification attempts for today.',
        500,
      );
    }

    // 3. 重置或增加发送次数
    const dailySendCount = isSameDay
      ? verificationRecord.dailySendCount + 1
      : 1;
    // 2. 检查邮箱是否已注册
    const user = await this.prisma.admin.findUnique({
      where: { email },
    });
    if (!user) {
      throw new HttpException('Email  not registered', 500);
    }
    // 3. 生成验证码
    const array = new Uint32Array(6);
    const verificationCode = crypto.getRandomValues(array)[0].toString();

    // 4. 验证码有效期 10 分钟
    const expiresAt = new Date();
    expiresAt.setMinutes(expiresAt.getMinutes() + 5);

    // 5. 保存验证码到数据库
    await this.prisma.emailVerification.update({
      where: { email },
      data: {
        email,
        verificationCode,
        expiresAt,
        lastSentAt: new Date(), // 当前时间
        dailySendCount,
      },
    });
    // 5. 发送验证码到邮箱
    this.sendMail(email, 'Verification Code', verificationCode.toString());

    return 'Verification code sent successfully!';
  }

   /**
   * 验证验证码
   *
   * @param data 包含邮箱和验证码的数据
   * @returns 验证码对应的记录
   * @throws 如果邮箱未注册,则抛出 HttpException 异常,状态码为 500,消息为 'Please Send Code First'
   * @throws 如果验证码过期,则抛出 HttpException 异常,状态码为 500,消息为 'Verification code expired'
   * @throws 如果验证码错误,则抛出 HttpException 异常,状态码为 500,消息为 'Verification code error'
   */
  public async validateCode(
    data: Pick<
      Prisma.EmailVerificationCreateInput,
      'email' | 'verificationCode'
    >,
  ) {
    const email = await this.prisma.emailVerification.findUnique({
      where: { email: data.email },
    });
    if (!email) {
      throw new HttpException('Please Send Code First', 500);
    }
    // 2. 检查验证码是否过期
    const currentTime = new Date();
    if (email.expiresAt < currentTime) {
      throw new HttpException('Verification code expired', 500);
    }
    if (email?.verificationCode === data.verificationCode) {
      return email;
    }
    throw new HttpException('验证码错误', 500);
  }

4. 项目演示

本验证码系统已应用于以下项目:

https://cdn.liboqiao.top/markdown/image-20241224150710556.png
https://cdn.liboqiao.top/markdown/image-20241224151229539.png

🛍️ - 订单系统前台

⚙️ - 订单系统后台管理

https://cdn.liboqiao.top/markdown/image-20241224160126995.png
mini-order
mini-order-admin