关于node.js:Node图形邮箱手机验证码实现方法总结

31次阅读

共计 8964 个字符,预计需要花费 23 分钟才能阅读完成。

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

目录

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

筹备工作

装置依赖包

持续关上上次新建的 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);
});

在线体验

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

写在最初

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

正文完
 0