【Vue3】滑动验证组件 您所在的位置:网站首页 拖动滑块验证怎么拖不动 【Vue3】滑动验证组件

【Vue3】滑动验证组件

2023-12-21 08:35| 来源: 网络整理| 查看: 265

前言

滑块验证不只判断是否滑动到尾部,真正的目的是检测用户行为,检测行为是人为、脚本、还是其它。

防止使用脚本大量注册、请求等 。比如发送请求时,判断用户在某个页面停留了多长时间。登录、注册时是否点击了登录、注册按钮,如果没有点击就直接发送登录、注册请求,那么这个行为十有八九是脚本、机器行为。

滑块验证有几个重要的数据

滑块的起点滑块的终点滑块从起点滑动到终点所用的时间,比如人为滑动长度为240px的滑块,至少需要50毫秒,才能冲起点滑倒终点。若起点滑到终点所用的时间是50毫秒以下,那么这种行为99%是机器、脚本行为。人为滑的没那么快。滑块的长度,滑块的长度越长越好,因为长度越长,我们采集到的数据就越多,数据越多就越容易判断是人为还是机器、脚本行为。滑块最好在200px+以上。滑动滑块的轨迹,例如滑动一个长度为300px的滑块,至少可以采集到20个点位,每个点位有用户滑动的x,y轴位置、滑动时间等。 验证方式 人为滑动轨迹参考

当我们用手滑动验证滑块时,滑动轨迹为变速曲线运动,上下起伏分布不均、且变速运动。如下图3图

图1 在这里插入图片描述

图2 在这里插入图片描述

图3 在这里插入图片描述

人为/人机/脚本滑动判断 滑动滑块所用时间低于50毫秒判断为人机/脚本,200px的滑块,人为滑动没那么快。判断滑动的线条起伏,若是纯直线,则为人机,因为人为滑动没那么直。计算滑动速度,人为滑动肯定是变速滑动,若是匀速滑动则为人机。

若不是人为滑动,多数是直线匀速滑动,不会上下起伏。例如脚本滑动。 但部分机器/脚本可以模拟人为滑动,所以很轻易破解滑块验证。

下面是一串人为滑动的数据,用canvs渲染出来, x - x轴位置,y - y轴位置 ,moveTime - 滑动到该点的时间 const draw = data => { const cvs = document.querySelector('#cvs'), c = cvs.getContext('2d') c.clearRect(0, 0, cvs.width, cvs.height) c.lineWidth = 2 c.strokeStyle = 'red' c.beginPath() data.forEach((it, i) => { if (i === 0) c.moveTo(it.x, it.y) c.lineTo(it.x, it.y) }) c.stroke() } const data = [{"x":41.4,"y":24.05,"moveTime":1684666751003},{"x":45.12,"y":23.12,"moveTime":1684666751014},{"x":47.91,"y":23.12,"moveTime":1684666751019},{"x":52.56,"y":22.19,"moveTime":1684666751027},{"x":56.28,"y":21.26,"moveTime":1684666751035},{"x":60.93,"y":20.33,"moveTime":1684666751043},{"x":63.72,"y":20.33,"moveTime":1684666751051},{"x":68.37,"y":20.33,"moveTime":1684666751059},{"x":73.02,"y":20.33,"moveTime":1684666751067},{"x":77.67,"y":20.33,"moveTime":1684666751075},{"x":83.26,"y":20.33,"moveTime":1684666751083},{"x":87.91,"y":20.33,"moveTime":1684666751091},{"x":93.49,"y":20.33,"moveTime":1684666751099},{"x":101.86,"y":20.33,"moveTime":1684666751107},{"x":109.3,"y":20.33,"moveTime":1684666751115},{"x":117.67,"y":20.33,"moveTime":1684666751124},{"x":125.12,"y":20.33,"moveTime":1684666751131},{"x":131.63,"y":20.33,"moveTime":1684666751139},{"x":139.07,"y":20.33,"moveTime":1684666751148},{"x":145.58,"y":21.26,"moveTime":1684666751155},{"x":151.16,"y":22.19,"moveTime":1684666751163},{"x":155.81,"y":23.12,"moveTime":1684666751171},{"x":161.4,"y":24.05,"moveTime":1684666751179},{"x":166.05,"y":24.05,"moveTime":1684666751187},{"x":170.7,"y":24.05,"moveTime":1684666751195},{"x":177.21,"y":24.05,"moveTime":1684666751204},{"x":181.86,"y":24.05,"moveTime":1684666751211},{"x":186.51,"y":24.05,"moveTime":1684666751219},{"x":191.16,"y":24.05,"moveTime":1684666751227},{"x":195.81,"y":24.05,"moveTime":1684666751236},{"x":201.4,"y":24.05,"moveTime":1684666751243},{"x":206.98,"y":24.05,"moveTime":1684666751251},{"x":213.49,"y":24.05,"moveTime":1684666751259},{"x":220,"y":24.05,"moveTime":1684666751268},{"x":227.44,"y":24.05,"moveTime":1684666751275},{"x":236.74,"y":24.05,"moveTime":1684666751283},{"x":245.12,"y":24.05,"moveTime":1684666751291},{"x":254.42,"y":24.05,"moveTime":1684666751299},{"x":261.86,"y":24.05,"moveTime":1684666751307},{"x":268.37,"y":24.05,"moveTime":1684666751315},{"x":273.02,"y":24.05,"moveTime":1684666751323},{"x":279.53,"y":24.05,"moveTime":1684666751332}] draw(data )

渲染图 在这里插入图片描述

滑动验证 (vue3) Props props类型默认值作用widthNumber300滑块宽度heightNumber45滑块高度servertestBlooearfalse是否开启后后端验证 - 点我跳转详情drop-colorString#fff滑块颜色tip-none-colorString#000待验证文本色tip-suc-colorString#fff验证成功文本色tip-test-ing-colorString#fff验证中的文本色tip-tail-colorString#ee0a24验证失败文本色slide-colorString#ee0a24滑块背景色颜色success-bg-colorString#07c160验证通过背景色tail-bg-colorString#ee0a24验证失败背景色active-bg-colorString#1989fa已激活的背景色test-ing-bg-colorString#ff976a验证中激活的背景色font-sizeNumber16文本大小test-tipString正在验证…验证中提示文本tip-txtString向右滑动验证待验证提示文本success-tipString太棒了,恭喜你验证通过!验证成功文字提示fail-tipString验证失败,请重试验证失败文字提示 事件 事件作用参数备注statu验证状态(vfcStatu, slideInfo ) vfcStatu.statu→验证状态 。slideInfo →滑动轨迹信息vfcStatu.statu有4种状态 。①tail - 验证失败 ②success - 验证成功 ③testing - 前端验证中 ④ servertest - 后端验证中,props的servertest设置未true才会触发后端验证(若servertest为true,不设置vfcStatu.statu状态会一直处于验证中动画) vue验证组件

SliderVfc.vue

const props = defineProps({ // 是否开启服务端验证 servertest: { type: Boolean, default: false }, width: { type: Number, default: 300 }, height: { type: Number, default: 45 }, strokeWidth: { type: Number, default: 5 }, // 滑块宽度 dropWidth: { type: Number, default: 50 }, // 已激活验证背景色 activeBgColor | 验证中激活的背景色 testIngBgColor| 验证成功激活的背景色 successBgColor // 验证成功文本色 tipSucColor| 验证失败文本色 tipTailColor | 验证中的文本色 tipTestIngColor | 待验证文本色 tipNoneColor // 移动滑块背景色 dropColor // 滑块原始背景色 slideColor // 滑块颜色 dropColor: { type: String, default: '#fff' }, // 待验证文本色 tipNoneColor: { type: String, default: '#000' }, // 验证成功文本色 tipSucColor: { type: String, default: '#fff' }, // 验证中的文本色 tipTestIngColor: { type: String, default: '#fff' }, // 验证失败文本色 tipTailColor: { type: String, default: '#ee0a24' }, // 验证中提示 testTip: { type: String, default: '正在验证...' }, // 滑块背景色颜色 slideColor: { type: String, default: '#e8e8e8' }, // 滑块背景色颜色 tipTxt: { type: String, default: '向右滑动验证' }, // 验证通过背景色 successBgColor: { type: String, default: '#07c160' }, // 验证失败背景色 tailBgColor: { type: String, default: '#ee0a24' }, // 已激活的背景色 activeBgColor: { type: String, default: '#1989fa' }, // 验证中激活的背景色 testIngBgColor: { type: String, default: '#ff976a' }, // 验证成功文字提示 successTip: { type: String, default: '太棒了,恭喜你验证通过!' }, // 验证失败文字提示 failTip: { type: String, default: '验证失败,请重试' }, // 文本大小 fontSize: { type: Number, default: 16 }, }) const emit = defineEmits(['statu']) let vfcx = null const cvs = ref() const cvsClass = ref('cur-none') let vfcres = { startX: 0,//开始拖动滑块位置 endX: 0,//结束拖动滑块位置 timed: 0,//拖动所用时间 || 低于30毫秒认定为机器 guiji: [],//拖动轨迹 | 连续2个2数之差相同判定为机器 width: props.width } const vfcStatu = reactive({ statu: 'none' }) // 监听数据,并发给父级 watch(vfcStatu, res => { emit('statu', res, vfcres) // 验证成功 if (res.statu === 'success') { vfcx.anmateOff = false vfcx.activeBgColor = props.successBgColor vfcx.tipTxt = props.successTip vfcx.colors.slideColor = props.successBgColor vfcx.evNone() } else if (res.statu === 'tail') { vfcx.reset() vfcx.tipTxt = props.failTip vfcx.fontColor = props.tipTailColor vfcx.draw() } }) /** * 验证器 * @param {Element} cvsEl canvas元素 * @param {String, default:'cur-none'} cvsClass canvas的class * @param {Boolear, default:fasle} vfcres 验证结果 * @param {Number, default:5} strokeWidth 滑块内边距 * @param {Number,default:50} dropWidth 滑块宽度 * @param {color,default:'#fff'} dropColor 移动滑块背景色 * @param {color,default:'#e8e8e8'} slideColor 滑块背景色颜色 * @param {color,default:'skyblue'} activeBgColor 已激活验证背景色 * @param {color,default:'#ff976a'} testIngBgColor 验证中激活的背景色 * @param {color,default:'#07c160'} successBgColor 验证成功激活的背景色 * @param {color,default:'#07c160'} tipSucColor 验证成功文本色 * @param {color,default:'#ee0a24'} tipTailColor 验证失败文本色 * @param {color,default:'#fff'} tipTestIngColor 验证中的文本色 * @param {color,default:'#000'} tipNoneColor 待验证文本色 * @param {String,default:'向右滑动验证'} tipTxt 文字提示 * @param {String,default:'太棒了,恭喜你验证通过!'} successTip 验证成功文字提示 * @param {String,default:'验证失败,请重试...'} failTip 验证失败文字提示 * @param {Bool} servertest 是否开启前端验证模式 * @param {String} testTip 验证提示 */ class Vfcs { constructor(cvsEl, cvsClass, vfcres, vfcStatu, strokeWidth, dropWidth, fontSize, servertest, colors, tipTxt) { this.cvsEl = cvsEl this.vfcres = vfcres this.cvsClass = cvsClass this.strokeWidth = strokeWidth this.dropWidth = dropWidth this.vfcStatu = vfcStatu this.colors = colors this.fontSize = fontSize this.dwonIsPath = false //是否按下验证滑块 this.ctx = null this.allTipTxts = tipTxt this.tipTxt = this.allTipTxts.tipTxt this.fontColor = this.colors.tipNoneColor this.activeBgColor = this.colors.activeBgColor this.servertest = servertest this.guiji = [] this.startTime = 0 this.endTime = 0 this.startX = 0 this.startY = 0 this.moveX = 0 this.moveY = 0 this.fontOp = 1 //文本透明度 this.met = false this.offX = 0//x轴的位移 this.minX = this.strokeWidth / 2 this.maxX = this.cvsEl.width - this.dropWidth - this.strokeWidth // this.dropX最大值 -》 cW - this.dropWidth - this.strokeWidth / 2 // this.dropX最小 -》 this.strokeWidth / 2 this.dropX = this.minX + this.offX // 滑块位置 this.toTouchEnd = false //是否按下滑块 this.isDown = false this.testAm = null //验证中动画的id this.anmateOff = true//动画开关 this.evsName = []//事件名 this.evsFun = [this.down.bind(this), this.move.bind(this), this.up.bind(this)]//事件方法 this.init() } init() { this.ctx = this.cvsEl.getContext('2d') this.draw() this.evsName = this.evType() // 给canvas添加事件 this.evsName.forEach((evName, i) => i === 0 ? this.cvsEl.addEventListener(evName, this.evsFun[i]) : document.addEventListener(evName, this.evsFun[i])) } // 绘制 draw() { let cW = this.cvsEl.width, cH = this.cvsEl.height, c = this.ctx c.clearRect(0, 0, cW, cH) c.globalAlpha = this.fontOp // 设置图像透明度 c.fillRect(0, 0, cW, cH) c.fillStyle = this.colors.slideColor c.strokeStyle = this.colors.slideColor c.lineWidth = this.strokeWidth c.fillRect(0, 0, cW, cH) c.strokeRect(0, 0, cW, cH) // 激活背景色 c.fillStyle = this.activeBgColor c.strokeStyle = this.activeBgColor c.fillRect(this.minX + 2, this.minX, this.offX, cH - this.strokeWidth) // 文本提示 c.textAlign = "center" c.textBaseline = 'middle' c.fillStyle = this.fontColor c.font = `${this.fontSize}px 黑体` c.fillText(this.tipTxt, cW / 2, cH / 2) // 验证失败 // 待验证 | 验证中 if (this.vfcStatu.statu === 'none' || this.vfcStatu.statu === 'testing' || this.vfcStatu.statu === 'servertest' || this.vfcStatu.statu === 'tail') { // 滑块 c.beginPath() c.fillStyle = this.colors.dropColor c.rect(this.dropX, this.minX, this.dropWidth, cH - this.strokeWidth) c.fill() // 箭头 c.lineWidth = 2 // 右边箭头 c.moveTo(this.dropX + this.dropWidth / 1.7 - 5, this.strokeWidth + 10) c.lineTo(this.dropX + this.dropWidth / 1.7 + 5, cH / 2) c.lineTo(this.dropX + this.dropWidth / 1.7 - 5, cH - this.strokeWidth - 10) // 左边箭头 c.moveTo(this.dropX + this.dropWidth / 1.7 - 15, this.strokeWidth + 10) c.lineTo(this.dropX + this.dropWidth / 1.7 - 5, cH / 2) c.lineTo(this.dropX + this.dropWidth / 1.7 - 15, cH - this.strokeWidth - 10) c.stroke() c.closePath() // 验证成功 } else if (this.vfcStatu.statu === 'success') { // 滑块 c.beginPath() c.fillStyle = this.colors.dropColor c.rect(this.dropX, this.minX, this.dropWidth, cH - this.strokeWidth) c.fill() c.closePath() // 圈 c.beginPath() c.fillStyle = this.colors.successBgColor c.arc(this.dropWidth / 2 + this.dropX, cH / 2, cH / 3, 0, 2 * Math.PI) c.fill() c.closePath() // 勾 c.beginPath() c.lineWidth = 3 c.lineJoin = "bevel" c.lineCap = "round" c.strokeStyle = this.colors.dropColor c.moveTo(this.dropX + this.dropWidth / 2 - 8, cH / 2 + 1) c.lineTo(this.dropX + this.dropWidth / 2.1, cH / 1.6) c.lineTo(this.dropX + this.dropWidth / 2 + 8, cH / 2 - 5) c.stroke() c.closePath() } } // 滑块按下 down(ev) { if (this.vfcStatu.statu === 'testing' || this.vfcStatu.statu === 'servertest') return this.setXY(ev) //按下滑块 this.isDown = true this.startTime = new Date().getTime() // 若按下滑块 const isPath = this.ctx.isPointInPath(this.startX, this.startY) this.dwonIsPath = isPath } // 滑块移动 move(ev) { if (this.vfcStatu.statu === 'testing' || this.vfcStatu.statu === 'servertest') return this.setXY(ev) const isPath = this.ctx.isPointInPath(this.moveX, this.moveX) // pc 鼠标变手势 if (ev.x) isPath === true ? this.cvsClass.value = 'cur' : this.cvsClass.value = 'cur-none' const x = Number(this.moveX.toFixed(2)) const y = Number(this.moveY.toFixed(2)) const moveTime = new Date().getTime() this.guiji.push({ x, y, moveTime }) if (this.dwonIsPath === false || this.moveX if (this.vfcStatu.statu === 'testing' || this.vfcStatu.statu === 'servertest' || this.offX === 0 || this.dwonIsPath === false || this.moveX this.vfcStatu.statu = 'testing' this.testAdmate() //开启动画 // 验证中 this.fontColor = this.colors.tipTestIngColor this.tipTxt = this.allTipTxts.testTip this.activeBgColor = this.colors.testIngBgColor this.dropX = this.maxX + this.minX// 滑块位置 const test = this.testVer() setTimeout(() => { // 前端验证通过 if (test === 'success') { // 已开启前端验证模式 if (this.servertest === true) { this.vfcStatu.statu = 'servertest' } else { this.vfcStatu.statu = 'success' } // 前端验证不通过 } else { this.vfcStatu.statu = 'tail' } }, 1000) } this.draw() this.guiji = [] } // 重置滑块 reset() { this.dropX = this.minX// 滑块位置 this.anmateOff = false this.activeBgColor = this.colors.activeBgColor this.fontColor = this.colors.tipNoneColor this.tipTxt = this.allTipTxts.tipTxt this.offX = 0 this.toTouchEnd = false this.guiji = [] this.draw() } // 解绑事件 evNone() { this.evsName.forEach((evName, i) => i === 0 ? this.cvsEl.removeEventListener(evName, this.evsFun[i]) : document.removeEventListener(evName, this.evsFun[i])) } // 验证中动画 testAdmate() { // 文本透明度 if (this.met === false && this.fontOp >= 1) { this.met = true } else if (this.met === true && this.fontOp cancelAnimationFrame(this.testAm) this.fontOp = 1 this.testAm = null this.met = false this.anmateOff = true } this.draw() } /** * 验证是否滑动到尾部 * @return {Number} return true 到尾部,false 没到尾部 */ touchDrosToEnd() { const x = this.offX + this.dropWidth + this.strokeWidth const isSuccess = x >= this.cvsEl.width return isSuccess } // 设置xy坐标 setXY(ev) { if (ev.type === 'touchstart') { this.startX = ev.touches[0].clientX - this.cvsEl.getBoundingClientRect().left this.startY = ev.touches[0].clientY - this.cvsEl.getBoundingClientRect().top } if (ev.type === 'touchmove') { this.moveX = ev.touches[0].clientX - this.cvsEl.getBoundingClientRect().left this.moveY = ev.touches[0].clientY - this.cvsEl.getBoundingClientRect().top } // ///pc事件 // if (ev.type === 'mousedown') { this.startX = ev.x - this.cvsEl.getBoundingClientRect().left this.startY = ev.y - this.cvsEl.getBoundingClientRect().top } if (ev.type === 'mousemove') { this.moveX = ev.x - this.cvsEl.getBoundingClientRect().left this.moveY = ev.y - this.cvsEl.getBoundingClientRect().top } // 防止滑块溢出指定范围 if (ev.type === 'mousemove' || ev.type === 'touchmove') { this.offX = this.moveX - this.startX if (this.offX > this.maxX) this.offX = this.maxX if (this.offX // 重复的数量 const repeatX = [] const repeatY = [] const timed = [] const chaArr = this.guiji.reduce((prev, itm, i, arr) => { if (i === arr.length - 1) return prev const nv = arr[i + 1] const chaX = Number((nv.x - itm.x).toFixed(2)) const chaY = Number((nv.y - itm.y).toFixed(2)) const timeCha = nv.moveTime - itm.moveTime timed.push(timeCha)//时间差 // 是否有重复的数组 const repeatXIndex = repeatX.findIndex(item => item.num === chaX) const repeatYIndex = repeatY.findIndex(item => item.num === chaY) // xy轴每2数差数据 if (repeatXIndex === -1) { const obj = { num: chaX, count: 1 } repeatX.push(obj) } else { repeatX[repeatXIndex].count++ } if (repeatYIndex === -1) { const obj = { num: chaY, count: 1 } repeatY.push(obj) } else { repeatY[repeatYIndex].count++ } prev.push({ x: chaX, y: chaY }) return prev }, []) // 所有重复次数 const findXCount = [] const findYCount = [] repeatX.forEach(it => findXCount.push(it.count)) repeatY.forEach(it => findYCount.push(it.count)) const repeatMaxXCount = Math.max(...findXCount)//x重复最多的次数 const repeatMaxYCount = Math.max(...findYCount)//y重复最多的次数 const repeatMaxTimed = Math.max(...timed)//滑动时间重复最多的次数 return { chaArr, repeatX, repeatY, repeatMaxXCount, repeatMaxYCount, repeatMaxTimed } } // 前端验证 // x轴最大波动大于数等于所有波动长度则为人机 | y轴最大波动数等于所有波动长度则为人机 | 滑动时间低于50毫秒不通过 | 时间波动最大次数大于滑动轨迹长度的3/1为人机 testVer() { // return 'tail' // 滑动所用时间低于50毫秒 是人机 if (this.vfcres.timed { const colors = { activeBgColor: props.activeBgColor, testIngBgColor: props.testIngBgColor, successBgColor: props.successBgColor, tipSucColor: props.tipSucColor, tipTailColor: props.tipTailColor, tipTestIngColor: props.tipTestIngColor, tipNoneColor: props.tipNoneColor, dropColor: props.dropColor, slideColor: props.slideColor, } const tipTxt = { testTip: props.testTip, tipTxt: props.tipTxt, successTip: props.successTip, failTip: props.failTip, } vfcx = new Vfcs( cvs.value, cvsClass, vfcres, vfcStatu, props.strokeWidth, props.dropWidth, props.fontSize, props.servertest, colors, tipTxt ) }) .cur { cursor: pointer; } .cur-none { cursor: default; } 使用滑动验证

Home.vue

// 滑块验证 const slide = (vfcStatu, slideInfo) => { /** 这里可以做一些自定义验证 - vfcStatu.statu有2状态,必须赋值状态 - success 验证成功状态 - tail 验证失败状态 - 可配合后端验证 */ const statu = vfcStatu.statu if (statu) { if (statu === 'success') { console.log('验证成功') } else if (statu === 'tail') { console.log('验证失败') } } } 成功效果图

在这里插入图片描述

失败效果图

在这里插入图片描述

验证中效果图

验证中会有一个透明度效果 在这里插入图片描述

搭配后端验证(axios | express 搭配后端验证,主要用到axios 与 express。后端验证的原理与前端验证是一个原理,只不过换了给地方做验证。但是还是不安全,因为前端可以伪造数据,然后给后端发送请求,后端返回数据。纯滑块还是要搭配检测行为才好使,因为滑块只是访问某个页面或请求某个数据时的多环验证的其中一环而已,比如用户停留某个页面多长时间,各方面因素来判断当前行为是否是人为、还是脚本\机器行为等等。

后端代码

import express from 'express' const app = express() const router = express.Router() app.use(router) /** * 滑块验证 * @data {Array} 前端传送data 验证的数据 * @res {String(success,tail)} success 验证成功 | tail验证失败 */ router.post('/api/slidetest', (req, res) => { const qy = req.query // 重复的数量 const repeatX = [], repeatY = [], timed = [] const data = JSON.parse(qy.datainfo) let resInfo = '' const chaArr = data.guiji.reduce((prev, itm, i, arr) => { if (i === arr.length - 1) return prev const nv = arr[i + 1] const chaX = Number((nv.x - itm.x).toFixed(2)) const chaY = Number((nv.y - itm.y).toFixed(2)) const timeCha = nv.moveTime - itm.moveTime timed.push(timeCha)//时间差 // 是否有重复的数组 const repeatXIndex = repeatX.findIndex(item => item.num === chaX) const repeatYIndex = repeatY.findIndex(item => item.num === chaY) // xy轴每2数差数据 if (repeatXIndex === -1) { const obj = { num: chaX, count: 1 } repeatX.push(obj) } else { repeatX[repeatXIndex].count++ } if (repeatYIndex === -1) { const obj = { num: chaY, count: 1 } repeatY.push(obj) } else { repeatY[repeatYIndex].count++ } prev.push({ x: chaX, y: chaY }) return prev }, []) // 所有重复次数 const findXCount = [] const findYCount = [] repeatX.forEach(it => findXCount.push(it.count)) repeatY.forEach(it => findYCount.push(it.count)) const repeatMaxXCount = Math.max(...findXCount)//x重复最多的次数 const repeatMaxYCount = Math.max(...findYCount)//y重复最多的次数 const repeatMaxTimed = Math.max(...timed)//滑动时间重复最多的次数 // 时间波动最大次数等于chaArr.length滑动轨迹长度为人机 const timeTest = repeatMaxTimed === chaArr.length // 滑动所用时间低于50毫秒 是人机 // x轴最大波动大于数等于所有波动长度则为人机 // y轴最大波动数等于所有波动长度则为人机 if (data.timed // 是真人 resInfo = 'success' } // console.log(resInfo); res.end(resInfo) }) const host = '127.0.0.1' const port = 3456 app.listen(port, host, () => { console.log(host + ':' + port + '/api/slidetest') }) 例:前端登录代码

需求:

用户登录输入的用户名与密码合法用户点击登录按钮,不直接向后端发起登录请求,而是弹出验证滑块验证成功 → 发起登录请求验证成功 → 提示验证失败 ,不发起登录请求

login.vue

import axios from 'axios' import { reactive } from 'vue' const userInfo = reactive({ userName: '', pwd: '', showVfc: false, }) const login = (vfcStatu, slideInfo) => { // 进行简单校验 。仅作测试,真实开发中需严格校验 if (userInfo.userName.length >= 3 && userInfo.pwd.length >= 3) { userInfo.showVfc = true } else { alert('请输入合法信息') return } if (vfcStatu.statu) { axios.post('/api/slidetest?datainfo=' + JSON.stringify(slideInfo)).then(res => { // 若滑块验证成功 验证成功可以做一些登录。注册等请求 vfcStatu.statu = res.data if (res.data === 'success') { console.log('验证成功') console.log('这里可以做一些登录请求') axios.post(`/api/login?unm=${userInfo.userName}&pwd=${userInfo.pwd}`) } else if (res.data === 'tail') { console.log('验证失败') } }).catch(err => { // 若滑块验证出错 vfcStatu.statu = 'tail' }) } }

输入前 在这里插入图片描述

输入的信息合法 + 点击登录按钮后 在这里插入图片描述

验证成功发起登录请求 在这里插入图片描述

验证失败 在这里插入图片描述

成品展示 登录前

在这里插入图片描述

未输入登录信息登录时

在这里插入图片描述

输入的信息不合法时 输入信息不合法不应该弹出滑块验证 在这里插入图片描述 输入信息合法时

在这里插入图片描述

验证成功时

验证成功自动登录,反之,验证不成功不会发起登录请求,并提示重验 在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有