在Node.js中實(shí)現(xiàn)郵箱驗(yàn)證碼的發(fā)送與驗(yàn)證

在實(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)證碼正確"})

})
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末十酣,一起剝皮案震驚了整個(gè)濱河市涩拙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耸采,老刑警劉巖兴泥,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洋幻,居然都是意外死亡郁轻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)好唯,“玉大人竭沫,你說(shuō)我怎么就攤上這事∑锔荩” “怎么了蜕提?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)靶端。 經(jīng)常有香客問(wèn)我谎势,道長(zhǎng),這世上最難降的妖魔是什么杨名? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任脏榆,我火速辦了婚禮,結(jié)果婚禮上台谍,老公的妹妹穿的比我還像新娘须喂。我一直安慰自己,他們只是感情好趁蕊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布坞生。 她就那樣靜靜地躺著,像睡著了一般掷伙。 火紅的嫁衣襯著肌膚如雪是己。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天任柜,我揣著相機(jī)與錄音卒废,去河邊找鬼。 笑死乘盼,一個(gè)胖子當(dāng)著我的面吹牛升熊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绸栅,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼级野,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了粹胯?” 一聲冷哼從身側(cè)響起蓖柔,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎风纠,沒(méi)想到半個(gè)月后况鸣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竹观,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年镐捧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了潜索。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懂酱,死狀恐怖竹习,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情列牺,我是刑警寧澤整陌,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站瞎领,受9級(jí)特大地震影響泌辫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜九默,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一震放、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驼修,春花似錦澜搅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)癌瘾。三九已至觅丰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妨退,已是汗流浹背妇萄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咬荷,地道東北人冠句。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像幸乒,于是被迫代替她去往敵國(guó)和親懦底。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348