实际开发工作中,在登陆的时候需要短信验证码,但容易引起爬虫行为,需要用到反爬虫验证码,今天介绍一下拼图验证码,解决验证码反爬虫中的滑动验证码反爬虫。滑动拼图验证码是在滑块验证码的基础上增加了一个随机滑动距离,用户需要将滑块滑到拼图的缺口处,使拼图完整,才能通过校验。平台兼容性,H5、微信小程序、字节、百度、qq等。

实际开发工作中,在登陆的时候需要短信验证码,但容易引起爬虫行为,需要用到反爬虫验证码,今天介绍一下拼图验证码,解决验证码反爬虫中的滑动验证码反爬虫。
原理
滑动拼图验证码是在滑块验证码的基础上增加了一个随机滑动距离,用户需要将滑块滑到拼图的缺口处,使拼图完整,才能通过校验。

平台兼容性,H5、微信小程序、字节、百度
XXX.vue文件引入
template标签中引入
"sliderVerifyFLag" @touchSliderResult="verifyResult" ref="verifyElement">
script标签中引入组件
- import sliderVerify from '@/components/slider-verify/slider-verify.vue';//组件存放的路径
-
- export default {
- components: {
- 'slider-verify': sliderVerify
- },
- data() {
- return {
- sliderVerifyFLag: false //滑块验证
- };
- },
- onLoad() {},
- methods: {
- // 滑块验证结果回调函数
- verifyResult(res) {
- this.sliderVerifyFLag = false;
-
- if (res) { //校验通过
-
- }else{
- // 校验失败,点击关闭按钮
- }
- }
- }
- };

slider-verify.vue文件代码
- <view class="slider-verify-box" v-if="isShow">
- <view class="verifyBox">
- <view class="slider-title">图形验证view>
- <view class="slide-content">
- <view class="slide-tips">拖动下方滑块完成拼图view>
- <view class="slider-pintu">
- <image id="pintuImg" :src="'/static/images/slider-verify/' + img + '.jpg'" class="pintu">image>
-
- <view class="pintukuai" :style="{ top: top + 'px', left: oldx + 'px' }">
- <image :src="'/static/images/slider-verify/' + img + '.jpg'" :style="{ top: '-' + top + 'px', left: '-' + left + 'px'}">image>
- view>
-
- <view class="yinying" :style="{ top: top + 'px', left: left + 'px' }">view>
- view>
-
- <view class="slider-movearea" @touchend="endTouchMove">
- <movable-area :animation="true"><movable-view :x="x" direction="horizontal" @change="startMove">movable-view>movable-area>
-
- <view class="huadao">拖动左边滑块完成上方拼图view>
- view>
- view>
-
- <view class="slider-btn-group">
- <view class="slider-btn" @tap="closeSlider">关闭view>
- <view class="slider-btn slide-btn-refresh" @tap="refreshVerify">刷新view>
- view>
- view>
- view>
-
- <script>
- export default {
- name: 'slider-verify',
- props: {
- isShow: true
- },
- data() {
- return {
- x: 0, //初始距离
- oldx: 0, //移动的距离
- img: '1', //显示哪张图片
- left: 0, //随机拼图的最终X轴距离
- top: 0, //拼图的top距离
- };
- },
- watch: {
- // 每次打开重新刷新拼图
- isShow(newValue, oldValue) {
- if(newValue){
- this.refreshVerify(); //刷新
- }
- }
- },
- mounted() {
- var that = this;
- that.refreshVerify();
- },
- methods: {
- //刷新验证
- refreshVerify() {
- var gl = Math.random().toFixed(2);
- this.left = uni.upx2px(560) * gl > uni.upx2px(280) ? uni.upx2px(280) : uni.upx2px(560) * gl + uni.upx2px(150); //生成随机X轴最终距离
- this.top = uni.upx2px(190) * gl; //生成随机Y轴初始距离
-
- if (gl <= 0.2) {
- this.img = 1;
- }
- if (gl > 0.2 && gl <= 0.4) {
- this.img = 2;
- }
- if (gl > 0.4 && gl <= 0.6) {
- this.img = 3;
- }
- if (gl > 0.6 && gl <= 0.8) {
- this.img = 4;
- }
- if (gl > 0.8 && gl <= 1) {
- this.img = 5;
- }
- this.resetMove(); //重置阴影位置
- },
-
- /* 滑动中 */
- startMove(e) {
- this.oldx = e.detail.x;
- },
-
- /* 滑动结束 */
- endTouchMove() {
- var that = this;
-
- if (Math.abs(that.oldx - that.left) <= 5) {
- uni.showToast({
- title: '验证成功',
- duration: 2500,
- success() {
- that.$emit('touchSliderResult', true);
- }
- });
- } else {
- that.refreshVerify();
- }
- },
-
- /* 重置阴影位置 */
- resetMove() {
- this.x = 1;
- this.oldx = 1;
- setTimeout(() => {
- this.x = 0;
- this.oldx = 0;
- }, 300);
- },
-
- // 关闭
- closeSlider(){
- this.$emit('touchSliderResult', false);
- }
- }
- };
- script>
-
- <style lang="less">
- .slider-verify-box {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 999;
- }
- .verifyBox {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- // width: 85%;
- background-color: #fff;
- border-radius: 20upx;
- box-shadow: 0 0 5upx rgba(0, 0, 0);
-
- .slider-title {
- font-size: 36upx;
- text-align: center;
- padding: 1em 0;
- color: rgba(2, 20, 33, 0.85);
- border-bottom: 1px solid rgba(2, 20, 33, 0.15);
- }
-
- .slide-content {
- width: 560rpx;
- padding: 0 1em;
- margin: 0 auto;
- .slide-tips {
- font-size: 28rpx;
- color: rgba(2, 20, 33, 0.45);
- padding: 0.5em 0;
- }
-
- .slider-pintu {
- position: relative;
- width: 100%;
- border-radius: 10rpx;
- overflow: hidden;
- .pintu {
- width: 560rpx;
- height: 315rpx;
- display: block;
- margin: 0 auto;
- }
-
- .pintukuai {
- position: absolute;
- top: 0;
- left: 0;
- width: 120rpx;
- height: 120rpx;
- z-index: 100;
- box-shadow: 0 0 5upx rgba(0, 0, 0, 0.3);
- overflow: hidden;
-
- image {
- display: block;
- position: absolute;
- top: 0;
- left: 0;
- width: 560rpx;
- height: 315rpx;
- }
- }
- }
-
- .yinying {
- position: absolute;
- width: 120rpx;
- height: 120rpx;
- background-color: rgba(0, 0, 0, 0.5);
- }
- }
- }
-
- .slider-movearea {
- position: relative;
- height: 80upx;
- width: 100%;
- margin: 25upx auto;
-
- movable-area {
- height: 80upx;
- width: 100%;
-
- movable-view {
- width: 80upx;
- height: 80upx;
- border-radius: 50%;
- background-color: #007cff;
- background-image: url(../../static/images/slider-verify/icon-button-normal.png);
- background-repeat: no-repeat;
- background-size: auto 30upx;
- background-position: center;
- position: relative;
- z-index: 100;
- }
- }
- }
-
- .huadao {
- width: 100%;
- height: 66upx;
- line-height: 66upx;
- background: #eee;
- box-shadow: inset 0 0 5upx #ccc;
- border-radius: 40rpx;
- color: #999;
- text-align: center;
- box-sizing: border-box;
- position: absolute;
- top: 7rpx;
- left: 0;
- font-size: 28rpx;
- z-index: 99;
- }
-
- .slider-btn-group{
- width: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- border-top: 1px solid rgba(2, 20, 33, 0.15);
-
- .slider-btn{
- flex: 1;
- height: 100rpx;
- line-height: 100rpx;
- text-align: center;
- font-size: 36rpx;
- color:rgba(2,20,33,0.85);
- &:active{
- opacity: 0.8;
- }
- }
- .slide-btn-refresh{
- color:rgba(14,107,176,1);
- border-left: 1px solid rgba(2, 20, 33, 0.15);
- }
- }
- style>

平台兼容性,H5、微信小程序、字节、百度、qq等
XXX.vue文件引入
template标签中引入
"getCode()" >获取验证码 - <validationPT class="setVef" ref="vefCode" @VefCodeTrue="getVefCodeTrue" :inputPhone="mobile" theme="dialog">validationPT>
script标签中引入组件
- import validationPT from '@/components/validationPT/validationPT.vue'
-
- export default {
- components: {
- 'slider-verify': sliderVerify
- },
- data() {
-
- },
- onLoad() {},
- methods: {
- // 效验手机号
- getCode() {
- this.getCodePT();
- },
- //获取弹窗
- getCodePT(msg) {
- this.$refs.vefCode.GetSlideBlockApi(); //调用图片接口,获取验证图片
- this.$refs.vefCode.clkOpenRef(); //验证通过,打开拼图验证
- this.$refs.vefCode.initial();//重置
- },
- // 滑块验证结果回调函数
- //拼图验证是否成功
- getVefCodeTrue(msg) {
- this.VefInfosBk = msg;
- if (msg.Res == true) {
- //获取验证码
- }
- },
- }
- };
validationPT.vue文件代码
- <view :class="[{'frame--dialog':theme}]">
- <view class="frameBg" v-show="showModal" style="z-index:10">view>
- <view class="framework" v-if="isVefCode">
- <view class="boxTopTitle">
- <text>图形验证码text>
- <text class="cuIcon-close font16 fontB" @click="clkCloseRef">text>
- view>
- <view class="boxImg">
- <view class="cutImgSet" :class="isAnimation?'animation':''"
- :style="{top:CutImgY+'px', left: blockLeft + 'px'}">
- <image :src="CutImg" style="cursor:pointer; width: 40px;height: 40px;z-index: 10;">image>
- view>
- <image :src="BGImg" style="border-radius:8px; width: 300px;height: 150px;">image>
- <view class="reset" @click="clkNext">
- <text class="cuIcon-refresh font16 text-white">text>
- view>
- view>
- <view class="checkBox">
- <view class="checkBar">
- <view class="slide" :class="bgColSet?'active':''">
- <view :class="'moveBac '+(isAnimation?' animation':'')+(bgColSet?' moveBacError':'')"
- :style="'width:'+(isSuccess ? 300 : blockLeft)+'px;'">view>
- <view :class="(bgColSet?'swiperTipsError':'swiperTips') + (!isSuccess?' paddingL40':'')">
- <text v-if="!isSuccess">请拖动滑块验证text>
- <text v-if="isSuccess" class="cuIcon-check margin-right-sm fontB">text>
- <text v-if="isSuccess">验证成功text>
- view>
- <view v-if="!isSuccess"
- :class="'swiperBlock '+(bgColSet?' errorBlock':' successBlock')+(isAnimation?' animation':'') "
- :style="'left:'+blockLeft+'px'" ref="sliderBtn" @touchstart="touchstartHandle"
- @mousedown="startMove" @touchmove.stop.prevent="touchmoveHandle"
- @touchend="touchendHandle">
-
- <image v-if="!bgColSet" :src="ImgUrl + 'Images/doubleArrow.svg'" mode="widthFix"
- style="width: 16px;">image>
- <text v-else class="cuIcon-close font16 text-white">text>
-
-
- <text v-if="bgColSet" class="cuIcon-close font16 text-white">text>
-
- view>
- view>
- view>
- view>
-
- view>
- view>
- <script>
- export default {
- props: {
- inputPhone: '',
- theme: {
- type: String,
- },
- swiperColor: {
- type: String,
- default: 'rgba(21, 132, 223, 0.08)'
- },
- title: {
- type: String,
- default: '人机校验'
- },
- barWidth: {
- type: Number,
- default: 300
- }
- },
- data() {
- return {
- ImgUrl: this.hostwapUrl,
- BGImg: '',
- CutImg: '',
- CutImgY: '',
- MarkCode: '', //验证拼图是否成功用,
- bgColSet: false, //拼图是否验证成功
- isSuccess: false, //是否验证成功
- isVefCode: false,
- showModal: false,
- startInfo: {},
- blockLeft: 0,//随机拼图的最终X轴距离
- isAnimation: false,
- msgType: '',
- }
- },
- computed: {
- trueMoveableW: function() {
- return this.barWidth - 40
- }
- },
- methods: {
- //获取图片接口
- GetSlideBlockApi() {
- let that = this;
- uni.request({
- url: url,//获取拼图接口
- data: data,//需要传给接口的参数
- headers: {
- Accept: "application/json; charset=utf-8"
- },
- dataType: 'json',
- method: 'GET',
- success: (res) => {
- this.BGImg = '';//大图
- this.CutImg = '';//拼图
- this.CutImgY = 0;//接口位置
- },
- complete: () => {}
- })
- },
-
- //手指按下
- touchstartHandle({
- changedTouches
- }) {
- if (this.isSuccess) {
- return
- }
- this.isAnimation = false
- this.startInfo = changedTouches[0]
- },
- // 手指移动
- touchmoveHandle({
- changedTouches
- }) {
- if (this.isSuccess) {
- return
- }
- let blockLeft = changedTouches[0].clientX - this.startInfo.clientX
- let blockLeftRpx = blockLeft;
- if (blockLeftRpx < 0) {
- this.blockLeft = 0
- } else {
- this.blockLeft = blockLeftRpx <= this.trueMoveableW ? blockLeftRpx : this.trueMoveableW
- }
- },
- // 手指离开
- touchendHandle(e) {
- if (this.isSuccess) {
- return
- }
- this.CheckSlideBlockApi();
- },
- //验证图片接口
- CheckSlideBlockApi() {
- let that = this;
- uni.request({
- url: url,//接口名称
- data: data,//接口需要的参数
- headers: {
- Accept: "application/json; charset=utf-8"
- },
- dataType: 'json',
- method: 'POST',
- success: (res) => {
- let infos = {}
- if (res.data.IsOK == true) {//成功
- that.isSuccess = true;
- infos = {
- Res: res.data.Results,
- };//返回传给父组件的值,判断是否成功
- setTimeout(() => {
- that.bgColSet = false;
- that.$emit('VefCodeTrue', infos)
- that.isVefCode = false;
- that.showModal = false;
- }, 1000);
-
- } else {
- that.bgColSet = true;
- that.isSuccess = false;
- that.GetSlideBlockApi();
- that.isAnimation = true
- let timeid = setTimeout(() => {
- clearTimeout(timeid)
- that.isAnimation = false
- that.bgColSet = false;
- }, 500)
- that.blockLeft = 0;
- }
- },
- complete: () => {}
- })
- },
- /* 鼠标按住滑块后初始化移动监听,记录初始位置 */
- startMove(e) {
- e.preventDefault() //禁止图片img拖动的解决方法
- e = e || window.event;
- this.moveStart = e.pageX || e.targetTouches[0].pageX;
- this.addMouseMoveListener();
- },
- /* 鼠标滑块移动 */
- moving(e) {
- // e.preventDefault() //禁止图片img拖动的解决方法
- let self = this;
- e = e || window.event;
- let moveX = (e.pageX || e.targetTouches[0].pageX);
-
- let d = moveX - self.moveStart;
-
- let w = self.dataWidth;
- let PL_Size = this.puzzleSize;
- let padding = this.padding;
-
- if (self.moveStart === "") {
- return "";
- }
- if (d < 0 || d > w - padding - PL_Size) {
- return "";
- }
-
- if (d <= 260) {
- self.blockLeft = d
- }
- },
- /* 鼠标移动结束,验证并回调 */
- moveEnd(e) {
- let self = this;
-
- e = e || window.event;
- let moveEnd_X = (e.pageX || e.changedTouches[0].pageX) - self.moveStart;
-
- if (moveEnd_X > 0) {
- self.CheckSlideBlockApi(); //验证拼图是否成功
- }
- self.moveStart = "";
-
- document.removeEventListener("mousemove", self.moving);
- document.removeEventListener("mouseup", self.moveEnd);
- },
- /* 鼠标全局绑定滑块移动与滑动结束,移动过程中鼠标可在页面任何位置 */
- addMouseMoveListener() {
- let self = this;
- document.addEventListener("mousemove", self.moving);
- document.addEventListener("mouseup", self.moveEnd);
- },
- //换一张
- clkNext() {
- this.GetSlideBlockApi();
- },
-
- // 重置滑块位置
- initial() {
- this.blockLeft = 0;
- this.bgColSet = false;
- this.isSuccess = false;
- },
- clkCloseRef() {
- this.showModal = false;
- this.isVefCode = false;
- },
- clkOpenRef(msg) {
- this.showModal = true;
- this.isVefCode = true;
- },
- }
- }
- script>
- <style lang="scss" type="text/css" rel="stylesheet" scoped="scoped">
- .framework {
- box-sizing: border-box;
- width: 332px;
- height: 270px;
- background: #fff;
- margin: 24px auto;
- z-index: 11;
- position: relative;
- padding: 0 16px;
- user-select: none;
- border-radius: 16px;
- }
-
- .framework .boxTopTitle {
- height: 48px;
- line-height: 48px;
- display: flex;
- justify-content: space-between;
- }
-
- .framework .boxImg {
- width: 300px;
- height: 150px;
- background: #fff;
- margin-bottom: 16px;
- border-radius: 8px;
- position: relative;
- }
-
- .reset {
- position: absolute;
- top: 0;
- right: 0;
- background-color: rgba(0, 0, 0, 0.24);
- border-radius: 0 8px 0 8px;
- padding: 0 12px;
- line-height: 32px;
- }
-
- .cutImgSet {
- position: absolute;
- }
-
- .frame--dialog {
- .framework {
- // margin: 38vh auto;
- z-index: 88888888;
- position: fixed;
- // top: 25vh;
- transform: translateX(-50%);
- left: 50%;
- }
-
- .frameBg {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.24);
- z-index: 8887 !important;
- }
- }
-
- .checkBox .checkBar {
- width: 100%;
- padding: 0px;
- }
-
- .slide {
- box-sizing: border-box;
- width: 100%;
- height: 40px;
- line-height: 40px;
- border-radius: 8px;
- background-color: #FFFFFF;
- position: relative;
- font-size: 13px;
- overflow: hidden;
- border: 1px solid rgba(65, 157, 231, 0.56);
- }
-
- .slide.active {
- border: 1px solid #FA7F7F;
- }
-
- .moveBac {
- background-color: rgba(21, 132, 223, 0.08);
- width: 100%;
- height: 100%;
- }
-
- .moveBacError {
- background-color: rgba(216, 63, 63, 0.08) !important;
- }
-
- .swiperTips {
- box-sizing: border-box;
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- color: rgba(255, 255, 255, 0.24);
- text-align: center;
- background: -webkit-gradient(linear, left top, right top, color-stop(0, #0076D6), color-stop(.4, #0076D6), color-stop(.5, #fff), color-stop(.6, #0076D6), color-stop(1, #0076D6));
- animation: tipsBlinkan 3s infinite;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- line-height: 40px;
- }
-
- .swiperTipsError {
- box-sizing: border-box;
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- text-align: center;
- color: rgba(255, 255, 255, 0.24);
- background: -webkit-gradient(linear, left top, right top, color-stop(0, #FA7F7F), color-stop(.4, #FA7F7F), color-stop(.5, #fff), color-stop(.6, #FA7F7F), color-stop(1, #FA7F7F));
- animation: tipsBlinkan 3s infinite;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- line-height: 40px;
- }
-
- .swiperBlock {
- width: 40px;
- height: 40px;
- border-radius: 8px;
- /* #ifdef MP-BAIDU */
- background-repeat: no-repeat;
- background-size: 16px;
- background-position: center;
- /* #endif */
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- left: 0px;
- top: -1px;
- }
-
- .successBlock {
- background-color: #0076D6;
- /* #ifdef MP-BAIDU */
- background-image: url('https://m.by56.com/Images/doubleArrow.svg');
- /* #endif */
- }
-
- .errorBlock {
- background-color: #FA7F7F !important;
- }
-
- .paddingL40 {
- padding-left: 40px;
- }
-
- .animation {
- transition: all 0.5s;
- }
-
- @keyframes tipsBlinkan {
- 0% {
- background-position: -100px 0;
- }
-
- 100% {
- background-position: 100px 0;
- }
- }
- style>