在實(shí)際開(kāi)發(fā)中坑傅,可能會(huì)遇到注冊(cè)時(shí)提供郵箱驗(yàn)證碼的需求僵驰。在Node.js中是可以實(shí)現(xiàn)的。以下分享一些我的開(kāi)發(fā)經(jīng)驗(yàn)。
開(kāi)發(fā)環(huán)境
- Node.js
- Express
- Mongoose
- MongoDB
郵箱驗(yàn)證碼思路
驗(yàn)證碼發(fā)送階段
客戶端上傳郵箱地址到服務(wù)端蒜茴,服務(wù)端通過(guò)生成隨機(jī)驗(yàn)證碼星爪,并將這個(gè)驗(yàn)證碼發(fā)送到用戶上傳的這個(gè)郵箱中。此時(shí)服務(wù)端需要將這個(gè)郵箱和驗(yàn)證碼保存到數(shù)據(jù)庫(kù)的某張表中(后文中使用Code來(lái)稱呼這個(gè)表)粉私,同時(shí)保證這條記錄的唯一性顽腾。并在有效時(shí)間內(nèi)將這條記錄刪除(此時(shí)間就是驗(yàn)證碼的有效時(shí)間)
驗(yàn)證碼驗(yàn)證階段
客戶端將收到的驗(yàn)證碼和郵箱重新發(fā)送到服務(wù)端,服務(wù)端開(kāi)始驗(yàn)證:
- 是否能夠從Code中查詢到這條驗(yàn)證碼和郵箱與客戶端發(fā)送一致的記錄
- 是:驗(yàn)證通過(guò)诺核,并刪除這條記錄
- 否:驗(yàn)證不通過(guò)
驗(yàn)證碼發(fā)送階段
安裝
安裝提供發(fā)送郵件的模塊:nodemailer
崔泵、nodemailer-smtp-transport
npm i nodemailer nodemailer-smtp-transport -save
使用
引入發(fā)送郵件的模塊
const nodemailer = require('nodemailer')
const smtpTransport = require('nodemailer-smtp-transport')
創(chuàng)建連接對(duì)象
const transport = nodemailer.createTransport(smtpTransport({
host: 'smtp.163.com', // 服務(wù) 由于我用的163郵箱
port: 465, // smtp端口 默認(rèn)無(wú)需改動(dòng)
secure: true,
auth: {
user: 'crackerlink@163.com', // 用戶名
pass: 'xxxxxxxxx' // SMTP授權(quán)碼
}
}));
host:如果發(fā)件使用sina郵箱的話就填寫(xiě) smtp.sina.com;qq郵箱同理
secure:如果為true猪瞬,則連接到服務(wù)器時(shí)連接將使用TLS。如果為false(默認(rèn)值)入篮,則在服務(wù)器支持STARTTLS擴(kuò)展名的情況下使用TLS陈瘦。在大多數(shù)情況下,如果要連接到端口465潮售,請(qǐng)將此值設(shè)置為true痊项。對(duì)于端口587或25,請(qǐng)將其保留為false
-
auth:發(fā)件人身份驗(yàn)證對(duì)象
- user:用戶名
- pass:SMTP授權(quán)碼 (通常在郵箱網(wǎng)站的設(shè)置里)
- login:認(rèn)真類(lèi)型,默認(rèn)login(普通用戶無(wú)需填寫(xiě)這一項(xiàng))
生成隨機(jī)驗(yàn)證碼函數(shù) 及 郵箱驗(yàn)證正則
const randomFns=()=> { // 生成6位隨機(jī)數(shù)
let code = ""
for(let i= 0;i<6;i++){
code += parseInt(Math.random()*10)
}
return code
}
const regEmail=/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/ //驗(yàn)證郵箱正則
建立連接發(fā)送驗(yàn)證碼
let EMAIL=req.body.e_mail //req為請(qǐng)求體對(duì)象 我使用的是post請(qǐng)求方式酥诽,所以通過(guò)req.body獲取用戶提交的郵箱
if (regEmail.test(EMAIL)){ //郵箱驗(yàn)證通過(guò)
let code=randomFns()
transport.sendMail({
from: 'crackerlink@163.com', // 發(fā)件郵箱
to: EMAIL, // 收件列表
subject: '驗(yàn)證你的電子郵件', // 標(biāo)題
html: `
<p>你好鞍泉!</p>
<p>您正在注冊(cè)Cracker社區(qū)賬號(hào)</p>
<p>你的驗(yàn)證碼是:<strong style="color: #ff4e2a;">${code}</strong></p>
<p>***該驗(yàn)證碼5分鐘內(nèi)有效***</p>` // html 內(nèi)容
},
function(error, data) {
assert(!error,500,"發(fā)送驗(yàn)證碼錯(cuò)誤!")
transport.close(); // 如果沒(méi)用肮帐,關(guān)閉連接池
})
//....驗(yàn)證碼發(fā)送后的相關(guān)工作
}else{
assert(false,422,'請(qǐng)輸入正確的郵箱格式咖驮!')
}
驗(yàn)證碼發(fā)送后的相關(guān)工作
接下來(lái)需要將這個(gè)郵箱和驗(yàn)證碼保存到Code中,同時(shí)保證這條記錄的唯一性训枢。并在5分鐘內(nèi)將這條記錄刪除
const Code = require("../models/Code")
const e_mail = EMAIL
await Code.deleteMany({e_mail}) //刪除該舊的驗(yàn)證碼托修,保證該郵箱是最新的驗(yàn)證碼有效
const [data] = await Code.insertMany({e_mail,veri_code:code}) //插入新郵箱驗(yàn)證碼組合
setTimeout(async ()=>{ //5分鐘后刪除
await Code.deleteMany({e_mail})
},1000*60*5)
發(fā)送階段完整代碼
(推薦將其封裝為中間件)
module.exports = app=>{
const nodemailer = require('nodemailer')
const smtpTransport = require('nodemailer-smtp-transport')
const assert = require('http-assert')
const transport = nodemailer.createTransport(smtpTransport({
host: 'smtp.163.com', // 服務(wù)
port: 465, // smtp端口
secure: true,
auth: {
user: 'crackerlink@163.com', //用戶名
pass: 'xxxxxxx' // SMTP授權(quán)碼
}
}));
const randomFns=()=> { // 生成6位隨機(jī)數(shù)
let code = ""
for(let i= 0;i<6;i++){
code += parseInt(Math.random()*10)
}
return code
}
const regEmail=/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/ //驗(yàn)證郵箱正則
return async(req,res,next)=>{
let EMAIL=req.body.e_mail
if (regEmail.test(EMAIL)){
let code=randomFns()
transport.sendMail({
from: 'crackerlink@163.com', // 發(fā)件郵箱
to: EMAIL, // 收件列表
subject: '驗(yàn)證你的電子郵件', // 標(biāo)題
html: `
<p>你好!</p>
<p>您正在注冊(cè)Cracker社區(qū)賬號(hào)</p>
<p>你的驗(yàn)證碼是:<strong style="color: #ff4e2a;">${code}</strong></p>
<p>***該驗(yàn)證碼5分鐘內(nèi)有效***</p>` // html 內(nèi)容
},
function(error, data) {
assert(!error,500,"發(fā)送驗(yàn)證碼錯(cuò)誤恒界!")
transport.close(); // 如果沒(méi)用睦刃,關(guān)閉連接池
})
const Code = require("../models/Code")
const e_mail = EMAIL
await Code.deleteMany({e_mail})
const [data] = await Code.insertMany({e_mail,veri_code:code})
setTimeout(async ()=>{ //5分鐘后失效
await Code.deleteMany({e_mail})
},1000*60*5)
}else{
assert(false,422,'請(qǐng)輸入正確的郵箱格式!')
}
next()
}
}
驗(yàn)證碼驗(yàn)證階段
驗(yàn)證階段只需要通過(guò)獲取用戶得郵箱和驗(yàn)證碼是否在Code中查詢到即可
router.post('/new',async (req,res)=>{
const {e_mail,veri_code} = req.body
// 驗(yàn)證碼驗(yàn)證
const vire = await require('../../models/Code').findOne({e_mail,veri_code})
assert(vire,422,'驗(yàn)證碼出錯(cuò)')
await require('../../models/Code').deleteMany({e_mail})
return res.send({message:"驗(yàn)證碼正確"})
})