> 技术文档 > 超详细 anji-captcha滑块验证uniapp微信小程序前端组件

超详细 anji-captcha滑块验证uniapp微信小程序前端组件

由于步骤太多,字数太多,废话也太多,所以前后端分开讲了,后端文章请看:

超详细 anji-captcha滑块验证springboot+uniapp微信小程序前后端组合https://blog.csdn.net/new_public/article/details/149116742


anji-captcha开源项目地址:https://github.com/anji-plus/captcha

anji-captcha开源文档地址:在线体验暂时下线 !!! | AJ-Captcha


写完后端代码,开始写前端,首选肯定又是一顿各种网上查资料搬砖,发现基本清一色用了anji-captcha开源文档介绍的【Verify组件,这组件是anji-captcha开源项目里面有的,在view文件夹下面,选择自己对应的前端类型,里面有下面几种类型。

比如:/view/vue/src/components/verifition/Verify 


 不过我没去看,是自己写了一个,毕竟只有自己写的才是最适合自己的。


效果如下:


每次滑动有广告语出现,比如视频里的,【大怨种】【纯牛马】。

获取次数和验证次数超限,会显示提示等。


组件代码

              拖动滑块验证   {{ slideCoverText }}          {{ errorMsg }}       // 这里用到util是防抖动方法和四舍五入方法,自己写一个吧,或者问api给一个 import util from \'@/utils/util\' // npm i crypto-js -s 引入,这里用的是4.2.0版本 import CryptoJS from \'crypto-js/crypto-js\' export default { data () { return { // 滑块宽高度 px slideBlockWidth: 47, // 背景图宽度 px slideImageWidth: 310, // 背景图高度 px slideImageHeight: 155, // 弹窗占据屏幕 90% modalWidth: 90, // 弹窗内部左右边距20px modalLRpadding: 20, // 缩放比率,不同屏幕的手机,必须适配 scaleRatio: 0, // 滑动起始X位置 px startX: 0, // 滑动停止位置,也就是距离起始位置长度 px lastLeft: 0, // 后端返回的验证码参数 captchaOption: {}, // 父页面参数 parentOption: {}, // 获取滑块验证码返回的结果码,用来显示错误信息的 captChaGetRepCode: void (0), // 广告语,每次随机取 slideCoverTextList: [\'大怨种\', \'纯牛马\', \'肝吧\', \'挤上牛马传送带\', \'这就是命啊\'] } }, created () { // 初始化背景图和滑块缩放 this.handlerCaptchaScale() // 手动获取滑块验证码的方法,加上防抖动,350毫秒 this.delayedGenerateSlideCaptchaImage = util.debounce(this.delayedGenerateSlideCaptchaImage, 350) }, computed: { modalPadding () { return `40rpx ${this.modalLRpadding}px 20rpx ${this.modalLRpadding}px` }, // 广告语,随机取,为了能随机,故跟startX绑定上了 slideCoverText () { const initIndex = Math.floor(Math.random() * this.slideCoverTextList.length) const startXStr = String(this.startX) const finalIndex = initIndex + (startXStr.length > 1 ? Number(startXStr.substring(startXStr.length - 1)) : this.startX) return this.slideCoverTextList[finalIndex > this.slideCoverTextList.length - 1 ? initIndex : finalIndex] }, // 背景图base64 sliderCaptchaBackBase64 () { const { originalImageBase64 } = this.captchaOption if (!originalImageBase64) {  return \'\' } return \'data:image/png;base64,\' + originalImageBase64 }, // 滑块base64 sliderCaptchaBlockBase64 () { const { jigsawImageBase64 } = this.captchaOption if (!jigsawImageBase64) {  return \'\' } return \'data:image/png;base64,\' + jigsawImageBase64 }, errorMsg () { if (this.captChaGetRepCode === \'6201\') {  return \'获取图形验证码频繁,请稍后再试\' } else if (this.captChaGetRepCode !== \'0000\') {  return \'啊哦,加载失败了,点击这里刷新\' } return \'\' } }, methods: { // 显示滑块验证码弹窗,父页面通过ref通用 show (option = {}) { this.resetSlideCaptcha() this.parentOption = option this.generateSlideCaptchaImage().then(_ => {  this.$nextTick(() => { // 调用弹窗组件方法,显示弹窗 this.$refs.$captchaModal.show({ title: option.title || \'验证\', maxHeight: 800, hideCancel: true, confirmText: \'关闭\', // 弹窗关闭按钮点击回调 callback: _ => { // 如果父页面有关闭回调,则这里调用 this.parentOption.closeCallback && this.parentOption.closeCallback() } })  }) }) }, close () { this.resetSlideCaptcha() // 关闭弹窗 this.$refs.$captchaModal.onClose() }, // 初始化缩放比率,以及设置滑块背景和滑块宽高 handlerCaptchaScale () { const { windowWidth } = uni.getSystemInfoSync() const modalWidthPx = windowWidth * (this.modalWidth / 100) const modalLRpaddingSum = this.modalLRpadding * 2 const modalInternalWidth = modalWidthPx - modalLRpaddingSum const onePercentagePx = windowWidth * 0.01 if (modalInternalWidth > this.slideImageWidth) {  // 如果屏幕宽度大于初始化滑块背景图宽度,则不缩放  this.modalWidth = util.toFixed((this.slideImageWidth + modalLRpaddingSum) / onePercentagePx, 2) } else if (modalInternalWidth  {  // request 基于uni.request封装,总之就是调获取验证码接口  request(\'/captcha/get\', \'POST\', { \'captchaType\' : \'blockPuzzle\' }, true).then(({ repData = {}, repCode }) => { this.captChaGetRepCode = repCode this.captchaOption = repData  }).finally(() => { resolve()  }) }) }, // 按中滑块,记录起始位置 touchstart (e) { const touch = e.touches[0] || e.changedTouches[0] this.startX = touch.clientX }, // 开始滑动,计算当前滑动位置 touchmove (e) { const touch = e.touches[0] || e.changedTouches[0] const pageX = touch.clientX const width = this.slideImageWidth - this.slideBlockWidth - 1 let left = pageX - this.startX left = left = width ? width : left) this.lastLeft = left }, // 滑动结束(松手),验证 touchend () { /**  * callback 父页面验证结果回调  * verifyCaptchaCallback 整个验证由父页面处理  * verifyApi 验证后端接口  *  * callback 和 verifyCaptchaCallback 二选一  * 选callback,verifyApi必传,让这个这个组件调用验证接口  */ const { callback, verifyCaptchaCallback, verifyApi } = this.parentOption if (callback || verifyCaptchaCallback) {  if (verifyCaptchaCallback) { verifyCaptchaCallback(this.captchaOption).then(flag => { if (flag) { this.close() } else { this.resetSlideCaptcha(true) } })  } else { this.verifyCaptcha(verifyApi).then(res => { if (res) { // 验证成功,记录本次验证码的aes密匙,给后面后端二次验证使用 res.secretKey = this.captchaOption.secretKey this.close() } else { // 验证失败重新获取 this.resetSlideCaptcha(true) } callback(res) })  } } else {  this.resetSlideCaptcha(true) } }, // 重置 resetSlideCaptcha (generateFlag) { this.lastLeft = 0 this.startX = 0 generateFlag && this.generateSlideCaptchaImage() }, // 验证滑动是否正确 verifyCaptcha (verifyApi) { return new Promise((resolve, reject) => {  const originalPointJson = this.generatePointJson()  const param = { captchaType: \'blockPuzzle\', token: this.captchaOption.token, pointJson: this.pointEncrypted(originalPointJson)  }  // request 基于uni.request封装,总之就是调验证接口  request(verifyApi || \'/captcha/check\', \'POST\', param, true).then(res => { const { repCode, repData = {}, repMsg } = res if (repCode === \'0000\' && repData.result) { resolve({ ...repData, originalPointJson }) } else { // toast 等同 uni.showToast() toast(repMsg || \'验证失败\') resolve(false) }  }).catch(_ => { resolve(false)  }) }) }, // 生成当前滑动完成的坐标点,加密的原文 generatePointJson () { const x = this.scaleRatio > 0 ? ((this.lastLeft - 1) / this.scaleRatio) : this.lastLeft - 1 return JSON.stringify({x,y:0}) }, // aes加密坐标点 pointEncrypted (originalPointJson) { const key = CryptoJS.enc.Utf8.parse(this.captchaOption.secretKey) const encrypted = CryptoJS.AES.encrypt(  CryptoJS.enc.Utf8.parse(originalPointJson),  key,  { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7  } ) return encrypted.toString() }, // 生成后端二次验证的密文 captchaVerificationEncrypted ({ originalPointJson, token, secretKey }) { const key = CryptoJS.enc.Utf8.parse(secretKey) const dataToEncrypt = token + \'---\' + originalPointJson const encrypted = CryptoJS.AES.encrypt(  CryptoJS.enc.Utf8.parse(dataToEncrypt),  key,  { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7  } ) return encrypted.toString() } } } .captcha-modal-content { position: relative; .captcha-back-image { width: 100%; border-radius: 10rpx; } .captcha-slider-image { position: absolute; left: 0; top: 0; z-index: 1; } .slide-parent { width: 100%; background-color: rgb(233, 233, 233); position: relative; padding: 1px 0; .slide-tip { position: absolute; top: 0; left: 0; right: 0; bottom: 0; color: #616161; font-size: 30rpx; display: flex; align-items: center; justify-content: center; } .slide-cover { width: 0; height: 100%; position: absolute; left: 0; top: 0; z-index: 1; display: flex; align-items: center; justify-content: center; overflow: hidden; .slide-cover-content {  height: 100%;  color: #FFF;  background-color: #0081ff;  font-size: 30rpx;  display: flex;  align-items: center;  justify-content: center;  position: absolute;  left: 0;  top: 0; } } .slide-view { position: absolute; left: 1px; z-index: 2; background: #FFF; display: flex; align-items: center; justify-content: center; } } } .tui-text-flashover { background: -webkit-gradient(linear, left top, right top, color-stop(0, #444), color-stop(.4, #444), color-stop(.5, white), color-stop(.6, #444), color-stop(1, #444));-webkit-background-clip: text !important;-webkit-text-fill-color: transparent !important;-webkit-animation: animate 1.8s infinite;} .tui-flex-center { display: flex; align-items: center; justify-content: center; } .tui-underline { text-decoration: underline; }@-webkit-keyframes animate {from {background-position: -90rpx;}to {background-position: 90rpx;}}@keyframes animate {from {background-position: -90rpx;}to {background-position: 90rpx;}}

注意:组件代码不可直接复制使用,里面使用了一些我项目的封装代码(下面这些)。

  • toast方法,是输出提示。
  • request方法,是调后端接口。
  • 是我封装的一个弹窗组件,弹窗组件UI库大把,自己套一个。参数width是百分比,我的modal组件接收的是Number,所以这里初始90,代表占屏幕90%宽度。然后再根据屏幕大小决定弹窗大小的。
  • 两个import上面有注释说明。

最好看一遍代码的里面的注释,上面说的几个地方需要自己改一下


使用流程

功能引入此组件并定义ref,通过ref调用组件的show方法即可,传入参数,例如:

this.$refs.$sliderCaptcha.show({// 验证接口verifyApi: \'/xxx/yyy\',callback: res => {if (res) {// 验证成功,生成后端二次验证密文(也就是二次验证的redisKey)(不是必须,看自己业务,如有需要的话)const captchaVerification = this.$refs.$sliderCaptcha.captchaVerificationEncrypted(res)}}})

其他罗里吧嗦

1:滑块滑动到缺口后,取哪里的x值?

正确缺口的开始位置,这个开始位置x值是正确的,对应后端缓存,基本大差不差。

2:怎么取?

触摸滑块时,touchstart方法记录触摸位置,滑动触发touchmove方法,获取当前滑动x值,然后减去起始触摸位置,就是正确的x值。


代码里面那个null_data.svg

我直接把源码粘贴出来,复制到文本文件里面,然后后缀改成svg就可以用了

  暂无相关搜索 Created with Sketch.                                    


码字不易,于你有利,勿忘点赞