• uniapp、vue实现滑动拼图验证码


    uniapp、vue实现滑动拼图验证码


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

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


    一、不对接口版本

    平台兼容性,H5、微信小程序、字节、百度
    XXX.vue文件引入
    template标签中引入

    "sliderVerifyFLag" @touchSliderResult="verifyResult" ref="verifyElement">

    script标签中引入组件

    1. import sliderVerify from '@/components/slider-verify/slider-verify.vue';//组件存放的路径
    2. export default {
    3. components: {
    4. 'slider-verify': sliderVerify
    5. },
    6. data() {
    7. return {
    8. sliderVerifyFLag: false //滑块验证
    9. };
    10. },
    11. onLoad() {},
    12. methods: {
    13. // 滑块验证结果回调函数
    14. verifyResult(res) {
    15. this.sliderVerifyFLag = false;
    16. if (res) { //校验通过
    17. }else{
    18. // 校验失败,点击关闭按钮
    19. }
    20. }
    21. }
    22. };

    在这里插入图片描述
    slider-verify.vue文件代码

    1. <script>
    2. export default {
    3. name: 'slider-verify',
    4. props: {
    5. isShow: true
    6. },
    7. data() {
    8. return {
    9. x: 0, //初始距离
    10. oldx: 0, //移动的距离
    11. img: '1', //显示哪张图片
    12. left: 0, //随机拼图的最终X轴距离
    13. top: 0, //拼图的top距离
    14. };
    15. },
    16. watch: {
    17. // 每次打开重新刷新拼图
    18. isShow(newValue, oldValue) {
    19. if(newValue){
    20. this.refreshVerify(); //刷新
    21. }
    22. }
    23. },
    24. mounted() {
    25. var that = this;
    26. that.refreshVerify();
    27. },
    28. methods: {
    29. //刷新验证
    30. refreshVerify() {
    31. var gl = Math.random().toFixed(2);
    32. this.left = uni.upx2px(560) * gl > uni.upx2px(280) ? uni.upx2px(280) : uni.upx2px(560) * gl + uni.upx2px(150); //生成随机X轴最终距离
    33. this.top = uni.upx2px(190) * gl; //生成随机Y轴初始距离
    34. if (gl <= 0.2) {
    35. this.img = 1;
    36. }
    37. if (gl > 0.2 && gl <= 0.4) {
    38. this.img = 2;
    39. }
    40. if (gl > 0.4 && gl <= 0.6) {
    41. this.img = 3;
    42. }
    43. if (gl > 0.6 && gl <= 0.8) {
    44. this.img = 4;
    45. }
    46. if (gl > 0.8 && gl <= 1) {
    47. this.img = 5;
    48. }
    49. this.resetMove(); //重置阴影位置
    50. },
    51. /* 滑动中 */
    52. startMove(e) {
    53. this.oldx = e.detail.x;
    54. },
    55. /* 滑动结束 */
    56. endTouchMove() {
    57. var that = this;
    58. if (Math.abs(that.oldx - that.left) <= 5) {
    59. uni.showToast({
    60. title: '验证成功',
    61. duration: 2500,
    62. success() {
    63. that.$emit('touchSliderResult', true);
    64. }
    65. });
    66. } else {
    67. that.refreshVerify();
    68. }
    69. },
    70. /* 重置阴影位置 */
    71. resetMove() {
    72. this.x = 1;
    73. this.oldx = 1;
    74. setTimeout(() => {
    75. this.x = 0;
    76. this.oldx = 0;
    77. }, 300);
    78. },
    79. // 关闭
    80. closeSlider(){
    81. this.$emit('touchSliderResult', false);
    82. }
    83. }
    84. };
    85. script>
    86. <style lang="less">
    87. .slider-verify-box {
    88. position: fixed;
    89. top: 0;
    90. left: 0;
    91. width: 100%;
    92. height: 100%;
    93. background-color: rgba(0, 0, 0, 0.5);
    94. z-index: 999;
    95. }
    96. .verifyBox {
    97. position: absolute;
    98. top: 50%;
    99. left: 50%;
    100. transform: translate(-50%, -50%);
    101. // width: 85%;
    102. background-color: #fff;
    103. border-radius: 20upx;
    104. box-shadow: 0 0 5upx rgba(0, 0, 0);
    105. .slider-title {
    106. font-size: 36upx;
    107. text-align: center;
    108. padding: 1em 0;
    109. color: rgba(2, 20, 33, 0.85);
    110. border-bottom: 1px solid rgba(2, 20, 33, 0.15);
    111. }
    112. .slide-content {
    113. width: 560rpx;
    114. padding: 0 1em;
    115. margin: 0 auto;
    116. .slide-tips {
    117. font-size: 28rpx;
    118. color: rgba(2, 20, 33, 0.45);
    119. padding: 0.5em 0;
    120. }
    121. .slider-pintu {
    122. position: relative;
    123. width: 100%;
    124. border-radius: 10rpx;
    125. overflow: hidden;
    126. .pintu {
    127. width: 560rpx;
    128. height: 315rpx;
    129. display: block;
    130. margin: 0 auto;
    131. }
    132. .pintukuai {
    133. position: absolute;
    134. top: 0;
    135. left: 0;
    136. width: 120rpx;
    137. height: 120rpx;
    138. z-index: 100;
    139. box-shadow: 0 0 5upx rgba(0, 0, 0, 0.3);
    140. overflow: hidden;
    141. image {
    142. display: block;
    143. position: absolute;
    144. top: 0;
    145. left: 0;
    146. width: 560rpx;
    147. height: 315rpx;
    148. }
    149. }
    150. }
    151. .yinying {
    152. position: absolute;
    153. width: 120rpx;
    154. height: 120rpx;
    155. background-color: rgba(0, 0, 0, 0.5);
    156. }
    157. }
    158. }
    159. .slider-movearea {
    160. position: relative;
    161. height: 80upx;
    162. width: 100%;
    163. margin: 25upx auto;
    164. movable-area {
    165. height: 80upx;
    166. width: 100%;
    167. movable-view {
    168. width: 80upx;
    169. height: 80upx;
    170. border-radius: 50%;
    171. background-color: #007cff;
    172. background-image: url(../../static/images/slider-verify/icon-button-normal.png);
    173. background-repeat: no-repeat;
    174. background-size: auto 30upx;
    175. background-position: center;
    176. position: relative;
    177. z-index: 100;
    178. }
    179. }
    180. }
    181. .huadao {
    182. width: 100%;
    183. height: 66upx;
    184. line-height: 66upx;
    185. background: #eee;
    186. box-shadow: inset 0 0 5upx #ccc;
    187. border-radius: 40rpx;
    188. color: #999;
    189. text-align: center;
    190. box-sizing: border-box;
    191. position: absolute;
    192. top: 7rpx;
    193. left: 0;
    194. font-size: 28rpx;
    195. z-index: 99;
    196. }
    197. .slider-btn-group{
    198. width: 100%;
    199. display: flex;
    200. justify-content: center;
    201. align-items: center;
    202. border-top: 1px solid rgba(2, 20, 33, 0.15);
    203. .slider-btn{
    204. flex: 1;
    205. height: 100rpx;
    206. line-height: 100rpx;
    207. text-align: center;
    208. font-size: 36rpx;
    209. color:rgba(2,20,33,0.85);
    210. &:active{
    211. opacity: 0.8;
    212. }
    213. }
    214. .slide-btn-refresh{
    215. color:rgba(14,107,176,1);
    216. border-left: 1px solid rgba(2, 20, 33, 0.15);
    217. }
    218. }
    219. style>

    二、对接口版本
    在这里插入图片描述

    平台兼容性,H5、微信小程序、字节、百度、qq等
    XXX.vue文件引入
    template标签中引入

    1. "getCode()" >获取验证码
    2. <validationPT class="setVef" ref="vefCode" @VefCodeTrue="getVefCodeTrue" :inputPhone="mobile" theme="dialog">validationPT>

    script标签中引入组件

    1. import validationPT from '@/components/validationPT/validationPT.vue'
    2. export default {
    3. components: {
    4. 'slider-verify': sliderVerify
    5. },
    6. data() {
    7. },
    8. onLoad() {},
    9. methods: {
    10. // 效验手机号
    11. getCode() {
    12. this.getCodePT();
    13. },
    14. //获取弹窗
    15. getCodePT(msg) {
    16. this.$refs.vefCode.GetSlideBlockApi(); //调用图片接口,获取验证图片
    17. this.$refs.vefCode.clkOpenRef(); //验证通过,打开拼图验证
    18. this.$refs.vefCode.initial();//重置
    19. },
    20. // 滑块验证结果回调函数
    21. //拼图验证是否成功
    22. getVefCodeTrue(msg) {
    23. this.VefInfosBk = msg;
    24. if (msg.Res == true) {
    25. //获取验证码
    26. }
    27. },
    28. }
    29. };

    validationPT.vue文件代码

    1. <script>
    2. export default {
    3. props: {
    4. inputPhone: '',
    5. theme: {
    6. type: String,
    7. },
    8. swiperColor: {
    9. type: String,
    10. default: 'rgba(21, 132, 223, 0.08)'
    11. },
    12. title: {
    13. type: String,
    14. default: '人机校验'
    15. },
    16. barWidth: {
    17. type: Number,
    18. default: 300
    19. }
    20. },
    21. data() {
    22. return {
    23. ImgUrl: this.hostwapUrl,
    24. BGImg: '',
    25. CutImg: '',
    26. CutImgY: '',
    27. MarkCode: '', //验证拼图是否成功用,
    28. bgColSet: false, //拼图是否验证成功
    29. isSuccess: false, //是否验证成功
    30. isVefCode: false,
    31. showModal: false,
    32. startInfo: {},
    33. blockLeft: 0,//随机拼图的最终X轴距离
    34. isAnimation: false,
    35. msgType: '',
    36. }
    37. },
    38. computed: {
    39. trueMoveableW: function() {
    40. return this.barWidth - 40
    41. }
    42. },
    43. methods: {
    44. //获取图片接口
    45. GetSlideBlockApi() {
    46. let that = this;
    47. uni.request({
    48. url: url,//获取拼图接口
    49. data: data,//需要传给接口的参数
    50. headers: {
    51. Accept: "application/json; charset=utf-8"
    52. },
    53. dataType: 'json',
    54. method: 'GET',
    55. success: (res) => {
    56. this.BGImg = '';//大图
    57. this.CutImg = '';//拼图
    58. this.CutImgY = 0;//接口位置
    59. },
    60. complete: () => {}
    61. })
    62. },
    63. //手指按下
    64. touchstartHandle({
    65. changedTouches
    66. }) {
    67. if (this.isSuccess) {
    68. return
    69. }
    70. this.isAnimation = false
    71. this.startInfo = changedTouches[0]
    72. },
    73. // 手指移动
    74. touchmoveHandle({
    75. changedTouches
    76. }) {
    77. if (this.isSuccess) {
    78. return
    79. }
    80. let blockLeft = changedTouches[0].clientX - this.startInfo.clientX
    81. let blockLeftRpx = blockLeft;
    82. if (blockLeftRpx < 0) {
    83. this.blockLeft = 0
    84. } else {
    85. this.blockLeft = blockLeftRpx <= this.trueMoveableW ? blockLeftRpx : this.trueMoveableW
    86. }
    87. },
    88. // 手指离开
    89. touchendHandle(e) {
    90. if (this.isSuccess) {
    91. return
    92. }
    93. this.CheckSlideBlockApi();
    94. },
    95. //验证图片接口
    96. CheckSlideBlockApi() {
    97. let that = this;
    98. uni.request({
    99. url: url,//接口名称
    100. data: data,//接口需要的参数
    101. headers: {
    102. Accept: "application/json; charset=utf-8"
    103. },
    104. dataType: 'json',
    105. method: 'POST',
    106. success: (res) => {
    107. let infos = {}
    108. if (res.data.IsOK == true) {//成功
    109. that.isSuccess = true;
    110. infos = {
    111. Res: res.data.Results,
    112. };//返回传给父组件的值,判断是否成功
    113. setTimeout(() => {
    114. that.bgColSet = false;
    115. that.$emit('VefCodeTrue', infos)
    116. that.isVefCode = false;
    117. that.showModal = false;
    118. }, 1000);
    119. } else {
    120. that.bgColSet = true;
    121. that.isSuccess = false;
    122. that.GetSlideBlockApi();
    123. that.isAnimation = true
    124. let timeid = setTimeout(() => {
    125. clearTimeout(timeid)
    126. that.isAnimation = false
    127. that.bgColSet = false;
    128. }, 500)
    129. that.blockLeft = 0;
    130. }
    131. },
    132. complete: () => {}
    133. })
    134. },
    135. /* 鼠标按住滑块后初始化移动监听,记录初始位置 */
    136. startMove(e) {
    137. e.preventDefault() //禁止图片img拖动的解决方法
    138. e = e || window.event;
    139. this.moveStart = e.pageX || e.targetTouches[0].pageX;
    140. this.addMouseMoveListener();
    141. },
    142. /* 鼠标滑块移动 */
    143. moving(e) {
    144. // e.preventDefault() //禁止图片img拖动的解决方法
    145. let self = this;
    146. e = e || window.event;
    147. let moveX = (e.pageX || e.targetTouches[0].pageX);
    148. let d = moveX - self.moveStart;
    149. let w = self.dataWidth;
    150. let PL_Size = this.puzzleSize;
    151. let padding = this.padding;
    152. if (self.moveStart === "") {
    153. return "";
    154. }
    155. if (d < 0 || d > w - padding - PL_Size) {
    156. return "";
    157. }
    158. if (d <= 260) {
    159. self.blockLeft = d
    160. }
    161. },
    162. /* 鼠标移动结束,验证并回调 */
    163. moveEnd(e) {
    164. let self = this;
    165. e = e || window.event;
    166. let moveEnd_X = (e.pageX || e.changedTouches[0].pageX) - self.moveStart;
    167. if (moveEnd_X > 0) {
    168. self.CheckSlideBlockApi(); //验证拼图是否成功
    169. }
    170. self.moveStart = "";
    171. document.removeEventListener("mousemove", self.moving);
    172. document.removeEventListener("mouseup", self.moveEnd);
    173. },
    174. /* 鼠标全局绑定滑块移动与滑动结束,移动过程中鼠标可在页面任何位置 */
    175. addMouseMoveListener() {
    176. let self = this;
    177. document.addEventListener("mousemove", self.moving);
    178. document.addEventListener("mouseup", self.moveEnd);
    179. },
    180. //换一张
    181. clkNext() {
    182. this.GetSlideBlockApi();
    183. },
    184. // 重置滑块位置
    185. initial() {
    186. this.blockLeft = 0;
    187. this.bgColSet = false;
    188. this.isSuccess = false;
    189. },
    190. clkCloseRef() {
    191. this.showModal = false;
    192. this.isVefCode = false;
    193. },
    194. clkOpenRef(msg) {
    195. this.showModal = true;
    196. this.isVefCode = true;
    197. },
    198. }
    199. }
    200. script>
    201. <style lang="scss" type="text/css" rel="stylesheet" scoped="scoped">
    202. .framework {
    203. box-sizing: border-box;
    204. width: 332px;
    205. height: 270px;
    206. background: #fff;
    207. margin: 24px auto;
    208. z-index: 11;
    209. position: relative;
    210. padding: 0 16px;
    211. user-select: none;
    212. border-radius: 16px;
    213. }
    214. .framework .boxTopTitle {
    215. height: 48px;
    216. line-height: 48px;
    217. display: flex;
    218. justify-content: space-between;
    219. }
    220. .framework .boxImg {
    221. width: 300px;
    222. height: 150px;
    223. background: #fff;
    224. margin-bottom: 16px;
    225. border-radius: 8px;
    226. position: relative;
    227. }
    228. .reset {
    229. position: absolute;
    230. top: 0;
    231. right: 0;
    232. background-color: rgba(0, 0, 0, 0.24);
    233. border-radius: 0 8px 0 8px;
    234. padding: 0 12px;
    235. line-height: 32px;
    236. }
    237. .cutImgSet {
    238. position: absolute;
    239. }
    240. .frame--dialog {
    241. .framework {
    242. // margin: 38vh auto;
    243. z-index: 88888888;
    244. position: fixed;
    245. // top: 25vh;
    246. transform: translateX(-50%);
    247. left: 50%;
    248. }
    249. .frameBg {
    250. position: fixed;
    251. top: 0;
    252. left: 0;
    253. width: 100%;
    254. height: 100%;
    255. background-color: rgba(0, 0, 0, 0.24);
    256. z-index: 8887 !important;
    257. }
    258. }
    259. .checkBox .checkBar {
    260. width: 100%;
    261. padding: 0px;
    262. }
    263. .slide {
    264. box-sizing: border-box;
    265. width: 100%;
    266. height: 40px;
    267. line-height: 40px;
    268. border-radius: 8px;
    269. background-color: #FFFFFF;
    270. position: relative;
    271. font-size: 13px;
    272. overflow: hidden;
    273. border: 1px solid rgba(65, 157, 231, 0.56);
    274. }
    275. .slide.active {
    276. border: 1px solid #FA7F7F;
    277. }
    278. .moveBac {
    279. background-color: rgba(21, 132, 223, 0.08);
    280. width: 100%;
    281. height: 100%;
    282. }
    283. .moveBacError {
    284. background-color: rgba(216, 63, 63, 0.08) !important;
    285. }
    286. .swiperTips {
    287. box-sizing: border-box;
    288. position: absolute;
    289. left: 0;
    290. top: 0;
    291. width: 100%;
    292. color: rgba(255, 255, 255, 0.24);
    293. text-align: center;
    294. 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));
    295. animation: tipsBlinkan 3s infinite;
    296. -webkit-background-clip: text;
    297. -webkit-text-fill-color: transparent;
    298. line-height: 40px;
    299. }
    300. .swiperTipsError {
    301. box-sizing: border-box;
    302. position: absolute;
    303. left: 0;
    304. top: 0;
    305. width: 100%;
    306. text-align: center;
    307. color: rgba(255, 255, 255, 0.24);
    308. 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));
    309. animation: tipsBlinkan 3s infinite;
    310. -webkit-background-clip: text;
    311. -webkit-text-fill-color: transparent;
    312. line-height: 40px;
    313. }
    314. .swiperBlock {
    315. width: 40px;
    316. height: 40px;
    317. border-radius: 8px;
    318. /* #ifdef MP-BAIDU */
    319. background-repeat: no-repeat;
    320. background-size: 16px;
    321. background-position: center;
    322. /* #endif */
    323. display: flex;
    324. justify-content: center;
    325. align-items: center;
    326. position: absolute;
    327. left: 0px;
    328. top: -1px;
    329. }
    330. .successBlock {
    331. background-color: #0076D6;
    332. /* #ifdef MP-BAIDU */
    333. background-image: url('https://m.by56.com/Images/doubleArrow.svg');
    334. /* #endif */
    335. }
    336. .errorBlock {
    337. background-color: #FA7F7F !important;
    338. }
    339. .paddingL40 {
    340. padding-left: 40px;
    341. }
    342. .animation {
    343. transition: all 0.5s;
    344. }
    345. @keyframes tipsBlinkan {
    346. 0% {
    347. background-position: -100px 0;
    348. }
    349. 100% {
    350. background-position: 100px 0;
    351. }
    352. }
    353. style>

  • 相关阅读:
    Codeforcess834
    CentOS 管理多版本gcc —— 筑梦之路
    【PostgreSQL】PostgreSQL 15移除了Stats Collector
    Git 回退代码的两种方法对比
    day58:ARMday5,GPIO流水灯实验
    动态规划(AcWing): 1)计数类DP,2)树形DP,3)记忆化搜索,4)状态压缩DP,5)数位统计DP;
    Filebeat 如何保持文件状态?
    大麦网-演出赛事票务系统画uml图
    思维导图软件 ConceptDraw MINDMAP mac中文特色介绍
    使用设计模式基于easypoi优雅的设计通用excel导入功能
  • 原文地址:https://blog.csdn.net/meimeieee/article/details/133255621