SSF0SSF0
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
  • Node.js

    • 在 Docker 容器中运行 Node.js Koa 应用的网络配置与常见问题
    • 在 Node.js 后端实现邮箱验证码功能
    • Redis 与 JWT 结合的 Token 验证方案
    • Node.js 后端三层架构中的错误处理最佳实践
    • 在 Koa 中批量设置路由 JWT 校验的最佳实践
  • Go

    • Go1
    • Go2
  • MySql

    • 数据库连接

在 Node.js 后端实现邮箱验证码功能

简介

邮箱验证码是现代 Web 应用中常见的功能,用于用户注册、密码重置或身份验证。本文将详细介绍如何在基于 Koa 的 Node.js 应用中实现完整的邮箱验证码功能,包括验证码生成、发送和校验全过程。

技术栈

  • 后端框架:Koa 2.16.1

  • 邮件发送:Nodemailer 6.10.1

  • 开发语言:TypeScript 5.8.3

  • 项目结构:分层架构(控制器、服务层、基础设施层)

项目结构

实现邮箱验证码功能涉及四个主要部分:

  1. 基础设施层:infrastructure/email - 邮件服务配置和实现

  2. 工具类:utils/email-code-verification.util.ts - 邮箱验证码生成和管理

  3. 服务层:services/email-verification.service.ts - 邮箱验证码业务逻辑

  4. 控制器层:controllers/email-verification.controller.ts - API 接口处理

核心实现

1. 邮箱验证码控制器

控制器负责处理 HTTP 请求,验证参数并调用服务层方法:

// email-verification.controller.ts
export class EmailVerificationController {
  // 发送邮箱验证码
  public async sendEmailVerificationCode(ctx: Context): Promise<void> {
    try {
      const { email } = ctx.request.body as EmailRequest;

      // 验证邮箱格式
      if (!email || !this.validateEmail(email)) {
        ctx.status = 400;
        ctx.body = error("邮箱地址格式不正确", 400);
        return;
      }

      // 发送验证码
      const result = await emailVerificationService.sendVerificationCode(email);

      if (result) {
        ctx.body = success(null, "验证码已发送,请检查邮箱");
      } else {
        ctx.status = 500;
        ctx.body = error("验证码发送失败,请稍后重试", 500);
      }
    } catch (err) {
      console.error("发送验证码接口错误:", err);
      ctx.status = 500;
      ctx.body = error("服务器错误", 500);
    }
  }

  // 验证邮箱验证码
  public async verifyEmailCode(ctx: Context): Promise<void> {
    // 类似的验证和处理逻辑...
  }

  // 邮箱格式验证
  private validateEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

2. 邮箱验证码服务

服务层实现业务逻辑,包括生成验证码、保存及校验:

export class EmailVerificationService implements IEmailVerificationService {
  // 生成并发送验证码
  public async sendVerificationCode(email: string): Promise<boolean> {
    try {
      // 生成验证码
      const code = EmailCodeUtil.generateEmailCode();

      // 保存验证码
      EmailCodeUtil.saveCode(email, code);

      // 发送验证码邮件
      const result = await emailService.sendVerificationCode(email, code);

      // 发送失败则删除保存的验证码
      if (!result) {
        EmailCodeUtil.removeCode(email);
      }

      return result;
    } catch (error) {
      console.error("发送验证码失败:", error);
      return false;
    }
  }

  // 验证邮箱验证码
  public verifyCode(email: string, code: string): boolean {
    return EmailCodeUtil.verifyCode(email, code);
  }
}

3. 验证码工具类

邮箱验证码工具类实现验证码的生成、存储和验证:

// email-code-verification.util.ts
export class EmailCodeUtil {
  private static codeStore: Map<string, { code: string; expires: number }> =
    new Map();
  private static attemptCounters: Map<string, number> = new Map();

  /**
   * 获取验证码过期时间(毫秒)
   */
  protected static getExpireTime(): number {
    return config.email.verification.codeExpireMinutes * 60 * 1000;
  }

  /**
   * 生成指定长度的验证码
   */
  public static generateCode(length: number = 6): string {
    const useAlphanumeric = config.email.verification.useAlphanumeric;
    const chars = useAlphanumeric
      ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      : "0123456789";

    let code = "";
    for (let i = 0; i < length; i++) {
      const randomIndex = Math.floor(Math.random() * chars.length);
      code += chars[randomIndex];
    }
    return code;
  }

  /**
   * 生成邮箱验证码,使用配置的长度
   */
  public static generateEmailCode(): string {
    return this.generateCode(config.email.verification.codeLength);
  }

  // 保存验证码
  public static saveCode(email: string, code: string): void {
    this.codeStore.set(email, {
      code,
      expires: Date.now() + this.getExpireTime(),
    });
  }

  // 验证验证码
  public static verifyCode(email: string, code: string): boolean {
    // 验证逻辑实现...
  }

  // 其他工具方法...
}

4. 邮件服务

邮件服务负责实际发送验证码邮件:

// email.service.ts
class EmailService {
  private transporter: nodemailer.Transporter;

  constructor() {
    this.transporter = nodemailer.createTransport({
      host: config.email.host,
      port: config.email.port,
      secure: config.email.secure,
      auth: {
        user: config.email.auth.user,
        pass: config.email.auth.pass,
      },
    });
  }

  // 发送验证码邮件
  public async sendVerificationCode(
    to: string,
    code: string,
    subject: string = "您的验证码"
  ): Promise<boolean> {
    const html = `
      <div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif;">
        <!-- 精美的HTML邮件模板 -->
        <div style="background-color: #fff; padding: 15px; text-align: center;">
          <span style="font-size: 24px; font-weight: bold;">${code}</span>
        </div>
      </div>
    `;

    return this.sendMail(to, subject, html);
  }
}

配置说明

环境变量配置

在.env文件中添加以下配置:

# 邮件服务基本配置
EMAIL_HOST=smtp.163.com
EMAIL_PORT=465
EMAIL_SECURE=true
EMAIL_USER=你的邮箱@163.com
EMAIL_PASSWORD=客户端授权密码
EMAIL_FROM=系统名称 <你的邮箱@163.com>

# 邮箱验证码配置
EMAIL_CODE_EXPIRE_MINUTES=10     # 验证码过期时间(分钟)
EMAIL_CODE_LENGTH=6              # 验证码长度
EMAIL_MAX_VERIFY_ATTEMPTS=5      # 同一验证码最大验证尝试次数
EMAIL_MAX_DAILY_SEND=10          # 同一邮箱24小时内最大发送次数
EMAIL_SEND_COOLDOWN=60           # 同一邮箱两次发送之间的最小间隔(秒)
EMAIL_USE_ALPHANUMERIC=false     # 是否使用字母数字混合验证码(true/false)
EMAIL_CODE_CASE_SENSITIVE=false  # 验证时是否区分大小写(true/false)

配置对象说明

在统一的配置文件中添加邮箱验证码相关配置:

// config/index.ts
const config = {
  // ... 其他配置项

  email: {
    host: process.env.EMAIL_HOST || "smtp.example.com",
    port: parseInt(process.env.EMAIL_PORT || "587", 10),
    secure: process.env.EMAIL_SECURE === "true",
    auth: {
      user: process.env.EMAIL_USER || "user@example.com",
      pass: process.env.EMAIL_PASSWORD || "password",
    },
    from: process.env.EMAIL_FROM || "Chat System <no-reply@chatsystem.com>",
    verification: {
      // 验证码过期时间(分钟),默认10分钟
      codeExpireMinutes: parseInt(
        process.env.EMAIL_CODE_EXPIRE_MINUTES || "10",
        10
      ),
      // 验证码长度,默认6位
      codeLength: parseInt(process.env.EMAIL_CODE_LENGTH || "6", 10),
      // 同一邮箱最大验证尝试次数,超过则需重新发送验证码,默认5次
      maxAttempts: parseInt(process.env.EMAIL_MAX_VERIFY_ATTEMPTS || "5", 10),
      // 同一邮箱24小时内最大发送次数,防止滥用,默认10次
      maxDailySendCount: parseInt(process.env.EMAIL_MAX_DAILY_SEND || "10", 10),
      // 发送频率限制(秒),同一邮箱两次发送之间的最小间隔,默认60秒
      sendCooldownSeconds: parseInt(
        process.env.EMAIL_SEND_COOLDOWN || "60",
        10
      ),
      // 是否使用字母数字混合验证码,默认false(纯数字)
      useAlphanumeric: process.env.EMAIL_USE_ALPHANUMERIC === "true",
      // 是否区分大小写,默认false
      caseSensitive: process.env.EMAIL_CODE_CASE_SENSITIVE === "true",
    },
  },
};

使用 163 邮箱配置

要使用 163 邮箱配置邮件服务,需要:

  1. 登录 163 邮箱并开启 SMTP 服务

  2. 生成专用的"客户端授权密码"(非登录密码)

  3. 在.env文件中配置相应参数,推荐设置如下:

    • EMAIL_HOST=smtp.163.com
    • EMAIL_PORT=465
    • EMAIL_SECURE=true

API 接口

发送验证码

POST /email-verification/send
Content-Type: application/json

{
  "email": "user@example.com"
}

验证验证码

POST /email-verification/verify
Content-Type: application/json

{
  "email": "user@example.com",
  "code": "123456"
}

安全性考虑

  1. 验证码有效期:设置为 10 分钟,防止长时间有效造成安全风险

  2. 邮箱格式验证:控制器层进行格式校验,防止恶意请求

  3. 错误处理:完善的异常捕获和错误返回

  4. 环境变量:敏感信息如邮箱密码通过环境变量配置

  5. 验证尝试次数限制:同一验证码最多尝试 5 次,防止暴力破解

  6. 发送频率控制:限制同一邮箱的发送频率,防止滥用

  7. 每日发送次数限制:限制 24 小时内最大发送次数,防止骚扰

生产环境优化建议

  1. Redis 存储:使用 Redis 替代内存存储验证码,便于分布式部署

  2. 限流措施:添加 IP 或用户级别的请求频率限制

  3. 日志监控:记录验证码发送和验证情况,便于异常监控

  4. 模板多语言:支持多语言邮件模板,提升国际化体验

总结

通过分层架构和 TypeScript 强类型的支持,我们实现了一个功能完善、代码清晰的邮箱验证码系统。这种实现不仅满足了基本的验证码功能需求,还考虑了安全性、可扩展性和生产环境部署等因素。

在实际应用中,可以根据项目需求对验证码长度、有效期、邮件模板等进行个性化调整,或结合短信验证码一起使用,提供更全面的用户验证方案。

最后更新时间:
贡献者: 何风顺
上一页
在 Docker 容器中运行 Node.js Koa 应用的网络配置与常见问题
下一页
Redis 与 JWT 结合的 Token 验证方案