导语:之前做过一个小项目,其中用到了图形验证码,邮箱和手机号注册登录,这三者基本上是现在网站常用的验证方法,现在就做一个使用操作总结。
继续打开上次新建的demo
文件夹,下载几个依赖包。
npm install svg-captcha nodemailer tencentcloud-sdk-nodejs --save
除了图形验证码可以安装后直接使用外,其他两个必须向邮箱服务商和云计算运营商申请授权密钥。
推荐网站:
QQ邮箱申请步骤:
打开QQ邮箱登录进去;
点击设置,然后打开账户选项卡,点击开启POP3/SMTP服务;
发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587
网易邮箱申请步骤:
打开163邮箱登录进去;
点击设置,然后打开设置选项卡,点击POP3/SMTP/IMAP;
本次使用的是腾讯云提供的短信服务。
按照上面操作完成后,你可以得到应用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); });
如果想要体验这个功能,这里有个小奇工具应用,可以进行注册和登录以及图形、邮箱和手机验证。
以上就是我开发的一些经验总结,如果有什么问题,请邮箱联系我,我一定抽出时间查看,不对的地方进行修正。