From 0118470c671707460aa1fc5cd4a87a195d2ef6ca Mon Sep 17 00:00:00 2001 From: nanhaoluo <3075912108@qq.com> Date: Tue, 20 Jan 2026 18:35:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E5=AE=98=E6=96=B9?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=89=88=20Geetest=20GT4=20=E5=BA=93?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E7=AE=80=E5=8C=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 https://static.geetest.com/v4/gt4.js 下载官方完整版本 - 替换之前的简化备用实现 - 确保所有 API 方法完整可用(包括 onClose) - 提供完整的验证码功能支持 --- assets/vendor/external/geetest/gt4.js | 537 +++++++++++++++++++++++--- 1 file changed, 487 insertions(+), 50 deletions(-) diff --git a/assets/vendor/external/geetest/gt4.js b/assets/vendor/external/geetest/gt4.js index 69141e3..772e096 100644 --- a/assets/vendor/external/geetest/gt4.js +++ b/assets/vendor/external/geetest/gt4.js @@ -1,50 +1,487 @@ -/* Geetest GT4 - Local Fallback */ -(function() { - 'use strict'; - - // 简化的 Geetest 备用实现 - window.initGeetest4 = function(config, callback) { - console.warn('Geetest GT4 本地备用版本 - 功能受限'); - - // 创建一个简单的验证码替代 - var captcha = { - appendTo: function(selector) { - var container = document.querySelector(selector); - if (container) { - container.innerHTML = '
' + - '

验证码服务暂时不可用,请稍后重试

' + - '' + - '
'; - } - return this; - }, - onReady: function(fn) { - setTimeout(fn, 100); - return this; - }, - onSuccess: function(fn) { - // 模拟成功回调 - return this; - }, - onError: function(fn) { - return this; - }, - onClose: function(fn) { - // 备用版本不支持关闭事件,但提供空实现避免错误 - return this; - }, - getValidate: function() { - return { - lot_number: 'fallback_' + Date.now(), - captcha_output: 'fallback_output', - pass_token: 'fallback_token', - gen_time: Date.now() - }; - } - }; - - if (typeof callback === 'function') { - callback(captcha); - } - }; -})(); \ No newline at end of file +"v4.2.0 Geetest Inc."; + +(function (window) { + "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]; +var TIMEOUT = 10000; + +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; + }, + _extend: function (obj){ + var self = this; + new _Object(obj)._each(function (key, value){ + self._obj[key] = value; + }) + } +}; + +var uuid = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0; + var v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + +function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); +} + +Config.prototype = { + apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'], + staticServers: ["static.geetest.com",'static.geevisit.com'], + protocol: 'http://', + typePath: '/load', + fallback_config: { + bypass: { + staticServers: ["static.geetest.com",'static.geevisit.com'], + type: 'bypass', + bypass: '/v4/bypass.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else { + return self.fallback_config.bypass; + } + }, + _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 MOBILE = /Mobi/i.test(navigator.userAgent); + +var callbacks = {}; +var status = {}; + +var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); +}; + +// bind 函数polify, 不带new功能的bind + +var bind = function(target,context){ + if(typeof target !== 'function'){ + return; + } + var args = Array.prototype.slice.call(arguments,2); + + if(Function.prototype.bind){ + return target.bind(context, args); + }else { + return function(){ + var _args = Array.prototype.slice.call(arguments); + return target.apply(context,args.concat(_args)); + } + } +} + + + +var toString = Object.prototype.toString; + +var _isFunction = function(obj) { + return typeof(obj) === 'function'; +}; +var _isObject = function(obj) { + return obj === Object(obj); +}; +var _isArray = function(obj) { + return toString.call(obj) == '[object Array]'; +}; +var _isDate = function(obj) { + return toString.call(obj) == '[object Date]'; +}; +var _isRegExp = function(obj) { + return toString.call(obj) == '[object RegExp]'; +}; +var _isBoolean = function(obj) { + return toString.call(obj) == '[object Boolean]'; +}; + + +function resolveKey(input){ + return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){ + return $1 + $3.toUpperCase() || ""; + }) +} + +function camelizeKeys(input, convert){ + if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){ + return convert ? resolveKey(input) : input; + } + + if(_isArray(input)){ + var temp = []; + for(var i = 0; i < input.length; i++){ + temp.push(camelizeKeys(input[i])); + } + + }else { + var temp = {}; + for(var prop in input){ + if(input.hasOwnProperty(prop)){ + temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]); + } + } + } + return temp; +} + +var loadScript = function (url, cb, timeout) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetest的静态资源添加 crossOrigin + if ( /static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + // 错误触发了,超时逻辑就不用了 + loaded = 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); + + setTimeout(function () { + if (!loaded) { + script.onerror = script.onload = null; + script.remove && script.remove(); + cb(true); + } + }, timeout || TIMEOUT); +}; + +var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest +}; +var normalizePath = function (path) { + + 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 (config, protocol, domains, path, query, cb, handleCb) { + var tryRequest = function (at) { + // 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数 + if(handleCb){ + var cbName = "geetest_" + random(); + // 需要与预先定义好cbname参数,删除对象 + window[cbName] = bind(handleCb, null, cbName); + query.callback = cbName; + } + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + // 超时或者出错的时候 移除回调 + if(cbName){ + try { + window[cbName] = function(){ + window[cbName] = null; + } + } catch (e) {} + } + + if (at >= domains.length - 1) { + cb(true); + // report gettype error + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }, config.timeout); + }; + tryRequest(0); +}; + + +var jsonp = function (domains, path, config, callback) { + + var handleCb = function (cbName, data) { + + // 保证只执行一次,全部超时的情况下不会再触发; + + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + //接口有返回,但是返回了错误状态,进入报错逻辑 + callback(data); + } + window[cbName] = undefined; + try { + delete window[cbName]; + } catch (e) { + } + }; + load(config, config.protocol, domains, path, { + callback: '', + captcha_id: config.captchaId, + challenge: config.challenge || uuid(), + client_type: config.clientType ? config.clientType : (MOBILE? 'h5':'web'), + risk_type: config.riskType, + user_info: config.userInfo, + call_type: config.callType, + lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase() + }, function (err) { + // 网络问题接口没有返回,直接使用本地验证码,走宕机模式 + // 这里可以添加用户的逻辑 + if(err && typeof config.offlineCb === 'function'){ + // 执行自己的宕机 + config.offlineCb(); + return; + } + if(err){ + callback(config._get_fallback_config()); + } + }, handleCb); +}; + +var reportError = function (config, url) { + load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: Date.now().getTime(), + captcha_id: config.gt, + challenge: config.challenge, + exception_url: url, + error_code: config.error_code + }, function (err) {}) +} + +var throwError = function (errorType, config, errObj) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError({ + desc: errObj.desc, + msg: errObj.msg, + code: errObj.code + }); + } else { + throw new Error(errors[errorType]); + } +}; + +var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); +}; + +if (detect()) { + status.slide = "loaded"; +} +var GeetestIsLoad = function (fname) { + var GeetestIsLoad = false; + var tags = { js: 'script', css: 'link' }; + var tagname = fname && tags[fname.split('.').pop()]; + if (tagname !== undefined) { + var elts = document.getElementsByTagName(tagname); + for (var i in elts) { + if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0) + || (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) { + GeetestIsLoad = true; + } + } + } + return GeetestIsLoad; +}; +window.initGeetest4 = function (userConfig,callback) { + + var config = new Config(userConfig); + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + + jsonp(config.apiServers , config.typePath, config, function (newConfig) { + //错误捕获,第一个load请求可能直接报错 + var newConfig = camelizeKeys(newConfig); + + if(newConfig.status === 'error'){ + return throwError('networkError', config, newConfig); + } + + var type = newConfig.type; + if(config.debug){ + new _Object(newConfig)._extend(config.debug) + } + var init = function () { + config._extend(newConfig); + callback(new window.Geetest4(config)); + }; + + callbacks[type] = callbacks[type] || []; + + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + if(newConfig.gctPath){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } 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] = []; + status[type] = 'init'; + } + }); + } else if (s === "loaded") { + // 判断gct是否需要重新加载 + if(newConfig.gctPath && !GeetestIsLoad(newConfig.gctPath)){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + return init(); + } else if (s === "fail") { + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + +}; + + +})(window);