导语:之前做过一个小我的项目,其中用到了图形验证码,邮箱和手机号注册登录,这三者基本上是当初网站罕用的验证办法,当初就做一个应用操作总结。

目录

  • 筹备工作
  • 原理解析
  • 办法总结
  • 在线体验

筹备工作

装置依赖包

持续关上上次新建的demo文件夹,下载几个依赖包。

npm install svg-captcha nodemailer tencentcloud-sdk-nodejs --save
  • svg-captcha 能够创立图形验证码
  • nodemailer 能够发送电子邮件
  • tencentcloud-sdk-nodejs 能够发送短信

申请筹备

除了图形验证码能够装置后间接应用外,其余两个必须向邮箱服务商和云计算运营商申请受权密钥。

电子邮件申请办法

举荐网站:

  • QQ邮箱
  • 网易邮箱

QQ邮箱申请步骤:

  • 关上QQ邮箱登录进去;
  • 点击设置,而后关上账户选项卡,点击开启POP3/SMTP服务;

  • 点击生成受权码,发送短信,点击我已发送,复制受权码到一个记事本外面去;

发送邮件服务器:smtp.qq.com,应用SSL,端口号465或587

网易邮箱申请步骤

  • 关上163邮箱登录进去;
  • 点击设置,而后关上设置选项卡,点击POP3/SMTP/IMAP;


  • 点击开启POP3/SMTP服务,发送短信,点击我已发送,复制受权码到一个记事本外面去;


手机短信申请办法

本次应用的是腾讯云提供的短信服务。

  • 关上腾讯云官网注册登录进去;
  • 关上短信产品页面,当初是618流动期间,购买短信,大概1000条,38元;

  • 而后点击控制台到短信控制台页面查看短信套餐;

  • 接下来就是短信开明配置,参考这篇文章国内短信疾速入门;

依照下面操作实现后,你能够失去利用id和模板id。

到此,这个整个申请流程就完结了。

原理解析

基本上几个验证形式都大同小异,能够独特演绎为以下几个步骤:

  • 创立/发送验证码内容
  • 验证是否正确

上面具体形容一下这个步骤。

创立/发送验证码内容

  • 图形验证码

当咱们去申请一个api地址的时候,首先引入依赖包,配置好参数,生成一个svg格局的图形,而后响应申请,发送svg数据,就能够看到一个N位字符的图片了。

  • 邮箱验证码

到引入邮箱依赖包,配置好参数,而后去调用发送邮件办法,最初关上申请发送的邮箱账户,就能够看到一封电子邮件。

  • 手机

到引入手机依赖包,配置好参数,而后去调用发送短信办法,最初关上申请发送的手机短信app,就能够看到一条短信了。

验证是否正确

  • 客户端提交参数;
  • 服务端检测是否输出正确,返回提示信息;

办法总结

本大节次要是封装一些罕用的办法,而后编写脚本文件。

关上demo文件夹,创立一个captcha的文件夹,而后新建config.js,次要是搁置一些配置信息;新建一个api.js,次要是搁置一些罕用办法。

而后新建svg,email,phone三个文件夹,并且各自新建index.js文件。

get申请形式用于发送验证码,post申请形式用于验证验证码。

罕用办法

关上config.js文件:

// 图形,邮箱,手机的验证码const svg = {    size: 4, // 验证码长度    ignoreChars: '012oOiILl', // 验证码字符中排除 0o1i    noise: 1, // 烦扰线条的数量    fontSize: 52,    color: true, //开启文字色彩    // background:"#000",//背景色    width: 200,    height: 80,    time: 2*60,}const email = {    service: 'qq',    port: 465,    secure: true,    user: 'xxx@xx.com',    pass: 'xxxxxxxxxxxxxx',    from: 'xxx@xx.com',    time: 2*60,}const phone = {    secretId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',    secretKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',    reqMethod: "POST",    reqTimeout: 30,    endpoint: "sms.tencentcloudapi.com",    signMethod: "HmacSHA256",    region: "ap-shanghai",    SmsSdkAppid: "XXXXXXX", // 利用id    Sign: "DEMO",    ExtendCode: "",    SenderId: "",    SessionContext: "",    TemplateID: {        eg: "XXXXXX",    },    time: 2*60,}module.exports = {    svg,    email,    phone}

关上api.js文件:

  • 检测办法
//一个检测验证码是否正确的办法;/**infoType 检测类型: svg,email,phone*codeInfo 服务端的验证码 session信息*verifyInfo 客户端提交的验证码信息*/function check (res, req, infoType, codeInfo, verifyInfo) {    let type = infoType == 'svg' ? 'svgInfo' :    infoType == 'email' ? 'emailInfo' : 'phoneInfo';    let typeText = infoType == 'svg' ? '图形' :    infoType == 'email' ? '邮箱' : '手机';    if (!Object.keys(codeInfo).length) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: `请从新获取${typeText}验证码!`            }        })    }    if (infoType != 'phone') {        codeInfo.code = codeInfo.code.toLowerCase();    }    if (!verifyInfo.code) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: `${typeText}验证码不能为空!`            }        })    }    if (infoType == 'email' ||    infoType == 'phone') {        if (!verifyInfo[infoType]) {            return res.json({                code: 101,                msg: 'get_fail',                data: {                    info: `${typeText}不能为空!`                }            })        }        if (verifyInfo[infoType] != codeInfo[infoType]) {            return res.json({                code: 101,                msg: 'get_fail',                data: {                    info: `${typeText}账号谬误!`                }            })        }    }    if (codeInfo.isVerify == 1) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: `${typeText}验证码曾经验证!`            }        })    }    if (((verifyInfo.time - codeInfo.time)/1000) > 60) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: `${typeText}验证码曾经过期!`            }        })    }    if (verifyInfo.code != codeInfo.code) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: `${typeText}验证码谬误!`            }        })    }    req.session[type].isVerify = 1;    return res.json({        code: 200,        msg: 'get_succ',        data: {            info: `${typeText}验证码验证胜利!`        }    })}
  • 发送邮件信息
const nodemailer = require('nodemailer');const emailConfig = require('./config').email;// option 配置参数function sendMail (res, option) {    let transporter = nodemailer.createTransport({        service: emailConfig.service,        port: 465,        secureConnection: true,        auth: {            user: emailConfig.user,            pass: emailConfig.pass        }    })    return transporter.sendMail(option, (error, info) => {        if (error) {            return res.json({                code: 101,                msg: 'get_fail',                data: {                    info: '发送失败,请重试!',                    des: error                }            })        } else {            return res.json({                code: 200,                msg: 'get_succ',                data: {                    info: '发送胜利,请留神查收!'                }            })        }    })}
  • 发送手机短信
/** phoneNo 手机号* type 模板类型* phoneCode 6位数字验证码*/const tencentcloud = require('tencentcloud-sdk-nodejs');const phoneConfig = require('./config').phone;function sendSms (phoneNo, type, phoneCode) {    // 导入对应产品模块的client models。    const smsClient = tencentcloud.sms.v20190711.Client    /* 实例化要申请产品(以sms为例)的client对象 */    const client = new smsClient({        credential: {            /* 必填:腾讯云账户密钥对secretId,secretKey。             * 这里采纳的是从环境变量读取的形式,须要在环境变量中先设置这两个值。             * 你也能够间接在代码中写死密钥对,然而小心不要将代码复制、上传或者分享给别人,             * 免得泄露密钥对危及你的财产平安。             * CAM密匙查问: https://console.cloud.tencent.com/cam/capi */            secretId: phoneConfig.secretId,            secretKey: phoneConfig.secretKey,        },        /* 必填:地区信息,能够间接填写字符串ap-guangzhou,或者援用预设的常量 */        region: phoneConfig.region,        /* 非必填:         * 客户端配置对象,能够指定超时工夫等配置 */        profile: {            /* SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要批改这个字段 */            signMethod: phoneConfig.signMethod,            httpProfile: {                /* SDK默认应用POST办法。                 * 如果你肯定要应用GET办法,能够在这里设置。GET办法无奈解决一些较大的申请 */                reqMethod: phoneConfig.reqMethod,                /* SDK有默认的超时工夫,非必要请不要进行调整                 * 如有须要请在代码中查阅以获取最新的默认值 */                reqTimeout: phoneConfig.reqTimeout,                /**                 * SDK会主动指定域名。通常是不须要顺便指定域名的,然而如果你拜访的是金融区的服务                 * 则必须手动指定域名,例如sms的上海金融区域名: sms.ap-shanghai-fsi.tencentcloudapi.com                 */                endpoint: phoneConfig.endpoint            },        },    })    /* 申请参数,依据调用的接口和理论状况,能够进一步设置申请参数     * 属性可能是根本类型,也可能援用了另一个数据结构     * 举荐应用IDE进行开发,能够不便的跳转查阅各个接口和数据结构的文档阐明 */    const smsParams = {        /* 短信利用ID: 短信SdkAppid在 [短信控制台] 增加利用后生成的理论SdkAppid,示例如1400006666 */        SmsSdkAppid: phoneConfig.SmsSdkAppid,        /* 短信签名内容: 应用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看 */        Sign: phoneConfig.Sign,        /* 短信码号扩大号: 默认未开明,如需开明请分割 [sms helper] */        ExtendCode: phoneConfig.ExtendCode,        /* 国内/港澳台短信 senderid: 国内短信填空,默认未开明,如需开明请分割 [sms helper] */        SenderId: phoneConfig.SenderId,        /* 用户的 session 内容: 能够携带用户侧 ID 等上下文信息,server 会原样返回 */        SessionContext: phoneConfig.SessionContext,        /* 下发手机号码,采纳 e.164 规范,+[国家或地区码][手机号]         * 示例如:+8613711112222, 其中后面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/        PhoneNumberSet: [`+86${phoneNo}`],        /* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */        TemplateID: phoneConfig.TemplateID[type],        /* 模板参数: 若无模板参数,则设置为空*/        TemplateParamSet: [phoneCode],    }    // 通过client对象调用想要拜访的接口,须要传入申请对象以及响应回调函数    return new Promise(function (resolve, reject) {        // 通过 client 对象调用想要拜访的接口,须要传入申请对象以及响应回调函数        client.SendSms(smsParams, function (err, response) {            // 申请异样返回,打印异样信息            if (err) {                reject({                    code: 102,                    info: 'get_fail',                    data: {                        info: '操作失败!',                        detail: err                    }                });            }            resolve({                code: 200,                info: 'get_succ',                data: {                    info: '操作胜利!',                    response                }            });        });    })}

验证码程序

图形验证码

写入以下代码:

const express = require('express');const app = express();const svgCaptcha = require('svg-captcha');const config = require('../config');const api = require('../api');app.get('/svg', (req, res) => {        // 创立图像    const svgImg = svgCaptcha.create(config.svg);    req.session.svgInfo = {        code: svgImg.text,        time: new Date().getTime(),        isVerify: 0,    };    // 发送    res.type('svg');    res.status(200).send(svgImg.data);});app.post('/svg', (req, res) => {    // 验证信息    let svgInfo = {...req.session.svgInfo};    let code = req.body.code;    let verifyInfo = {        code,        time: new Date().getTime(),    };    // 检测信息    return api.check(res, req, 'svg', svgInfo, verifyInfo);});

邮箱验证码

const express = require('express');const app = express();const config = require('../config');const api = require('../api');app.get('/email', (req, res) => {    // 验证参数    let email = req.query.email;    if (!email) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: '邮箱账号不能为空!'            }        })    }        // 邮箱配置    let emailInfo = {...req.session.emailInfo};    console.log('get email info:', emailInfo);    let emailParams = {        email,        time: new Date().getTime(),    };    let emailConfig = config.email;    let emailCode = (Math.random() * Math.pow(52, 2)).toString(36).slice(4, 10);    let option = {        from: `"前端实验室" <${emailConfig.from}>`,        to: email,        subject: '邮箱验证码',        text: `尊敬的用户您好:您的邮箱验证码是${emailCode},有效期${emailConfig.time/60}分钟,请尽快应用!`,        html: ''    }    // 邮箱验证码检测    if (emailInfo &&        email === emailInfo.email &&        ((emailInfo.time - emailParams.time)/1000)  < emailConfig.time) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: '该邮箱验证码已发送!'            }        })    } else {        let emailInfo = {            code: emailCode,            email,            time: new Date().getTime(),            isVerify: 0,        };        console.log('save email info:', emailInfo);        req.session.emailInfo = emailInfo;    }    // 发送邮件    return api.sendMail(res, option);});app.post('/email', (req, res) => {    let emailInfo = {        ...req.session.emailInfo    };    let code = req.body.code;    let email = req.body.email;    let verifyInfo = {        email,        code,        time: new Date().getTime(),    };    return api.check(res, req, 'email', emailInfo, verifyInfo);});

手机验证码

const express = require('express');const app = express();const config = require('../config');const api = require('../api');app.get('/phone', async (req, res) => {    // 验证参数    let phone = req.query.phone;    if (!phone) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: '手机账号不能为空!'            }        })    }    // 手机配置    let phoneInfo = {...req.session.phoneInfo};    console.log('get phone info:', phoneInfo);    let phoneParams = {        phone,        time: new Date().getTime(),    };    let phoneConfig = config.phone;    let phoneCode = Math.ceil(Math.random() * 1000000);    // 手机验证码检测    if (phoneInfo &&        phone === phoneInfo.phone &&        ((phoneInfo.time - phoneParams.time)/1000)  < phoneConfig.time) {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: '该手机验证码已发送!'            }        })    } else {        let phoneInfo = {            code: phoneCode,            phone,            time: new Date().getTime(),            isVerify: 0,        };        console.log('save phone info:', phoneInfo);        req.session.phoneInfo = phoneInfo;    }    // 发送手机    let phoneSet = await api.sendSms(phone, 'eg', phoneCode);    if (phoneSet.code === 200) {        return res.json({            code: 200,            msg: 'get_succ',            data: {                info: '发送胜利,请留神查收!',                err: phoneSet.data.detail            }        })            } else {        return res.json({            code: 101,            msg: 'get_fail',            data: {                info: '发送失败,请重试!'            }        })    }});app.post('/phone', (req, res) => {    let phoneInfo = {        ...req.session.phoneInfo    };    let code = req.body.code;    let phone = req.body.phone;    let verifyInfo = {        phone,        code,        time: new Date().getTime(),    };    return api.check(res, req, 'phone', phoneInfo, verifyInfo);});

在线体验

如果想要体验这个性能,这里有个小奇工具利用,能够进行注册和登录以及图形、邮箱和手机验证。

写在最初

以上就是我开发的一些经验总结,如果有什么问题,请邮箱分割我,我肯定抽出工夫查看,不对的中央进行修改。