在写用邮箱注册的接口,需要通过邮箱发送验证码给用户。这里记录如何使用Nodemailer给邮箱用户发送验证码。
Nodemailer是一个用于Node.js应用程序的模块,可以方便地发送邮件。该项目开始于2010年,当时还没有发送电子邮件消息的明智选择,今天它是大多数Node.js用户默认使用的解决方案。
npm i nodemailer
代码如下(示例):
// controller/util.ts
const nodemailer = require("nodemailer");
到邮箱设置中寻找到”开启POP3/SMTP服务“,这里我用的是qq邮箱,在设置->账户->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,找到后点击开启
不同的邮箱host的配置也不一样,qq邮箱可参考:常用邮件客户端软件设置
let transporter = nodemailer.createTransport({
host: "smtp.qq.com",
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: "...@qq.com", // 发送者邮箱
pass: "...", // 邮箱对应的服务授权码
},
});
let info = {
from: "386086310@qq.com", // 谁发
to: "386086310@qq.com", // 发给谁
subject: "请查收:后台管理系统注册验证码", // 邮件标题
text: `您正在注册我们的后台管理系统,您的验证码是:${code}`, // 邮件内容
// html: "<b>Hello world?</b>", // 以 html 形式编写邮件内容
};
let code = Math.random().toString(16).slice(2, 6).toUpperCase();
transporter.sendMail(info, (err, data) => {
if (err) {
console.log(`发送邮件失败:${err}`);
} else {
console.log(`发送邮件成功:${data}`);
}
});
本文简单介绍了Nodemailer的使用,这里附上封装好的代码。
// util/emailCode.ts
const nodemailer = require("nodemailer");
let transporter;
export default {
// 创建发送邮件对象
createTransporterInstance(ctx) {
if (transporter) {
return transporter;
}
transporter = nodemailer.createTransport({
host: ctx.app.config.smtp.host,
port: ctx.app.config.smtp.port,
secure: ctx.app.config.smtp.secure, // true for 465, false for other ports
auth: {
user: ctx.app.config.smtp.user, // 发送者邮箱
pass: ctx.app.config.smtp.pass, // 邮箱对应的服务授权码
},
});
return transporter;
},
// 创建发送的内容
createEmailInfo(ctx, to: string) {
let code = Math.random().toString(16).slice(2, 6).toUpperCase();
let info = {
from: ctx.app.config.smtp.user, // 谁发
to: to, // 发给谁
subject: "请查收:后台管理系统注册验证码", // 邮件标题
text: `您正在注册我们的后台管理系统,您的验证码是:${code}`, // 邮件内容
// html: "<b>Hello world?</b>", // 以 html 形式编写邮件内容
};
ctx.session.email = {
code: code,
expire: Date.now() + 60 * 1000, // 1min之后过期
};
console.log(code);
return info;
},
// 发送验证码
async sendEmailCode(ctx, to: string) {
const transporter = this.createTransporterInstance(ctx);
const info = this.createEmailInfo(ctx, to);
return new Promise((resolve, reject) => {
transporter.sendMail(info, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
},
verifyEmailCode(ctx, clientCode) {
// 1.取出服务端中保存的验证码和过期时间
const serverEmail = ctx.session.email;
console.log(serverEmail);
let serverCode;
let serverExpire;
// 无论成功还是失败验证码只能用一次 需要清空验证码缓存
try {
serverCode = serverEmail.code;
serverExpire = serverEmail.expire;
} catch (error) {
ctx.session.email = null;
throw new Error("请重新获取验证码");
}
console.log(serverCode, serverExpire, clientCode);
if (Date.now() > serverExpire) {
ctx.session.email = null;
throw new Error("验证码过期");
} else if (serverCode !== clientCode) {
// 与客户端传递过来的验证码进行比对
ctx.session.email = null;
throw new Error("验证码不正确");
}
},
};
将逻辑代码抽离在 helper.ts 里面成为一个独立的函数,可以避免逻辑散乱。
import EmailCode from "../util/emailCode";
// this.ctx
module.exports = {
async sendEmailCode(to: string) {
return await EmailCode.sendEmailCode(this.ctx, to);
},
verifyEmailCode(clientCode) {
EmailCode.verifyEmailCode(this.ctx, clientCode);
},
};
import { Controller } from "egg";
export default class UtilController extends Controller {
public async emailCode() {
const { ctx } = this;
try {
const { email } = ctx.query;
const data = await ctx.helper.sendEmailCode(email);
ctx.success(data);
} catch (e) {
ctx.error(400, e.message);
}
}
}
switch (registerType) {
// ...
case RegisterTypeEnum.Email:
ctx.validate(EmailUserRule, data);
ctx.helper.verifyEmailCode(data.captcha);
break;
// ...
default:
throw new Error("注册类型不存在!");
}