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 相关文章推荐
科讯商业版中用到的ajax空间与分页函数
Sep 02 Javascript
Javascript 跨域访问解决方案
Feb 14 Javascript
JavaScript 操作键盘的Enter事件(键盘任何事件),兼容多浏览器
Oct 11 Javascript
用js实现小球的自由移动代码
Apr 22 Javascript
bootstrap插件treeview实现全选父节点下所有子节点和反选功能
Jul 21 Javascript
浅谈angular4生命周期钩子
Sep 05 Javascript
JS实现的哈夫曼编码示例【原始版与修改版】
Apr 22 Javascript
JavaScript解决浮点数计算不准确问题的方法分析
Jul 09 Javascript
微信小程序视图容器(swiper)组件创建轮播图
Jun 19 Javascript
详解Vue用cmd创建项目
Feb 12 Javascript
修改vue源码实现动态路由缓存的方法
Jan 21 Javascript
javascript实现京东登录显示隐藏密码
Aug 02 Javascript
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
PHP4实际应用经验篇(8)
2006/10/09 PHP
浅析PHP的ASCII码转换类
2013/07/05 PHP
yii用户注册表单验证实例
2015/12/26 PHP
浅析php如何实现爬取数据原理
2018/09/27 PHP
PHP5.0 TIDY_PARSE_FILE缓冲区溢出漏洞的解决方案
2018/10/14 PHP
Laravel中10个有用的用法小结
2019/05/06 PHP
js获取元素到文档区域document的(横向、纵向)坐标的两种方法
2013/05/17 Javascript
原生javascript模仿win8等待提示圆圈进度条
2014/04/24 Javascript
node.js中的fs.mkdir方法使用说明
2014/12/17 Javascript
JavaScript实现简单获取当前网页网址的方法
2015/11/09 Javascript
AngularJS中处理多个promise的方式
2016/02/02 Javascript
javascript移动开发中touch触摸事件详解
2016/03/18 Javascript
require.js 加载 vue组件 r.js 合并压缩的实例
2016/10/14 Javascript
MUI 解决动态列表页图片懒加载再次加载不成功的bug问题
2017/04/13 Javascript
Vue.js 2.0 移动端拍照压缩图片预览及上传实例
2017/04/27 Javascript
mac下的nodejs环境安装的步骤
2017/05/24 NodeJs
Vue2.0结合webuploader实现文件分片上传功能
2018/03/09 Javascript
Vue-cli配置打包文件本地使用的教程图解
2018/08/02 Javascript
[01:51]DAC趣味视频-如何成为职业选手.mp4
2017/04/02 DOTA
Anaconda+Pycharm环境下的PyTorch配置方法
2020/03/13 Python
python实现对变位词的判断方法
2020/04/05 Python
聊聊Python pandas 中loc函数的使用,及跟iloc的区别说明
2021/03/03 Python
美国在线精品家居网站:Burke Decor
2017/04/12 全球购物
华为的Java面试题
2014/03/07 面试题
Sony C++笔试题
2013/03/10 面试题
abstract 可以和 virtual 一起使用吗?可以和 override 一起使用吗?
2012/10/15 面试题
装饰资料员岗位职责
2013/12/30 职场文书
建筑班组长岗位职责
2014/01/02 职场文书
珍惜资源的建议书
2014/08/26 职场文书
2014年前台个人工作总结
2014/11/14 职场文书
社会实践活动总结
2015/02/05 职场文书
2015年高一班主任工作总结
2015/05/13 职场文书
Java多条件判断场景中规则执行器的设计
2021/06/26 Java/Android
pandas数值排序的实现实例
2021/07/25 Python
用Python生成会跳舞的美女
2022/01/18 Python