vue项目中极验验证的使用代码示例


Posted in Javascript onDecember 03, 2019
使用vue、react的项目获取数据、上传数据、注册、登陆等都是通过接口来完成的,接口很容易被人恶意调用,最容易被人恶意调用的接口就是注册、登陆类的接口,为了防止接口被恶意调用很多公司开发了出了很多的人机验证的工具,今天就来讲下极验验证在vue项目中的使用。

效果预览

vue项目中极验验证的使用代码示例

1、遇到的问题

  • 在项目中任何一个页面或vue组件都有可能需要使用到极验,并且极验在初始化时需要传递一些参数,要怎么做才能做到每一个组件都能很方便的使用极验呢?
  • 极验应该在什么时候初始化?是组件一加载就初始化还是用户点击按钮后再初始化?
  • 在多语言项目中,用户手动切换语言后的极验重置

2、代码分享

为了在多个页面或多个组件中都能方便的使用极验,我将极验初始化的相关代码写到了mixins中。这样做的好处是:方便在组件中获取极验相关的数据,以及调用极验相关api,如做重置、销毁等操作;缺点是:在同一个页面中无法在多个地方使用mixin,但这也是有解决方案的。

geetest-mixin.js

/*
 极验mixin
 */
// 导入极验官方给的代码
import gt from "../common/js/geetest/geetest.gt";
import {commonApi} from "../api/commonApi";
import {mapGetters} from "vuex";
// 自定义语言与极验语言对应表
const geetestLangMap = {
 "zh_CN": "zh-cn",
 "zh_TW": "zh-tw",
 "en_US": "en",
 "ja_JP": "ja",
 "ko_KR": "ko",
 "ru_RU": "ru"
};
console.log('gt',gt)
// 极验默认配置
const geetestOptions = {
 product: 'popup', // 极验展现形式 可选值有 float、popup、custom、bind
 width: '100%',
 lang: 'zh_CN',
 autoShow: true, // 当product为bind时,如果次参数为true,则在极验加载完成后立即显示极验弹窗
 area: null, // 极验绑定的元素,仅在 product为 custom、float、popup时需要传递
 autoRefreshOnLangChange: true, // 语言改变时是否自动刷新
};
export const geetestMixin = {
 data(){
  return {
   geetest: {
    geetestSuccessData: null, // 极验用户行为操作成功后的数据
    geetestObj: null, // 极验对象
    geetestLoading: false,
    geetestFatched: false, // 判断是否从服务器获取了极验数据
    showGeetest: false, // 是否使用极验
    sign: "", // 极验降级 用的签名
    geetestRestartCountMax: 5, // 极验重试最大此时
    count: 1,
    geetestOptions: {}
   }
  }
 },
 computed: {
  ...mapGetters(['get_lang'])
 },
 watch: {
  get_lang(lang){
   let options = this.geetest.geetestOptions;
   if(options.autoRefreshOnLangChange && this.geetest.geetestObj){
    this.initGeetest({
     ...options,
     lang: lang.code
    });
   }
  }
 },
 methods: {
  // 初始化极验
  initGeetest(options){
   if(!options || ({}).toString.call(options) !== '[object Object]'){
    console.error('initGeetest方法的参数options必须是一个对象!');
    return;
   }
   let newOptions = Object.assign({}, geetestOptions, options);
   if((newOptions.product === 'popup' || newOptions.product === 'custom' || newOptions.product === 'float') && !newOptions.area){
    console.error('product为popup、custom、float时options参数中必须有area属性,area属性值可以为css选择器或dom元素!');
    return;
   }
   this.geetest.geetestOptions = newOptions;
   this._geetestRegist_(newOptions);
  },
  // 重置极验
  geetestReset(cb){
   if(this.geetest.geetestObj){
    this.geetest.geetestSuccessData = {};
    this.geetest.geetestObj.reset();
    if(typeof cb === 'function'){
     cb();
    }
   }else{
    console.error('极验不存在!');
   }
  },
  // 显示极验弹窗,此方法只有在product为bind时才有效
  geetestShow(){
   if(this.geetest.geetestObj){
    if(this.geetest.geetestOptions.product === 'bind'){
     this.geetest.geetestObj.verify();
    }else{
     console.error('极验的product值非bind,无法调用show!');
    }
   }else{
    console.error('极验不存在!');
   }
  },
  // 初始化极验,内部使用
  _initGeetestInternal_(data, options){
   let that = this;
   let geetest = this.geetest;

   window.initGeetest({
    // 以下 4 个配置参数为必须,不能缺少
    gt: data.gt,
    challenge: data.challenge,
    offline: !data.success, // 表示用户后台检测极验服务器是否宕机
    new_captcha: true, // 用于宕机时表示是新验证码的宕机
    product: options.product, // 产品形式,包括:float,popup,bind
    width: options.width,
    lang: geetestLangMap[options.lang]
   }, function (captchaObj) {
        if(geetest.geetestObj){
         try {
          // 如果之前已经初始化过了,则线将之前生成的dom移除掉
          geetest.geetestObj.destroy();
         }catch (e) {
          console.error('极验销毁失败', e);
         }
        }
    geetest.geetestObj = captchaObj;
    if((options.product === 'popup' || options.product === 'custom' || options.product === 'float')){
     captchaObj.appendTo(options.area);
    }
    // 为bind模式时极验加载完成后自动弹出极验弹窗
    if(options.autoShow && options.product === 'bind'){
     captchaObj.onReady(() => {
      captchaObj.verify();
     });
    }
    geetest.geetestSuccessData = {};
    // 当用户操作后并且通过验证后的回调
    captchaObj.onSuccess(function () {
     let successData = captchaObj.getValidate();
     geetest.geetestSuccessData = successData;
     console.log('用户行为验证通过数据', successData);
     /*
      这种方式不可采用,原因,作用域会被缓存
      if (typeof options.callback === 'function') {
       options.callback(successData);
      }
      用户行为验证通过后调用回调函数
     */
     if(typeof that.onGeetestSuccess === 'function'){
      that.onGeetestSuccess(successData);
     }
    });
    captchaObj.onError(function (e) {
     console.error("极验出错了", e);
    });
    console.log('极验实例', captchaObj);
   });
  },
  // 调用接口,获取极验数据
  _geetestRegist_(options){
   if(this.geetest.geetestLoading){
    return;
   }
   this.geetest.geetestLoading = true;
   commonApi.getGtCaptcha()
    .then(res => {
     let data = res.data;
     // TIP 后台需要控制是否开启极验,因此需要判断 enable===true && success===1 才显示极限
     this.geetest.sign = data.sign;
     this.geetest.geetestFatched = true;
     if(typeof data.enable == "undefined" || (data.enable === true && data.success === 1)) {
      this.geetest.showGeetest = true;
     }else{
      this.geetest.showGeetest = false;
      this.geetest.geetestLoading = false;
      /*// 如果极验禁用,则调用onDisableGeetest回调
      if(typeof options.onDisableGeetest === 'function'){
       options.onDisableGeetest();
      }*/
      // 如果极验禁用,则调用onDisableGeetest回调
      if(typeof this.onDisableGeetest === 'function'){
       this.onDisableGeetest();
      }
      return
     }
     this.geetest.geetestLoading = false;
     this._initGeetestInternal_(data, options);
    })
    .catch((err) => {
     console.error('极验初始化失败', err);
     if(this.geetest.count > this.geetest.geetestRestartCountMax){
      this.geetest.geetestLoading = false;
      return;
     }
     console.log('正在重试初始化极验!当前次数:' + this.geetest.count);
     this.geetest.count++;
     this._geetestRegist_(options);
    });
  }
 },
 beforeDestroy(){
  if(this.geetest.geetestObj){
   this.geetest.geetestObj.destroy();
  }
 }
};

geetest.gt.js

段代码可以不用关注,极验官网有
/* initGeetest 1.0.0
 * 用于加载id对应的验证码库,并支持宕机模式
 * 暴露 initGeetest 进行验证码的初始化
 * 一般不需要用户进行修改
 */
var gtInit = (function (global, factory) {
 "use strict";
 if (typeof module === "object" && typeof module.exports === "object") {
  // CommonJS
  module.exports = global.document ?
   factory(global, true) :
   function (w) {
    if (!w.document) {
     throw new Error("Geetest requires a window with a document");
    }
    return factory(w);
   };
 } else {
  factory(global);
 }
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
 "use strict";
 if (typeof window === 'undefined') {
  throw new Error('Geetest requires browser environment');
 }
 var document = window.document;
 var Math = window.Math;
 var head = document.getElementsByTagName("head")[0];

 function _Object(obj) {
  this._obj = obj;
 }

 _Object.prototype = {
  _each: function (process) {
   var _obj = this._obj;
   for (var k in _obj) {
    if (_obj.hasOwnProperty(k)) {
     process(k, _obj[k]);
    }
   }
   return this;
  }
 };
 function Config(config) {
  var self = this;
  new _Object(config)._each(function (key, value) {
   self[key] = value;
  });
 }

 Config.prototype = {
  api_server: 'api.geetest.com',
  protocol: 'http://',
  type_path: '/gettype.php',
  fallback_config: {
   slide: {
    static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
    type: 'slide',
    slide: '/static/js/geetest.0.0.0.js'
   },
   fullpage: {
    static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
    type: 'fullpage',
    fullpage: '/static/js/fullpage.0.0.0.js'
   }
  },
  _get_fallback_config: function () {
   var self = this;
   if (isString(self.type)) {
    return self.fallback_config[self.type];
   } else if (self.new_captcha) {
    return self.fallback_config.fullpage;
   } else {
    return self.fallback_config.slide;
   }
  },
  _extend: function (obj) {
   var self = this;
   new _Object(obj)._each(function (key, value) {
    self[key] = value;
   })
  }
 };
 var isNumber = function (value) {
  return (typeof value === 'number');
 };
 var isString = function (value) {
  return (typeof value === 'string');
 };
 var isBoolean = function (value) {
  return (typeof value === 'boolean');
 };
 var isObject = function (value) {
  return (typeof value === 'object' && value !== null);
 };
 var isFunction = function (value) {
  return (typeof value === 'function');
 };
 var callbacks = {};
 var status = {};
 var random = function () {
  return parseInt(Math.random() * 10000) + (new Date()).valueOf();
 };
 var loadScript = function (url, cb) {
  var script = document.createElement("script");
  script.charset = "UTF-8";
  script.async = true;
  script.onerror = function () {
   cb(true);
  };
  var loaded = false;
  script.onload = script.onreadystatechange = function () {
   if (!loaded &&
    (!script.readyState ||
     "loaded" === script.readyState ||
     "complete" === script.readyState)) {

    loaded = true;
    setTimeout(function () {
     cb(false);
    }, 0);
   }
  };
  script.src = url;
  head.appendChild(script);
 };
 var normalizeDomain = function (domain) {
  return domain.replace(/^https?:\/\/|\/$/g, '');
 };
 var normalizePath = function (path) {
  path = path.replace(/\/+/g, '/');
  if (path.indexOf('/') !== 0) {
   path = '/' + path;
  }
  return path;
 };
 var normalizeQuery = function (query) {
  if (!query) {
   return '';
  }
  var q = '?';
  new _Object(query)._each(function (key, value) {
   if (isString(value) || isNumber(value) || isBoolean(value)) {
    q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&';
   }
  });
  if (q === '?') {
   q = '';
  }
  return q.replace(/&$/, '');
 };
 var makeURL = function (protocol, domain, path, query) {
  domain = normalizeDomain(domain);

  var url = normalizePath(path) + normalizeQuery(query);
  if (domain) {
   url = protocol + domain + url;
  }

  return url;
 };
 var load = function (protocol, domains, path, query, cb) {
  var tryRequest = function (at) {

   var url = makeURL(protocol, domains[at], path, query);
   loadScript(url, function (err) {
    if (err) {
     if (at >= domains.length - 1) {
      cb(true);
     } else {
      tryRequest(at + 1);
     }
    } else {
     cb(false);
    }
   });
  };
  tryRequest(0);
 };
 var jsonp = function (domains, path, config, callback) {
  if (isObject(config.getLib)) {
   config._extend(config.getLib);
   callback(config);
   return;
  }
  if (config.offline) {
   callback(config._get_fallback_config());
   return;
  }
  var cb = "geetest_" + random();
  window[cb] = function (data) {
   if (data.status === 'success') {
    callback(data.data);
   } else if (!data.status) {
    callback(data);
   } else {
    callback(config._get_fallback_config());
   }
   window[cb] = undefined;
   try {
    delete window[cb];
   } catch (e) {
   }
  };
  load(config.protocol, domains, path, {
   gt: config.gt,
   callback: cb
  }, function (err) {
   if (err) {
    callback(config._get_fallback_config());
   }
  });
 };
 var throwError = function (errorType, config) {
  var errors = {
   networkError: '网络错误'
  };
  if (typeof config.onError === 'function') {
   config.onError(errors[errorType]);
  } else {
   throw new Error(errors[errorType]);
  }
 };
 var detect = function () {
  return !!window.Geetest;
 };
 if (detect()) {
  status.slide = "loaded";
 }
 var initGeetest = function (userConfig, callback) {
  var config = new Config(userConfig);
  if (userConfig.https) {
   config.protocol = 'https://';
  } else if (!userConfig.protocol) {
   config.protocol = window.location.protocol + '//';
  }
  jsonp([config.api_server || config.apiserver], config.type_path, config, function (newConfig) {
   var type = newConfig.type;
   var init = function () {
    config._extend(newConfig);
    callback(new window.Geetest(config));
   };
   callbacks[type] = callbacks[type] || [];
   var s = status[type] || 'init';
   if (s === 'init') {
    status[type] = 'loading';
    callbacks[type].push(init);
    load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) {
     if (err) {
      status[type] = 'fail';
      throwError('networkError', config);
     } else {
      status[type] = 'loaded';
      var cbs = callbacks[type];
      for (var i = 0, len = cbs.length; i < len; i = i + 1) {
       var cb = cbs[i];
       if (isFunction(cb)) {
        cb();
       }
      }
      callbacks[type] = [];
     }
    });
   } else if (s === "loaded") {
    init();
   } else if (s === "fail") {
    throwError('networkError', config);
   } else if (s === "loading") {
    callbacks[type].push(init);
   }
  });
 };
 window.initGeetest = initGeetest;
 return initGeetest;
});

export default {
 gtInit
}

页面中使用

// 导入极验验证
import {geetestMixin} from "./geetest-mixin";
import {mapGetters} from "vuex";
import {commonApi} from "../api/commonApi";

export default {
 name: 'Regist',
 mixins: [geetestMixin],
 data(){
  return {
   form: {
    ...表单数据
   },
   committing: false,
   errMsg: ' ',.
  }
 },
 methods: {
  // 提交注册数据
  submitRegistData(){
   ...你的业务逻辑
   /*if(this.geetest.showGeetest){
    // 如果没有geetest_challenge,则说明用户没有进行行为验证
    if(!this.geetest.geetestSuccessData.geetest_challenge){
     this.errMsg = this.$t('formError.geetest'); // 点击按钮进行验证
     return;
    }
   }*/
   this.errMsg = '';


   if(!this.geetest.geetestObj){
    /*
     如果this.geetest.geetestFatched==true,则说明已经加载过极验了
     如果this.geetest.showGeetest==false,则说明后台关闭极验了
     */
    if(this.geetest.geetestFatched && !this.geetest.showGeetest){
     //this.committing = true;
     this.commitData();
    }else{
     this.initGeetest({
      product: 'bind',
      lang: this.get_lang.code,
     });
    }
   }else{
    if(this.geetest.showGeetest){
     this.geetestShow();
    }else{
     console.log('fasfsafsdfsd')
     //this.committing = true;
     this.commitData();
    }
   }
  },
  // 提交数据
  commitData(){
   if(this.committing){
    return;
   }
   this.committing = true;
   let data = {
    ...this.form
   };
   let geetestData = {};
   // 获取极验数据
   if(this.geetest.showGeetest){
    geetestData = {
     ...this.geetest.geetestSuccessData,
     sign: this.geetest.sign
    }
   }
   if(!this.form.inviteCode){
    delete data.inviteCode;
   }
   commonApi.regist(data, geetestData)
    .then(res => {
     this.committing = false;
     if(res.errcode === 0){
      ...你的业务逻辑
     }else{
     // 重置极验,使极验回到可操作状态
      this.geetestReset();
     }
    })
    .catch(() => {
     this.committing = false;
     // 重置极验,使极验回到可操作状态
     this.geetestReset();
    });
  },
  // 极验验证成功后回调
  onGeetestSuccess(){
   // 用户通过极验行为验证后做的操作
   this.commitData();
  },
  // 极验被禁用后回调
  onDisableGeetest(){
   this.commitData();
  }
 },
 computed: {
  ...mapGetters(['get_lang'])
 }
};

3、极验初始化时间问题

geetest-mixin.js设计的比较灵活,它可以允许你在任何时机初始化极验。但在项目中推荐在需要使用到的时候再初始化,1来可以节省流量;2来可以提升页面性能;3是最重要的一个点,在单页面应用程序中都是通过接口来访问数据的,而接口都有过期时间,如果组件初始化完成就立即初始化极验,而用户在接口过期后再去操作则会出现一些奇葩的bug

4、多语言项目中用户手动切换语言的问题

由于单页面应用程序切换语言后页面不会刷新,所以就会出现页面语言已经切换了,但极验还是使用的原来的语言。我的解决方案就是在用户切换语言后手动的刷新一下极验

{
 watch: {
  get_lang(lang){
   let options = this.geetest.geetestOptions;
   // 如果开启了语言切换自手动刷新极验,并且极验已经初始化了则刷新。如果极验都还没有初始化则可以不用去刷新
   if(options.autoRefreshOnLangChange && this.geetest.geetestObj){
    this.initGeetest({
     ...options,
     lang: lang.code
    });
   }
  }
 }
}

5、关于点击按钮时按钮loading效果的控制

如《效果预览》图中的获取验证码loading效果控制,可以通过geetest.geetestLoading来进行判断

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js调用css属性写法
Sep 21 Javascript
写JQuery插件的基本知识
Nov 25 Javascript
JQuery中$.ajax()方法参数详解及应用
Dec 12 Javascript
对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解
Apr 11 Javascript
基于Jquery插件实现跨域异步上传文件功能
Apr 26 Javascript
jquery ezUI 双击行记录弹窗查看明细的实现方法
Jun 01 Javascript
JS左右无缝轮播功能完整实例
May 16 Javascript
解决layui的input独占一行的问题
Sep 10 Javascript
js实现带搜索功能的下拉框
Jan 11 Javascript
JS加载解析Markdown文档过程详解
May 19 Javascript
bootstrap实现tab选项卡切换
Aug 09 Javascript
Vue-Element-Admin集成自己的接口实现登录跳转
Jun 23 Vue.js
jQuery轮播图功能制作方法详解
Dec 03 #jQuery
JS实现的雪花飘落特效示例
Dec 03 #Javascript
vue中实现高德定位功能
Dec 03 #Javascript
node静态服务器实现静态读取文件或文件夹
Dec 03 #Javascript
js实现时分秒倒计时
Dec 03 #Javascript
Vue实现验证码功能
Dec 03 #Javascript
JS实现压缩上传图片base64长度功能
Dec 03 #Javascript
You might like
php+mysql分页代码详解
2008/03/27 PHP
php 数组二分法查找函数代码
2010/02/16 PHP
php xml 入门学习资料
2011/01/01 PHP
PHP strip_tags()去除HTML、XML以及PHP的标签介绍
2014/02/18 PHP
ThinkPHP中使用ajax接收json数据的方法
2014/12/18 PHP
php操作xml入门之cdata区段
2015/01/23 PHP
PHP实现CSV文件的导入和导出类
2015/03/24 PHP
PHP基于cookie实现统计在线人数功能示例
2019/01/16 PHP
JavaScript针对网页节点的增删改查用法实例
2015/02/02 Javascript
jQuery实现可用于博客的动态滑动菜单完整实例
2015/09/17 Javascript
理解JS事件循环
2016/01/07 Javascript
使用JS实现图片轮播的实例(前后首尾相接)
2017/09/21 Javascript
vue中动态设置meta标签和title标签的方法
2018/07/11 Javascript
Vue.js上传图片到阿里云OSS存储的方法示例
2018/12/13 Javascript
vue实现给div绑定keyup的enter事件
2020/07/31 Javascript
python实现2048小游戏
2015/03/30 Python
Python基于PycURL自动处理cookie的方法
2015/07/25 Python
Python简单实现enum功能的方法
2016/04/25 Python
python使用正则表达式替换匹配成功的组并输出替换的次数
2017/11/22 Python
Anaconda下配置python+opencv+contribx的实例讲解
2018/08/06 Python
Django使用Channels实现WebSocket的方法
2019/07/28 Python
python使用Matplotlib改变坐标轴的默认位置
2019/10/18 Python
pygame实现贪吃蛇游戏(下)
2019/10/29 Python
python pandas dataframe 去重函数的具体使用
2020/07/20 Python
15个应该掌握的Jupyter Notebook使用技巧(小结)
2020/09/23 Python
关于Java finally的面试题
2016/04/27 面试题
电大学习个人自我评价范文
2013/10/04 职场文书
公务员政审个人鉴定
2014/02/25 职场文书
实习单位评语
2014/04/26 职场文书
老公保证书范文
2014/04/29 职场文书
服务理念标语
2014/06/18 职场文书
中小学校园安全广播稿
2014/09/29 职场文书
考研导师推荐信范文
2015/03/27 职场文书
2015年机械设备管理工作总结
2015/05/04 职场文书
教师读书笔记
2015/06/29 职场文书
python​格式化字符串
2022/04/20 Python