从零学习Node.js框架Koa 【七】Koa实战:构建企业级邮箱验证注册系统

系列文章目录

从零学习Node.js框架Koa 【一】 Koa 初探从环境搭建到第一个应用程序
从零学习Node.js框架Koa 【二】Koa 核心机制解析:中间件与 Context 的深度理解
从零学习Node.js框架Koa 【三】Koa路由与静态资源管理:处理请求与响应
从零学习Node.js框架Koa 【四】Koa 与数据库(MySQL)连接,实现CRUD操作
从零学习Node.js框架Koa 【五】Koa鉴权全解析:JWT+Redis构建安全认证系统
从零学习Node.js框架Koa 【六】Koa文件上传下载实现:@koa/multer 与 koa-send 深度解析
从零学习Node.js框架Koa 【七】Koa实战:构建企业级邮箱验证注册系统


文章目录


前言

在现代Web应用中,用户认证系统不仅仅是"用户名+密码"那么简单。随着网络安全威胁的日益复杂,我们需要从多个维度构建防护体系:防止暴力破解、防范机器人注册、保障数据传输安全、确保会话管理可靠.。本篇我们将继续学习如何基于Koa框架,构建一个完整的、生产可用的邮箱验证注册系统。深入探讨架构设计、安全考量以及实际编码中的最佳实践。


一、注册功能需求拆解

核心业务功能

  1. 用户注册流程:用户通过邮箱接收验证码、填写账号、密码、验证码完成注册。
  2. 验证码发送机制:向用户邮箱发送4位数字验证码
  3. 密码安全存储:使用现代加密算法存储用户密码

安全防护需求

  1. 验证码安全设计:5分钟有效期,一次性使用

  2. 防刷机制:

    (1) 60秒内不能重复发送验证码

    (2)每个邮箱每日最多发送10次

  3. 输入验证:

    (1) 邮箱格式校验

    (2)密码强度验证

    (3) 必填字段校验

  4. 唯一性校验:账号和邮箱不能重复注册

技术架构需求

  1. 分层架构设计:清晰的路由-控制器-服务分层
  2. Redis集成:高性能验证码存储和频率限制
  3. 邮件服务:专业的HTML邮件模板
  4. 环境配置:敏感信息通过环境变量管理

用户体验需求

  1. 友好的错误提示:清晰的验证失败反馈
  2. 美观的邮件界面:专业且易于阅读的验证码邮件
  3. 合理的限制策略:防止滥用同时不影响正常使用

技术栈选型

  1. 邮件服务:Nodemailer + QQ邮箱SMTP
  2. 密码加密:Argon2(现代密码哈希算法)
  3. 环境管理:dotenv

现在我们已经明确了系统的完整功能需求,接下来让我们深入代码实现,看看如何将这些需求转化为具体的架构设计和代码实现。


二、架构设计:分层清晰的模块化方案

让我们先看看整体项目架构:

javascript 复制代码
├── config/            # 配置层 - 应用配置
├── routes/           # 路由层 - 路由接口定义
├── controllers/      # 控制层 - 请求响应处理
├── services/         # 服务层 - 业务逻辑核心
├── models/           # 数据层 - 数据访问抽象
├── utils/            # 工具类 - 通用功能
├── .env              # 环境变量 - 全局配置
└── app.js            # 入口文件 
  • 路由层:管路径映射(写URL到处理函数的绑定)
  • 控制层:管请求协调(写参数提取校验和响应格式化)
  • 服务层:管业务逻辑 (写具体的注册、验证码发送等核心功能)
  • 数据层:管持久化(写数据库/缓存的读写操作)

这种分层设计遵循了单一职责原则,每层都有明确的职责边界,便于维护和测试。

二、代码实现

路由层

路由层作为系统的入口点,对应需求中的API端点定义:

routes/auth.js代码如下

javascript 复制代码
// routes/auth.js
const authController=require('../controllers/AuthController')
const Router = require('koa-router');
const router = new Router({
  prefix: "/api", //统一前缀
});
// 登录接口 
....
....

// 注册接口
router.post('/register',authController.register)
// 发送验证码接口
router.post('/send-verification-code',authController.sendVerificationCode)


module.exports = router;

说明:上述代码我们设计了注册和发送验证码两个接口,这种分离设计符合用户的操作流程:先获取验证码,再完成注册。再者发送验证码接口也可以复用其他的场景(如密码重置)。


控制器层

控制器层对应功能需求中的入参处理和响应格式化

controllers/AuthController.js代码如下:

javascript 复制代码
// controllers/AuthController.js
const AuthService = require("../services/AuthService.js");
const { success } = require("../utils/response.js");

class AuthController {
  //登录
  async login(ctx) {
    ....
    ....
  }

  //注册
  async register(ctx) {
    let { account, password, email, code } = ctx.request.body;
    let res = await AuthService.register(ctx, account, password, email, code);
    if (res) {
      success(ctx, res, "注册成功");
    }
  }

  //发送验证码
  async sendVerificationCode(ctx) {
    let { email } = ctx.request.body;
    let res = await AuthService.sendVerificationCode(ctx, email);
    if (res) {
      success(ctx, true, "验证码发送成功");
    }
  }
}

module.exports = new AuthController();
javascript 复制代码
//utils/response.js
// 统一响应格式
const success = (ctx, data = null, message = 'success', code = 200) => {
    ctx.body = {
      code,
      message,
      data,
    };
  };
  
  const error = (ctx, message = 'error', code = 500) => {
    ctx.body = {
      code,
      message,
    };
  };
  
  module.exports = {
    success,
    error
  };

说明:控制器主要功能是对入参提取校验和响应格式化,尽量保持精简,所有的复杂业务逻辑都委托给服务层处理,代码控制在20行以内。一旦超过这个阈值,就应该考虑将逻辑下沉到服务层。


服务层

服务层是实现所有功能需求的核心,包含了完整的安全校验链和业务逻辑。

(1)注册功能(services/AuthService.js)

javascript 复制代码
//services/AuthService.js

const authDAO = require("../models/Auth.js");
const { error } = require("../utils/response.js");
const argon2 = require("argon2");
const passwordValidator = require("../utils/passwordValidator.js");
const {
  generateVerificationCode,
  sendVerificationCode,
  isValidEmail,
} = require("./emailVerificationService.js");
const {
  setVerifyCode,
  getVerifyCode,
  delVerifyCode,
  checkSendLimit,
  setSendLimit,
  checkDailyAttemptsLimit,
  setDailyAttemptsCount,
} = require("./verificationCodeRedis.js");

  /**
   * 注册
   * @param {*} ctx:koa上下文
   * @param {*} account :账号
   * @param {*} password :密码
   * @param {*} email :邮箱
   * @param {*} code :验证码
   */
  async register(ctx, account, password, email, code) {
    if (!account) return error(ctx, "请输入账号", 400);
    if (!password) return error(ctx, "请输入密码", 400);
    if (!email) return error(ctx, "请输入邮箱", 400);
    if (!code) return error(ctx, "请输入验证码", 400);
    //密码强度验证
    let validateResult = passwordValidator.validate(password);
    if (!validateResult.isValid) {
      return error(ctx, validateResult.errors[0], 400);
    }

    //账号唯一性校验
    if (await authDAO.isFieldValueExists("account", account)) {
      return error(ctx, "账号已存在", 409);
    }
    //邮箱格式校验
    if (!isValidEmail(email)) {
      return error(ctx, "邮箱格式不正确,请输入正确的邮箱", 400);
    }
    // 邮箱唯一性校验
    if (await authDAO.isFieldValueExists("email", email)) {
      return error(ctx, "邮箱已被注册", 409);
    }
    //验证码校验
    const storedCode = await getVerifyCode(email);
    if (!storedCode) {
      return error(ctx, "验证码无效或已过期,请重新获取", 400);
    }

    if (storedCode !== code) {
      return error(ctx, "验证码错误,请重新输入", 400);
    }

    // 验证成功后清理 - 防止重复使用
    await delVerifyCode(email);

    //密码加密存储
    const hashedPassword = await argon2.hash(password, {
      type: argon2.argon2id,
      memoryCost: 2 ** 16,
      timeCost: 3,
      parallelism: 1,
    });

    //注册用户
    let { id } = await authDAO.create({
      account,
      password: hashedPassword,
      email,
      created_at: Date.now(),
    });
    return { id };
  }

说明:这个注册流程完整实现了我们之前定义的功能需求,从基础验证到核心业务逻辑的完整链条,每个步骤都有明确的安全考量和清晰的错误反馈机制。

为了满足密码安全存储需求,我们选择Argon2算法:

javascript 复制代码
const hashedPassword = await argon2.hash(password, {
  type: argon2.argon2id,    // 混合模式,平衡防御侧信道和GPU攻击
  memoryCost: 2 ** 16,      // 64MB内存消耗,增加攻击成本
  timeCost: 3,              // 时间成本,平衡性能与安全
  parallelism: 1,           // 并行度
});

Argon2是密码哈希竞赛的获胜者,能够有效抵抗GPU和ASIC攻击,完美满足了我们对密码安全的需求。

ps:代码中authDAO 是模型层封装好的对数据库增删改查工具类实例,后续文章会详细介绍,此处只需了解其作用是对数据库操作即可

加强密码强度校验,工具类封装utils/passwordValidator.js

javascript 复制代码
//passwordValidator.js
// 密码校验
class PasswordValidator {
    /**
     * 密码强度验证
     */
    static validate(password) {
      const rules = {
        minLength: 8,
        maxLength: 128,
        requireUppercase: false,//必须大写
        requireLowercase: false,//必须小写
        requireNumbers: false,//必须有数字
        requireSpecialChars: false,//必须有特殊字符
        weakPassword: ['12345678', 'password', 'admin123', 'qwertyui'],//禁用弱密码
      };
  
      const errors = [];
  
      // 长度检查
      if (password.length < rules.minLength) {
        errors.push(`密码长度至少 ${rules.minLength} 位`);
      }
      if (password.length > rules.maxLength) {
        errors.push(`密码长度不能超过 ${rules.maxLength} 位`);
      }
  
      // 大写字母检查
      if (rules.requireUppercase && !/[A-Z]/.test(password)) {
        errors.push('密码必须包含至少一个大写字母');
      }
  
      // 小写字母检查
      if (rules.requireLowercase && !/[a-z]/.test(password)) {
        errors.push('密码必须包含至少一个小写字母');
      }
  
      // 数字检查
      if (rules.requireNumbers && !/\d/.test(password)) {
        errors.push('密码必须包含至少一个数字');
      }
  
      // 特殊字符检查
      if (rules.requireSpecialChars && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
        errors.push('密码必须包含至少一个特殊字符');
      }
  
      // 常见弱密码检查
      if (rules.weakPassword.includes(password.toLowerCase())) {
        errors.push('密码过于简单,请使用更复杂的密码');
      }
  
      return {
        isValid: errors.length === 0,
        errors
      };
    }
  }
  
  module.exports = PasswordValidator;

说明:该密码校验工具可校验密码长度、是否包含小写字母、大写字母、特殊字符以及密码是否过于简单,并有配置项可开启或关闭。

(2)验证码发送功能

1、验证码发送服务(services/AuthService.js)

该文件是验证码发送接口主要业务逻辑实现,包括入参校验、验证码生成、邮件发送和存储

javascript 复制代码
//services/AuthService.js
 /**
   * 发送验证码
   * @param {*} ctx :上下文
   * @param {*} email :邮箱
   */
  async sendVerificationCode(ctx, email) {
    //邮箱基础校验
    if (!email) {
      return error(ctx, "请输入邮箱", 400);
    }
    if (!isValidEmail(email)) {
      return error(ctx, "邮箱格式不正确,请输入正确的邮箱", 400);
    }

    //  邮箱注册状态检查
    if (await authDAO.isFieldValueExists("email", email)) {
      return error(ctx, "邮箱已被注册", 409);
    }
    // 每日发送次数限制
    if (!(await checkDailyAttemptsLimit(email))) {
      return error(ctx, "今日发送验证码已达上限", 400);
    }
    // 发送频率限制
    if (!(await checkSendLimit(email))) {
      return error(
        ctx,
        `${process.env.VERIFICATION_SEND_LIMIT_EXPIRE}秒内已发送验证码,请稍后再试`,
        400
      );
    }

    //生成验证码
    const code = generateVerificationCode();
    //存储验证码
    await setVerifyCode(email, code);
    await setSendLimit(email);
    await setDailyAttemptsCount(email);
    
    //发送邮件
    await sendVerificationCode(email, code);
    return true;
  }

说明:这里实现了四层防护,完美对应了安全防护需求中的防刷机制。1、格式校验防止恶意邮箱格式 2、唯一性检查:避免已验证邮箱重复发送3、每日上限:限制每个邮箱每日最大发送次数(10次)4、频率限制:防止短时间内频繁发送(60秒间隔)

2、qq邮箱验证码发送器(services/EmailVerificationService.js)

验证码发送服务逻辑中调用封装好的qq邮箱验证码发送器进行验证码生成和邮件发送。

javascript 复制代码
//services/EmailVerificationService
/**
 * 验证码发送工具
 */
const nodemailer = require("nodemailer");
// 引入QQ邮箱配置
const QQ_EMAIL_CONFIG = require("../config/email");

// 创建传输器
const transporter = nodemailer.createTransport(QQ_EMAIL_CONFIG);

/**
 * 生成4位验证码
 * @returns 验证码
 */
const generateVerificationCode = () => {
  let code = '';
  for (let i = 0; i < 4; i++) {
    code += Math.floor(Math.random() * 10); // 0-9
  }
  return code;
};

/**
 * 发送验证码邮件
 * @param {*} email 收件人邮箱
 * @param {*} code  验证码
 * @returns
 */
const sendVerificationCode = async (email, code) => {
  try {
    // 验证收件人邮箱格式
    if (!isValidEmail(email)) {
      throw new Error("收件人邮箱格式不正确,请输入正确的邮箱");
    }

    const mailOptions = {
      from: {
        name: "验证码服务", // 发件人名称
        address: QQ_EMAIL_CONFIG.auth.user,
      },
      to: email,
      subject: "【重要】注册验证码",
      text: `您的注册验证码是:${code},验证码5分钟内有效。`,
      html: generateQQEmailTemplate(code),
    };

    // 发送邮件
    const info = await transporter.sendMail(mailOptions);

    return {
      success: true,
      message: "验证码发送成功",
      messageId: info.messageId,
      to: email,
    };
  } catch (error) {
    // console.error(" 发送验证码失败:", error.message);
    // 提供友好的错误提示
    let userMessage = "验证码发送失败";
    if (error.code === "EAUTH") {
      userMessage = "邮箱认证失败,请检查授权码是否正确";
    } else if (error.code === "ECONNECTION") {
      userMessage = "连接QQ邮箱服务器失败,请检查网络";
    } else if (error.responseCode === 550) {
      userMessage = "收件人邮箱地址不存在或无法接收邮件";
    }

    throw new Error(userMessage);
  }
};

// 验证是否为邮箱
const isValidEmail = (email) => {
  const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return reg.test(email);
};

// 生成QQ邮箱专用HTML模板
const generateQQEmailTemplate = (code) => {
  return `
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>注册验证码</title>
        <style>
          body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
          }
          .email-container {
            max-width: 600px;
            margin: 0 auto;
            background-color: white;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
          }
          .header {
            background: linear-gradient(135deg, #12c2e9, #c471ed, #f64f59);
            color: white;
            padding: 30px 20px;
            text-align: center;
          }
          .header h1 {
            margin: 0;
            font-size: 24px;
          }
          .content {
            padding: 30px;
          }
          .code-container {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 25px;
            text-align: center;
            border-radius: 8px;
            margin: 30px 0;
            box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
          }
          .code {
            font-size: 42px;
            font-weight: bold;
            letter-spacing: 8px;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
            font-family: 'Courier New', monospace;
          }
          .tips {
            background-color: #f8f9fa;
            border-left: 4px solid #12c2e9;
            padding: 15px;
            margin: 20px 0;
            border-radius: 4px;
          }
          .footer {
            text-align: center;
            padding: 20px;
            color: #666;
            font-size: 12px;
            border-top: 1px solid #eee;
            background-color: #f9f9f9;
          }
          .warning {
            color: #ff6b6b;
            font-weight: bold;
            background-color: #ffeaa7;
            padding: 10px;
            border-radius: 4px;
            margin: 15px 0;
          }
          .qq-logo {
            width: 60px;
            height: 60px;
            background: linear-gradient(135deg, #12c2e9, #c471ed);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 auto 20px;
            color: white;
            font-size: 24px;
            font-weight: bold;
          }
        </style>
      </head>
      <body>
        <div class="email-container">
          <div class="header">
            <div class="qq-logo">Q</div>
            <h1>邮箱验证码</h1>
            <p>QQ邮箱安全验证服务</p>
          </div>
          
          <div class="content">
            <p>尊敬的用户 ,您好!</p>
            <p>您正在申请注册账号,请使用以下验证码完成验证:</p>
            
            <div class="code-container">
              <div class="code">${code}</div>
            </div>
            
            <div class="warning">
              ⚠️ 重要提示:请勿将验证码透露给任何人!
            </div>
            
            <div class="tips">
              <p><strong>使用说明:</strong></p>
              <ul>
                <li>此验证码有效期为 <strong>5分钟</strong></li>
                <li>请在注册页面输入此验证码完成验证</li>
                <li>如非本人操作,请忽略此邮件</li>
                <li>如有疑问,请联系客服</li>
              </ul>
            </div>
            
            <p>感谢您使用我们的服务!</p>
            <p><em>系统自动发送,请勿回复本邮件。</em></p>
          </div>
          
          <div class="footer">
            <p>© ${new Date().getFullYear()} QQ邮箱验证服务</p>
            <p>本邮件由系统自动发送,如有疑问请联系管理员</p>
            <p>发送时间:${new Date().toLocaleString("zh-CN")}</p>
          </div>
        </div>
      </body>
      </html>
    `;
};

module.exports = {
  generateVerificationCode,
  sendVerificationCode,
  isValidEmail,
};

说明:我们这里选择qq邮箱作为验证码发送载体,技术栈使用Nodemailer + QQ邮箱SMTP。其中nodemailer可通过npm安装,QQ邮箱SMTP需要配置,具体配置教程见后面介绍。上述代码我们封装了一个qq邮箱验证码发送器,为了满足用户体验需求中的美观邮件界面,我们精心设计了专业的HTML邮件模板,模版如下图所示:


(3)验证码存储(services/VerificationCodeRedis.js)

验证码发送服务逻辑中调用封装好验证码存储工具(/VerificationCodeRedis.js)

javascript 复制代码
//services/VerificationCodeRedis.js
/**
 * 验证码校验和存储相关-redis存储
 */

const redis = require("../config/redis.js");
const dayjs = require("dayjs");

// 验证码Key前缀
const CODE_KEY_PREFIX = "email:code:";
// 短时间内发送限制Key前缀
const LIMIT_KEY_PREFIX = "email:limit:";
// 每天发送次数限制Key前缀
const DAILY_COUNT_KEY_PREFIX  = "email:day:";

/**
 * 存储验证码
 * @param {*} email     邮箱
 * @param {*} code      验证码
 */
const setVerifyCode = async (email, code) => {
  const key = `${CODE_KEY_PREFIX}${email}`;
  // 存储验证码,设置过期时间
  await redis.set(key, code, "EX", process.env.VERIFICATION_CODE_EXPIRE);
};

/**
 * 获取验证码
 * @param {*} email     邮箱
 */
const getVerifyCode = async (email) => {
  const key = `${CODE_KEY_PREFIX}${email}`;
  return await redis.get(key);
};

/**
 * 删除验证码
 * @param {*} email     邮箱
 */
const delVerifyCode = async (email) => {
  const key = `${CODE_KEY_PREFIX}${email}`;
  return await redis.del(key);
};

/**
 * 验证码短时间(60s)内发送限制验证
 * @param {*} email     邮箱
 */
const checkSendLimit = async (email) => {
  const key = `${LIMIT_KEY_PREFIX}${email}`;
  const exists = await redis.exists(key);
  return !exists
};

/**
 * 设置验证码短时间(60s)内是否发送过
 * @param {*} email     邮箱
 */
const setSendLimit = async (email) => {
  const key = `${LIMIT_KEY_PREFIX}${email}`;
  await redis.set(key, "1", "EX", process.env.VERIFICATION_SEND_LIMIT_EXPIRE);
};


/**
 * 验证码每天发送次数限制验证
 * @param {*} email     邮箱
 */
const checkDailyAttemptsLimit = async (email) => {
  const today = dayjs().format("YYYY-MM-DD");
  const key = `${DAILY_COUNT_KEY_PREFIX}${email}:${today}`;
  const count = Number((await redis.get(key)) ?? 0);
  return count < Number(process.env.MAX_ATTEMPTS_PER_DAY)
};

/**
 * 设置验证码每天发送次数
 * @param {*} email     邮箱
 */
const setDailyAttemptsCount = async (email) => {
  const today = dayjs().format("YYYY-MM-DD");
  const key = `${DAILY_COUNT_KEY_PREFIX}${email}:${today}`;
  const count = Number((await redis.get(key)) ?? 0);
  await redis.set(key, count + 1, "EX", 24 * 60 * 60);
};

module.exports = {
  setVerifyCode,
  getVerifyCode,
  delVerifyCode,
  checkSendLimit,
  setSendLimit,
  checkDailyAttemptsLimit,
  setDailyAttemptsCount
};

说明:

验证码的存储设计直接影响系统的安全性和性能。我们选择Redis作为存储介质,原因如下:

  • 高性能:内存操作,响应迅速
  • 自动过期:内置TTL支持,无需手动清理
  • 原子操作:保证数据一致性

email:code:【邮箱】、email:limit:【邮箱】、email:day:【日期】这种键设计有几个优点:

  • 不同类型的key使用不同前缀,避免冲突
  • 时间维度:每日限制使用日期作为key的一部分
  • 自动清理:依赖Redis的过期机制,无需手动清理

环境配置

app.js引入dotenv来管理环境变量,dotenv是一个流行的模块,它允许我们将环境变量从.env文件加载到process.env中。

javascript 复制代码
//app.js
require("dotenv").config(); // 在最顶部引入并配置

使用环境变量管理敏感信息,满足安全配置需求:

.env

javascript 复制代码
#env
# 服务器配置
PORT=3001 # 端口
NODE_ENV=development #开发环境



# 邮箱配置
EMAIL_HOST=smtp.qq.com          # QQ邮箱SMTP服务器地址(固定)
EMAIL_PORT=465                  # SSL加密端口(推荐)
EMAIL_USER=xxx@qq.com           # 发件人邮箱地址(改成自己)
EMAIL_PASS=zsqpzljnqnxxxxx      # 邮箱授权码(非登录密码)(改成自己的,qq邮箱后台找)
EMAIL_FROM=xxx@qq.com           # 显示的发件人地址 (改成自己)


# 验证码配置
VERIFICATION_CODE_EXPIRE=300 # 验证码过期时间 5分钟
VERIFICATION_SEND_LIMIT_EXPIRE=60  # 重复发送限制时间60秒
MAX_ATTEMPTS_PER_DAY=10 # 每日最多发送10次

config/email.js(QQ邮箱的SMTP配置)

javascript 复制代码
// QQ邮箱专用配置
const QQ_EMAIL_CONFIG = {
    // QQ邮箱固定配置
    host: process.env.EMAIL_HOST,
    port: process.env.EMAIL_PORT,
    secure: true, // 使用SSL
    
    // 认证信息(从环境变量获取)
    auth: {
      user: process.env.EMAIL_USER,
      pass: process.env.EMAIL_PASS
    },
    
    // 连接配置
    connectionTimeout: 10000, // 10秒连接超时
    greetingTimeout: 10000,   // 10秒问候超时
    socketTimeout: 60000,     // 60秒socket超时
    
    // TLS配置
    tls: {
      rejectUnauthorized: false // 允许自签名证书
    },
    
    // 调试模式
    debug: process.env.NODE_ENV === 'development',
    logger: process.env.NODE_ENV === 'development'
  };


  module.exports = QQ_EMAIL_CONFIG;

除了使用qq邮箱外,常见邮件服务商还有163邮箱、Gmail、Outlook等,生产环境建议使用企业邮箱服务,避免个人邮箱限制。

相关依赖安装

(1) argon2 (哈希加密)

javascript 复制代码
npm install argon2

(2)nodemailer (邮件发送)

javascript 复制代码
npm install nodemailer

(3)dotenv(环境变量)

javascript 复制代码
npm install dotenv

QQ邮箱授权码获取

QQ邮箱使用SMTP服务时,需要使用授权码(.env 文件里EMAIL_PASS变量)

步骤:

(1)登录QQ邮箱网页版

(2)右上角进入设置

(3)选择左侧菜单"账户与安全"选项卡。

(4)选择左侧菜单"安全设置"选项卡。

(5)开启POP3/IMAP/SMTP/Exchange/CardDAV 服务

(6)成功开启后,页面会显示一个16位的授权码,立即复制并保存这个授权码(只显示一次!)


总结

通过本文的详细解析,我们看到了如何将最初定义的14个功能需求转化为具体的技术实现,构建一个完整的安全的邮箱验证注册系统。在实际项目中,建议根据具体业务场景调整参数设置,如验证码长度、有效期、发送限制等。

相关推荐
噗噗夹的TA之旅9 小时前
Unity Shader 学习20:URP LitForwardPass PBR 解析
学习·unity·游戏引擎·图形渲染·技术美术
小胖霞9 小时前
企业级全栈项目(14) winston记录所有日志
vue.js·前端框架·node.js
栀秋6669 小时前
“无重复字符的最长子串”:从O(n²)哈希优化到滑动窗口封神,再到DP降维打击!
前端·javascript·算法
2401_834517079 小时前
AD学习笔记-36 gerber文件输出
笔记·学习
xhxxx9 小时前
不用 Set,只用两个布尔值:如何用标志位将矩阵置零的空间复杂度压到 O(1)
javascript·算法·面试
有意义9 小时前
斐波那契数列:从递归到优化的完整指南
javascript·算法·面试
气π9 小时前
【JavaWeb】——(若依 + AI)-基础学习笔记
java·spring boot·笔记·学习·java-ee·mybatis·ruoyi
深蓝海拓9 小时前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
暗然而日章9 小时前
C++基础:Stanford CS106L学习笔记 8 继承
c++·笔记·学习
2401_834517079 小时前
AD学习笔记-34 PCBlogo的添加
笔记·学习