今天咱们来继续完善上一篇的 注册教程,在现实注册过程当中,手机短信验证码是必不可少的。那么怎么实现呢?前端
首先咱们须要在短信平台开通短信服务功能,大的平台主要有阿里云、腾讯云、聚合数据等( 通常须要拿到短信模板ID、APPID、发送连接便可 ),通常费用在每条0.04元左右,比较大的平台天天须要发送几千、甚至上万条,可见天天总的短信验证码也是有必定的成本的,这还排除了存在恶意刷验证码状况。所以在后台编写验证码部分代码时,必定要加上发送的数量限制,好比:每一个手机号天天最多发送条数(好比最多6条)、每一个IP地址天天最多发送条数等,这样即便是遇到恶意刷验证码的状况也能够保证短信数量不至于过多。vue
这篇文章是按照生产环境来写的,代码部分包含了真实的验证码发送部分,只要配置好相应的短信平台参数就能够实现发送验证码。这篇文章做为学习研究用,特地将验证码 alert() 处来。ios
效果图:ajax
弹出六位数验证码,而后发送验证码按钮显示1分钟倒计时,控制台打印后端发来的返回信息。正则表达式
主要部分:数据库
1> 添加发送验证码的条数限制,每一个IP天天最多发送10条、每一个手机号天天最多发送6条,axios
2> 验证码用 Math.random() 、Math.floor() 生成随机六位数,后端
3> 用到 koa-session 将生成的验证码存入session,等到用户点击注册按钮时,将用户输入的验证码与生成的验证码进行比对,api
1、前端部分服务器
1.1 向Register.vue中加入 验证码输入框:
<van-field v-model="smscode" center clearable label="短信验证码" placeholder="请输入短信验证码" > <template #button> <van-button v-if="!cutDownTime" size="small" type="primary" @click="sendSMSCode">发送验证码</van-button> <van-button v-if="cutDownTime" size="small" type="primary">{{cutDownTime}}s后再试</van-button> </template> </van-field>
1.2 添加 sendSMSCode 方法:
async sendSMSCode() { let pattern = /^[1][3,4,5,7,8][0-9]{9}$/g; //正则表达式,验证手机号格式 if(this.telnumber.length != 11 | !pattern.test(this.telnumber)) return this.$toast('请输入正确的手机号') } let res = await ajax.sendSMSCode(this.telnumber);this.cutDownTime = 60; let timer = setInterval(() => { // this.cutDownTime--; if(this.cutDownTime <= 0) { this.cutDownTime = '' } }, 1000)
1.3 修改src\api\index.js,添加方法,将手机号发送到后端:
// 发送 短信验证码 sendSMSCode(telnumber) { return axios.post(Url.sendSMSCodeApi,{telnumber}) }
2、后端部分
2.1 首先,咱们来安装几个须要用到的插件:
2.1.1 silly-datetime
很便捷的设置时间格式插件,安装好之后,咱们在 mall-server\utils 下,新建 tools.js ,用于将用到的工具都封装在这里:
const sd = require('silly-datetime')
/** * 工具封装 */ class Tools { // 格式化当前日期 getCurDate(format = 'YYYYMMDD') { // 默认返回格式:20200529 return sd.format(new Date(),format); } } module.exports = new Tools()
2.1.2 koa-session
安装完毕后,须要在 app.js 中引入:
app.keys = [ 'session secret' ]; // 设置签名的 Cookie 密钥 const CONFIG = { key: 'sessionId', maxAge: 60000, // cookie 的过时时间 60000ms => 60s => 1min overwrite: true, // 是否能够 overwrite (默认 default true) httpOnly: true, // true 表示只有服务器端能够获取 cookie signed: true, // 默认 签名 rolling: false, // 在每次请求时强行设置 cookie,这将重置 cookie 过时时间(默认:false) renew: false, // 在每次请求时强行设置 session,这将重置 session 过时时间(默认:false) }; app.use(session(CONFIG, app));
2.1.3 request:用于简化请求写法
2.1.4 querystring: 用于生成序列化请求路径,用法举例:
let queryData = querystring.stringify({ "mobile": mobilePhone, // 接受短信的用户手机号码 "tpl_id": "187915", // 您申请的短信模板 ID,根据实际状况修改 "tpl_value": `#code#=${ randomNum }`, // 您设置的模板变量,根据实际状况修改 "key": "d52256474eb6d73350e47eb52adbca67", // 应用 APPKEY (应用详细页查询) }); let queryUrl = 'http://v.juhe.cn/sms/send?' + queryData;
2.2 创建手机短信验证码模型,用于验证码发送条数限制统计:
// 手机号数据模型 (用于发送验证码) const mobilePhoneSchema = new Schema({ mobilePhone: Number, // 手机号 clientIp: String, // 客户端 ip sendCount: Number, // 发送次数 curDate: String, // 当前日期 sendTimestamp: { type: String, default: +new Date() }, // 短信发送的时间戳 });
2.3 添加接收前端post路由,接收telnumber:
/** * 发送短信验证码 */ router.post('/sendSMSCode', async function (ctx) { let { telnumber } = ctx.request.body; const clientIp = ctx.req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP ctx.req.connection.remoteAddress || // 判断 connection 的远程 IP ctx.req.socket.remoteAddress || // 判断后端的 socket 的 IP ctx.req.connection.socket.remoteAddress || ''; const curDate = tools.getCurDate(); // 当前时间 // console.log('ip:', clientIp) // console.log('date:', curDate) let args = { telnumber, clientIp, curDate }; try { let smsCodeData = await userService.dispatchSMSCode(args); // 将验证码保存入 session 中 (smsCodeData.code === 200) && (ctx.session.smsCode = smsCodeData.smsCode);
ctx.body = smsCodeData; } catch (error) { console.log(error) } })
2.4 添加 dispatchSMSCode 方法,添加发送条数限制,并调用发送短信API,咱们将发送API封装在sendSMSCode()方法里,在下一步介绍,研究用不调用API,只将验证码 return给前端。
/** * 发送短信验证码 * 一个手机号天天最多发送 6 条验证码 * 同一个 ip,一天只能向手机号码发送 10 次 */ async dispatchSMSCode({ mobilePhone, clientIp, curDate }) { let smsSendMax = 6; // 设定每一个手机号短信发送限制数 let ipCountMax = 10; // 设定 ip 数限制数 let smsCode = ''; // 随机短信验证码 let smsCodeLen = 6; // 随机短信验证码长度 for (let i = 0; i < smsCodeLen; i++) { smsCode += Math.floor(Math.random() * 10); } console.log('短信验证码:', smsCode) try { // 根据当前日期查询到相应文档 let mobilePhoneDoc = await MobilePhoneModel.find({mobilePhone, curDate}).sort({_id: -1}).limit(1); // 同一天,同一个 ip 文档条数 let clientIpCount = await MobilePhoneModel.find({clientIp, curDate}).countDocuments(); if(mobilePhoneDoc) { // 说明次数未到到限制,可继续发送 if(mobilePhoneDoc.sendCount < smsSendMax && clientIpCount < ipCountMax) { let sendCount = mobilePhoneDoc.sendCount + 1; //将发送记录添加到数据库中 let newmobilePhone = new MobilePhoneModel({mobilePhone: mobilePhone, clientIp: clientIp, sendCount: sendCount, curDate: curDate}); let res = await newmobilePhone.save(); // 执行发送短信验证码 // let data = sendSMSCode(smsCode, mobilePhone); switch(data.error_code) { case 0: return {smsCode, code: 200, msg: '验证码发送成功'}; case 10012: return { smsCode, code: 5000, msg: '没有免费短信了' }; default: return { smsCode, code: 4000, msg: '未知错误' }; } } else { return {code: 4020, msg: '当前手机号码发送次数达到上限,明天重试'} } } else { return { smsCode, code: 200, msg: '验证码发送成功' }; // 执行发送短信验证码 // const data = sendSMSCode(mobilePhone, smsCode); switch (data.error_code) { case 0: // 建立新文档 | 新增数据 let mPdoc = await MobilePhoneModel.create({ mobilePhone, clientIp, curDate, sendCount: 1 }); console.log(mPdoc) return { smsCode, code: 200, msg: '验证码发送成功' }; case 10012: return { smsCode, code: 5000, msg: '没有免费短信了' }; default: return { smsCode, code: 4000, msg: '未知错误' }; } } } catch (error) { console.log(error) } }
2.5 咱们将发送API封装在sendSMSCode() 方法里,该方法位于 mall-server\utils\sms.js里。
let request = require('request'); let querystring = require('querystring'); /** * 当前选用聚合数据 https://www.juhe.cn SMS API (有无偿使用短信条数) * 固然也能够选择其余第三方云服务提供商: 阿里云 | 腾讯云 | 网易云 | ... * * 发送手机短信验证码 * @param {String} mobilePhone 接受短信的用户手机号码 * @param {Number} randomNum 随机验证码 */ function sendSMSCode(randomNum, mobilePhone) { let queryData = querystring.stringify({ "mobile": mobilePhone, // 接受短信的用户手机号码 "tpl_id": "187915", // 您申请的短信模板 ID,根据实际状况修改 "tpl_value": `#code#=${ randomNum }`, // 您设置的模板变量,根据实际状况修改 "key": "d52256474eb6d73350e47eb52adbca67", // 应用 APPKEY (应用详细页查询) }); let queryUrl = 'http://v.juhe.cn/sms/send?' + queryData; return new Promise((resolve, reject) => { request(queryUrl, function(error, response, body) { if (!error && response.statusCode == 200) { // 解析接口返回的JSON内容 let newBody = JSON.parse(body); resolve(newBody); } else { reject('请求异常'); } }); }); } module.exports = sendSMSCode;
其中根据你申请的API文档,能够找到每一种返回状态码对应的说明,并根据状态码设定不一样的返回状态解释。
好了,到这里咱们就完整的实现了发送短信验证码功能,有了它,你的注册页面看起来就会有一种高达上的感受,而且在验证用户手机号的真实性也是颇有意义的。
文章中若存在错误之处,欢迎你们留言指正。