优化了一些问题fix(captcha)

This commit is contained in:
2026-08-29 17:03:05 +08:00
parent 41da33bb13
commit 88eb29c645
10 changed files with 1074 additions and 407 deletions
+133 -391
View File
@@ -5,7 +5,8 @@
* 唯一配置源:option `aksrt_captcha_config`。
* 闭环契约:
* aksrt_get_captcha_config() -> aksrt_is_captcha_enabled_for(scene)
* -> aksrt_render_captcha(scene) -> 客户端 AKSRTCaptchaState + token
* -> aksrt_render_captcha(scene) -> [data-aksrt-captcha-config] 挂载点
* -> captcha-front.js(public bundle)-> 客户端 AKSRTCaptchaState + token
* -> 业务入口 aksrt_require_captcha(scene, $payload) -> proceed/fail。
*
* 仅支持云厂商:tencent / aliyun / cloudflare / geetest。
@@ -81,9 +82,16 @@ function aksrt_captcha_sanitize_scene_list($raw): array {
/**
* 获取人机验证完整配置(规范化,不合并历史脏键)。
*
* 结果在同一请求内缓存;后台保存后调用 aksrt_captcha_config_reset() 失效。
*
* @return array
*/
function aksrt_get_captcha_config(): array {
$cache =& $GLOBALS['aksrt_captcha_config_cache'];
if (is_array($cache)) {
return $cache;
}
$stored = get_option('aksrt_captcha_config', []);
$cfg = array_merge(aksrt_captcha_default_config(), is_array($stored) ? $stored : []);
@@ -103,9 +111,17 @@ function aksrt_get_captcha_config(): array {
$result[$p] = isset($cfg[$p]) && is_array($cfg[$p]) ? $cfg[$p] : [];
}
$cache = $result;
return $result;
}
/**
* 使请求内的配置缓存失效(后台保存路径调用)。
*/
function aksrt_captcha_config_reset(): void {
$GLOBALS['aksrt_captcha_config_cache'] = null;
}
/**
* 各服务商必填字段。
*/
@@ -164,6 +180,25 @@ function aksrt_is_captcha_enabled_for($scene): bool {
return in_array($scene, $cfg['enabled_scenarios'], true);
}
/**
* 场景验证通过后的服务端放行时长(秒),0 表示不启用。
*
* 下载页多个卡片共享一次验证结果(前端会把 token 同步到兄弟挂载点),
* 而厂商 token 是一次性的,因此验证通过后由服务端记录短时放行,
* 后续下载请求直接命中放行,避免票据被判定重复使用。
*/
function aksrt_captcha_pass_ttl(string $scene): int {
return $scene === 'download' ? 10 * MINUTE_IN_SECONDS : 0;
}
/**
* 服务端放行记录的 transient key:场景 + 用户 + IP + UA。
*/
function aksrt_captcha_pass_key(string $scene): string {
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';
return 'aksrt_captcha_pass_' . md5($scene . '|' . get_current_user_id() . '|' . aksrt_get_client_ip() . '|' . md5($ua));
}
/**
* 业务入口统一调用:未启用放行;启用则校验。
*
@@ -175,7 +210,17 @@ function aksrt_require_captcha(string $scene, array $payload = []) {
if (!aksrt_is_captcha_enabled_for($scene)) {
return true;
}
return aksrt_verify_captcha($payload);
$ttl = aksrt_captcha_pass_ttl($scene);
if ($ttl > 0 && get_transient(aksrt_captcha_pass_key($scene))) {
return true;
}
$result = aksrt_verify_captcha($payload, $scene);
if ($result === true && $ttl > 0) {
set_transient(aksrt_captcha_pass_key($scene), time(), $ttl);
}
return $result;
}
/**
@@ -196,6 +241,28 @@ function aksrt_get_captcha_status_summary(): array {
];
}
/**
* 凭据未配齐时的后台警告(前台保持放行,验证码不是准入门槛)。
*/
add_action('admin_notices', static function () {
if (!current_user_can('manage_options')) {
return;
}
$cfg = aksrt_get_captcha_config();
if (empty($cfg['enabled']) || $cfg['active_provider'] === '') {
return;
}
$provider = $cfg['active_provider'];
if (aksrt_captcha_provider_is_configured($provider, $cfg[$provider] ?? [])) {
return;
}
echo '<div class="notice notice-error"><p>' . esc_html(sprintf(
/* translators: %s: captcha provider key */
__('人机验证服务商「%s」已启用但凭据未配齐,前台人机验证未生效。请到「功能&权限 → 人机验证」补全配置。', 'aksrt'),
$provider
)) . '</p></div>';
});
// ============================================================
// 客户端真实 IP(穿透 CDN / 代理)
// ============================================================
@@ -255,7 +322,7 @@ function aksrt_tencent_tc3_sign($secret_id, $secret_key, $service, $timestamp, $
$secret_date = hash_hmac('sha256', $date, 'TC3' . $secret_key, true);
$secret_service = hash_hmac('sha256', $service, $secret_date, true);
$secret_signing = hash_hmac('sha256', 'tc3_request', $secret_service, true);
$secret_signing = hash_hmac('sha256', 'tc3_request', $secret_signing, true);
$signature = hash_hmac('sha256', $string_to_sign, $secret_signing);
$authorization = 'TC3-HMAC-SHA256 '
@@ -291,13 +358,21 @@ function aksrt_aliyun_afs_sign(array $params, string $secret): string {
// 服务端二次校验
// ============================================================
/**
* 记录传输层 / 厂商原始错误细节,供站点排查;对用户只暴露通用文案。
*/
function aksrt_captcha_log(string $provider, string $detail): void {
error_log('[AKSRT Captcha] ' . $provider . ': ' . $detail);
}
/**
* 校验客户端提交的人机验证字段。
*
* @param array $post_data 客户端字段(captcha_verified + 厂商 token)
* @param array $post_data 客户端字段(captcha_verified + 厂商 token)
* @param string $scene 触发场景(用于 Cloudflare cdata 绑定校验)
* @return true|string 成功返回 true,失败返回错误信息
*/
function aksrt_verify_captcha($post_data) {
function aksrt_verify_captcha($post_data, string $scene = '') {
$cfg = aksrt_get_captcha_config();
if (empty($cfg['enabled']) || $cfg['active_provider'] === '') {
return true;
@@ -317,7 +392,7 @@ function aksrt_verify_captcha($post_data) {
$result = aksrt_verify_tencent($pcfg, $post_data, $client_ip);
break;
case 'cloudflare':
$result = aksrt_verify_cloudflare($pcfg, $post_data, $client_ip);
$result = aksrt_verify_cloudflare($pcfg, $post_data, $client_ip, $scene);
break;
case 'geetest':
$result = aksrt_verify_geetest($pcfg, $post_data);
@@ -366,37 +441,29 @@ function aksrt_verify_tencent(array $pcfg, array $post_data, string $client_ip)
$headers = aksrt_tencent_tc3_sign($secret_id, $secret_key, 'captcha', $timestamp, $date, 'DescribeCaptchaResult', '2019-07-22', $payload);
$curl_headers = [];
foreach ($headers as $k => $v) {
$curl_headers[] = "{$k}: {$v}";
}
$ch = curl_init('https://captcha.tencentcloudapi.com/');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $curl_headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => false,
$resp = wp_remote_post('https://captcha.tencentcloudapi.com/', [
'timeout' => 5,
'headers' => $headers,
'body' => $payload,
]);
$response_body = curl_exec($ch);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
return '验证码服务请求失败: ' . $curl_error;
if (is_wp_error($resp)) {
aksrt_captcha_log('tencent', $resp->get_error_message());
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
$body = json_decode((string) $response_body, true);
$body = json_decode(wp_remote_retrieve_body($resp), true);
if (!is_array($body)) {
aksrt_captcha_log('tencent', 'unparsable response');
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
if (isset($body['Response']['Error'])) {
$err = $body['Response']['Error'];
return '腾讯 API 调用失败: ' . ($err['Message'] ?? $err['Code'] ?? '未知错误');
aksrt_captcha_log('tencent', ($err['Code'] ?? 'unknown') . ': ' . ($err['Message'] ?? ''));
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
$resp = $body['Response'] ?? [];
$captcha_code = (int) ($resp['CaptchaCode'] ?? 0);
$resp_data = $body['Response'] ?? [];
$captcha_code = (int) ($resp_data['CaptchaCode'] ?? 0);
if ($captcha_code === 1) {
return true;
}
@@ -411,16 +478,16 @@ function aksrt_verify_tencent(array $pcfg, array $post_data, string $client_ip)
21 => '票据校验异常(风控拦截)',
100 => '参数校验失败(AppId/SecretKey/Ticket 不匹配)',
];
$msg = $resp['CaptchaMsg'] ?? ($messages[$captcha_code] ?? '未知错误');
$msg = $resp_data['CaptchaMsg'] ?? ($messages[$captcha_code] ?? '未知错误');
return '腾讯验证失败: ' . $msg . " (CaptchaCode: {$captcha_code})";
}
/**
* Cloudflare Turnstile siteverify。
* Cloudflare Turnstile siteverify。校验 action 与 cdata(场景)绑定,防 token 转用。
*
* @return true|string
*/
function aksrt_verify_cloudflare(array $pcfg, array $post_data, string $client_ip) {
function aksrt_verify_cloudflare(array $pcfg, array $post_data, string $client_ip, string $scene = '') {
$token = (string) ($post_data['cf_turnstile_response'] ?? '');
if ($token === '' || strlen($token) < 10) {
return __('缺少验证参数', 'aksrt');
@@ -435,7 +502,7 @@ function aksrt_verify_cloudflare(array $pcfg, array $post_data, string $client_i
$body = [];
foreach ($bodies as $attempt) {
$resp = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'timeout' => 10,
'timeout' => 5,
'body' => $attempt,
]);
if (is_wp_error($resp)) {
@@ -443,12 +510,22 @@ function aksrt_verify_cloudflare(array $pcfg, array $post_data, string $client_i
}
$body = json_decode(wp_remote_retrieve_body($resp), true);
if (!empty($body['success'])) {
if (isset($body['action']) && (string) $body['action'] !== 'aksrt-verify') {
aksrt_captcha_log('cloudflare', 'action mismatch: ' . (string) $body['action']);
return __('人机验证未通过,请重试', 'aksrt');
}
if ($scene !== '' && isset($body['cdata']) && (string) $body['cdata'] !== $scene) {
aksrt_captcha_log('cloudflare', 'cdata mismatch: ' . (string) $body['cdata'] . ' != ' . $scene);
return __('人机验证未通过,请重试', 'aksrt');
}
return true;
}
}
$codes = isset($body['error-codes']) && is_array($body['error-codes']) ? implode(', ', $body['error-codes']) : '未知错误';
return 'Cloudflare 验证失败: ' . $codes;
if (is_array($body) && isset($body['error-codes'])) {
aksrt_captcha_log('cloudflare', 'siteverify failed: ' . implode(', ', (array) $body['error-codes']));
}
return __('人机验证未通过,请重试', 'aksrt');
}
/**
@@ -469,7 +546,7 @@ function aksrt_verify_geetest(array $pcfg, array $post_data) {
$sign_token = hash_hmac('sha256', $lot_number, $captcha_key);
$resp = wp_remote_post('https://gcaptcha4.geetest.com/validate', [
'timeout' => 10,
'timeout' => 5,
'body' => [
'captcha_id' => (string) ($pcfg['captcha_id'] ?? ''),
'lot_number' => $lot_number,
@@ -481,15 +558,16 @@ function aksrt_verify_geetest(array $pcfg, array $post_data) {
]);
if (is_wp_error($resp)) {
return '验证请求失败: ' . $resp->get_error_message();
aksrt_captcha_log('geetest', $resp->get_error_message());
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
$body = json_decode(wp_remote_retrieve_body($resp), true);
if (!empty($body['result']) && $body['result'] === 'success') {
if (is_array($body) && !empty($body['result']) && $body['result'] === 'success') {
return true;
}
$reason = $body['reason'] ?? ($body['msg'] ?? '未知错误');
return '极验验证失败: ' . $reason;
aksrt_captcha_log('geetest', 'validate failed: ' . (string) ($body['reason'] ?? ($body['msg'] ?? 'unknown')));
return __('人机验证未通过,请重试', 'aksrt');
}
/**
@@ -514,7 +592,7 @@ function aksrt_verify_aliyun(array $pcfg, array $post_data) {
'Format' => 'JSON',
'SignatureMethod' => 'HMAC-SHA1',
'SignatureVersion' => '1.0',
'SignatureNonce' => uniqid('', true),
'SignatureNonce' => wp_generate_uuid4(),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Version' => '2023-03-05',
'CaptchaVerifyParam' => $captcha_verify_param,
@@ -525,15 +603,20 @@ function aksrt_verify_aliyun(array $pcfg, array $post_data) {
$params['Signature'] = aksrt_aliyun_afs_sign($params, (string) ($pcfg['access_key_secret'] ?? ''));
$resp = wp_remote_post('https://' . $endpoint . '/', [
'timeout' => 10,
'timeout' => 5,
'body' => $params,
]);
if (is_wp_error($resp)) {
return '验证请求失败: ' . $resp->get_error_message();
aksrt_captcha_log('aliyun', $resp->get_error_message());
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
$body = json_decode(wp_remote_retrieve_body($resp), true);
if (!is_array($body)) {
aksrt_captcha_log('aliyun', 'unparsable response');
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
if (!empty($body['Success'])) {
$verify_result = $body['Result']['VerifyResult'] ?? false;
if ($verify_result === true) {
@@ -561,15 +644,18 @@ function aksrt_verify_aliyun(array $pcfg, array $post_data) {
return '阿里云 V3 验证失败: ' . ($code_messages[$verify_code] ?? '未知错误 (' . ($verify_code ?: 'N/A') . ')');
}
return '阿里云 V3 验证失败: ' . ($body['Message'] ?? '未知错误');
aksrt_captcha_log('aliyun', 'api failed: ' . (string) ($body['Message'] ?? 'unknown'));
return __('人机验证服务暂不可用,请稍后重试', 'aksrt');
}
// ============================================================
// 前端渲染(挂载点 + 内联 SDK + AKSRTCaptchaState)
// 前端渲染(挂载点 + data 配置,运行时在 public bundle 的 captcha-front.js)
// ============================================================
/**
* 渲染人机验证挂载点。未启用场景直接返回。
*
* 挂载点自带 JSON 配置,由 captcha-front.js(public bundle)扫描激活。
*/
function aksrt_render_captcha($scene = 'comment') {
$scene = sanitize_key((string) $scene);
@@ -618,354 +704,10 @@ function aksrt_render_captcha($scene = 'comment') {
'scene_id' => (string) ($pcfg['scene_id'] ?? ''),
],
];
$token_fields = ['cf_turnstile_response', 'tencent_ticket', 'tencent_randstr', 'tencent_sid',
'geetest_lot_number', 'geetest_captcha_output', 'geetest_pass_token', 'geetest_gen_time',
'aliyun_captcha_verify_param'];
$token_fields_json = wp_json_encode($token_fields, JSON_UNESCAPED_UNICODE);
?>
<div class="aksrt-captcha-wrap" data-provider="<?php echo esc_attr($provider); ?>" data-scene="<?php echo esc_attr($scene); ?>">
<div class="aksrt-captcha-wrap" data-provider="<?php echo esc_attr($provider); ?>" data-scene="<?php echo esc_attr($scene); ?>" data-aksrt-captcha-config="<?php echo esc_attr(wp_json_encode($js_config, JSON_UNESCAPED_UNICODE)); ?>">
<div id="<?php echo esc_attr($uid); ?>" class="aksrt-captcha-box" style="display:none"></div>
<input type="hidden" name="captcha_verified" id="<?php echo esc_attr($vid); ?>" value="0">
</div>
<link rel="stylesheet" href="<?php echo esc_url(AKSRT_URI . '/assets/css/shared/captcha.css'); ?>">
<script>(function(){
var C=<?php echo wp_json_encode($js_config, JSON_UNESCAPED_UNICODE); ?>;
var TOKEN_FIELDS=<?php echo $token_fields_json; ?>;
var root=window.AKSRTCaptchaState=window.AKSRTCaptchaState||{loaders:{}, scenes:{}};
function loadScriptOnce(key, src, readyCheck){
if(typeof readyCheck==='function' && readyCheck()){ return Promise.resolve(); }
if(root.loaders[key]){ return root.loaders[key]; }
root.loaders[key]=new Promise(function(resolve, reject){
var existing=document.querySelector('script[data-aksrt-sdk="'+key+'"]');
if(existing){
if(typeof readyCheck==='function' && readyCheck()){ resolve(); return; }
existing.addEventListener('load', function(){ resolve(); }, {once:true});
existing.addEventListener('error', function(){ reject(new Error(key+' load failed')); }, {once:true});
return;
}
var script=document.createElement('script');
script.src=src; script.async=true; script.setAttribute('data-aksrt-sdk', key);
script.onload=function(){ resolve(); };
script.onerror=function(){ reject(new Error(key+' load failed')); };
document.head.appendChild(script);
});
return root.loaders[key];
}
var box, hField, form=null;
var verified=false;
var pendingAction=null;
var triggerCaptcha=function(){ showTip('验证码加载中,请稍候', '#e67e22'); };
function findForm(){
form=box.closest('form');
if(!form&&C.scene==='comment') form=document.getElementById('commentform');
if(!form&&(C.scene==='login'||C.scene==='register')){
form=box.closest('[data-auth-form]')||document.querySelector('[data-auth-form]');
}
if(!form&&C.scene==='bind_phone'){
form=document.getElementById('aksrt-bind-phone-form')
||document.querySelector('[data-aksrt-bind-phone-form]')
||document.querySelector('.auth-form');
}
return form;
}
function bindSubmit(){
if(!form) return;
form.addEventListener('submit', function(e){
if(verified || hField.value==='1') return;
e.preventDefault(); e.stopImmediatePropagation();
pendingAction=null; triggerCaptcha();
}, true);
}
function setField(name, value){
var container = form || box;
var el=container.querySelector('input[name="'+name+'"]');
if(!el){ el=document.createElement('input'); el.type='hidden'; el.name=name; container.appendChild(el); }
el.value=value;
}
function resetVerificationState(){
verified=false;
hField.value='0';
var container = form || box;
TOKEN_FIELDS.forEach(function(n){
var f=container.querySelector('input[name="'+n+'"]');
if(f) f.remove();
});
if(C.scene==='download'){
syncDownloadSiblings(false);
}
}
function syncDownloadSiblings(passed){
var myContainer=box.closest('[data-aksrt-download-captcha]');
if(!myContainer) return;
document.querySelectorAll('[data-aksrt-download-captcha]').forEach(function(target){
if(target===myContainer) return;
var tgtH=target.querySelector('input[name="captcha_verified"]');
if(tgtH) tgtH.value=passed ? '1' : '0';
TOKEN_FIELDS.forEach(function(name){
var f=target.querySelector('input[name="'+name+'"]');
if(f) f.remove();
if(passed){
var srcEl=myContainer.querySelector('input[name="'+name+'"]');
if(srcEl&&srcEl.value!==''){
var tgtEl=document.createElement('input'); tgtEl.type='hidden'; tgtEl.name=name; target.appendChild(tgtEl);
tgtEl.value=srcEl.value;
}
}
});
});
}
function submitNativeForm(){
if(!form) return;
if(form.requestSubmit){ form.requestSubmit(); }
else { HTMLFormElement.prototype.submit.call(form); }
}
function submitForm(){
verified=true; hField.value='1';
if(C.scene==='download') syncDownloadSiblings(true);
if(typeof pendingAction==='function'){
finishPending(true); return;
}
setTimeout(resetVerificationState, 3000);
submitNativeForm();
}
function showTip(msg, color){
var t=box.querySelector('.captcha-tip');
if(!t){ t=document.createElement('div'); t.className='captcha-tip'; t.style.cssText='font-size:12px;margin-top:4px;text-align:left;'; box.appendChild(t); }
t.style.color=color||'#e74c3c'; t.textContent=msg;
setTimeout(function(){ if(t.parentNode) t.remove(); }, 4000);
}
function showLoading(){ box.innerHTML='<div style="text-align:center;padding:20px;color:#9ca3af;">验证码加载中...</div>'; }
function clearLoading(){ box.innerHTML=''; }
function finishPending(ok){
var action=pendingAction; pendingAction=null;
if(typeof action==='function'){ action(!!ok); }
}
function cancelPending(msg){
if(msg) showTip(msg, '#e67e22');
finishPending(false);
}
root.scenes[C.scene]={
verify:function(callback){
if(verified || hField.value==='1'){
if(typeof callback==='function') callback(true);
return;
}
pendingAction=(typeof callback==='function')?function(ok){ callback(ok!==false); }:null;
triggerCaptcha();
},
reset:resetVerificationState
};
// ============ 腾讯云 Captcha 2.0 ============
function loadTencent(){
showLoading();
loadScriptOnce('tencent', 'https://turing.captcha.qcloud.com/TJCaptcha.js', function(){
return typeof TencentCaptcha!=='undefined';
}).then(function(){
clearLoading();
var capIns=null;
triggerCaptcha=function(){
if(!capIns){
capIns=new TencentCaptcha(C.tc.app_id, function(res){
if(res.ret===2){ cancelPending('已取消验证'); return; }
if(res.ret===0 && res.ticket){
if(res.ticket.indexOf('trerror_')===0){
showTip('验证码服务异常,请刷新页面重试', '#e67e22'); return;
}
setField('tencent_ticket', res.ticket);
setField('tencent_randstr', res.randstr);
setField('tencent_sid', res.sid||'');
submitForm();
return;
}
if(res.errorCode){
var errorMap={1001:'验证码组件加载失败',1002:'验证码超时',1006:'验证码配置拉取失败',1008:'验证码页面加载异常',1012:'验证码刷新次数过多',1013:'验证码网络异常'};
showTip(errorMap[res.errorCode]||('验证码异常(errorCode:'+res.errorCode+')'), '#e74c3c');
}
}, {userLanguage:'zh-cn'});
}
capIns.show();
};
}).catch(function(){ clearLoading(); showTip('验证码组件加载失败,请刷新重试', '#e74c3c'); });
}
// ============ Cloudflare Turnstile(弹窗) ============
function loadCloudflare(){
var cfWidgetId=null, cfVerifying=false, cfModal=null;
if(typeof turnstile==='undefined'){
window['onTurnstileLoad_'+C.uid]=function(){};
loadScriptOnce('cloudflare', 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad_'+C.uid+'&render=explicit', function(){
return typeof turnstile!=='undefined';
});
}
function renderWidget(){
if(cfWidgetId){ try { turnstile.remove(cfWidgetId); } catch(e) {} cfWidgetId=null; }
if(typeof turnstile==='undefined'){ setTimeout(renderWidget, 200); return; }
cfWidgetId=turnstile.render('#modalWidget_'+C.uid, {
sitekey:C.cf.site_key, theme:C.cf.theme, size:C.cf.size, action:'aksrt-verify',
callback:function(token){
if(!cfVerifying) return;
cfVerifying=false;
setField('cf_turnstile_response', token);
closeModal(); submitForm();
},
'error-callback':function(){ showTip('验证码失败,请重试', '#e74c3c'); cfWidgetId=null; setTimeout(renderWidget, 500); },
'expired-callback':function(){ showTip('验证码已过期,请重新验证', '#e67e22'); cfWidgetId=null; setTimeout(renderWidget, 500); }
});
}
function createModal(){
cfModal=document.createElement('div');
cfModal.className='aksrt-captcha-modal-overlay';
cfModal.innerHTML='<div class="aksrt-captcha-modal-backdrop"></div>'+
'<div class="aksrt-captcha-modal-box"><div class="aksrt-captcha-modal-header">'+
'<span class="aksrt-captcha-modal-title">人机验证</span>'+
'<button type="button" class="aksrt-captcha-modal-close">&times;</button></div>'+
'<div class="aksrt-captcha-modal-body"><p class="aksrt-captcha-modal-hint">请完成验证后继续操作</p>'+
'<div id="modalWidget_'+C.uid+'" class="aksrt-captcha-modal-widget"></div></div></div>';
document.body.appendChild(cfModal);
var cancelFn=function(){ cfVerifying=false; closeModal(); cancelPending('已取消验证'); };
cfModal.querySelector('.aksrt-captcha-modal-backdrop').addEventListener('click', cancelFn);
cfModal.querySelector('.aksrt-captcha-modal-close').addEventListener('click', cancelFn);
renderWidget();
}
function closeModal(){
if(cfWidgetId){ try { turnstile.remove(cfWidgetId); } catch(e) {} cfWidgetId=null; }
if(cfModal && cfModal.parentNode){ cfModal.parentNode.removeChild(cfModal); }
cfModal=null;
}
triggerCaptcha=function(){
if(cfVerifying){ showTip('验证码进行中,请稍候', '#e67e22'); return; }
cfVerifying=true; createModal();
};
}
// ============ 极验 GeeTest v4 ============
function loadGeetest(){
showLoading();
var captchaId=(C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : '';
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
if(!captchaId){
clearLoading();
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 人机验证」填写并保存', '#e74c3c');
return;
}
function render(){
clearLoading();
initGeetest4({captchaId:captchaId, product:product, hideSuccess:true}, function(capIns){
window['__gt_'+C.uid]=capIns;
function onSuccess(){
var result=capIns.getValidate();
if(!result) return;
setField('geetest_lot_number', result.lot_number);
setField('geetest_captcha_output', result.captcha_output);
setField('geetest_pass_token', result.pass_token);
setField('geetest_gen_time', result.gen_time);
submitForm();
}
if(product==='bind' || product==='popup'){
capIns.onReady(function(){}).onSuccess(onSuccess).onClose(function(){});
}else{
capIns.appendTo('#'+C.uid).onSuccess(onSuccess).onClose(function(){});
}
});
triggerCaptcha=function(){
var instance=window['__gt_'+C.uid];
if(!instance){ showTip('验证码加载中,请稍候再试', '#e67e22'); return; }
if(product==='bind' || product==='popup'){ instance.showCaptcha(); return; }
showTip('请先完成人机验证', '#e67e22');
};
}
if(typeof initGeetest4==='undefined'){
loadScriptOnce('geetest', 'https://static.geetest.com/v4/gt4.js', function(){
return typeof initGeetest4!=='undefined';
}).then(render).catch(function(){ clearLoading(); showTip('极验组件加载失败', '#e74c3c'); });
return;
}
render();
}
// ============ 阿里云 CAPTCHA 2.0 V3 ============
function loadAliyun(){
showLoading();
window.AliyunCaptchaConfig={ region:C.al.region||'cn', prefix:C.al.prefix };
function render(){
clearLoading();
var captchaIns=null;
var btn=box.closest('form') ? box.closest('form').querySelector('button[type="submit"], input[type="submit"]') : null;
window.initAliyunCaptcha({
SceneId:C.al.scene_id, mode:'popup', element:'#'+C.uid,
button:btn ? '#' + (btn.id || (function(){ btn.id='aksrt-captcha-btn-'+C.scene; return btn.id; })()) : null,
success:function(captchaVerifyParam){
setField('aliyun_captcha_verify_param', captchaVerifyParam);
submitForm();
},
fail:function(result){ showTip('验证码失败: ' + (result || '请重试'), '#e74c3c'); captchaIns=null; },
getInstance:function(instance){ captchaIns=instance; },
slideStyle:{width:360, height:40}, language:'cn'
});
triggerCaptcha=function(){
if(captchaIns && captchaIns.show){ captchaIns.show(); return; }
showTip('验证码组件加载中,请稍候再试', '#e67e22');
};
}
if(typeof window.initAliyunCaptcha==='undefined'){
loadScriptOnce('aliyun', 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js', function(){
return typeof window.initAliyunCaptcha!=='undefined';
}).then(render).catch(function(){ clearLoading(); showTip('验证码组件加载失败,请刷新重试', '#e74c3c'); });
return;
}
render();
}
function loadProvider(){
switch(C.provider){
case 'tencent': loadTencent(); break;
case 'cloudflare': loadCloudflare(); break;
case 'geetest': loadGeetest(); break;
case 'aliyun': loadAliyun(); break;
}
}
function init(){
box=document.getElementById(C.uid);
hField=document.getElementById(C.vid);
if(!box||!hField) return;
var tries=0;
(function retryFind(){
findForm();
if(!form && ++tries < 10){ setTimeout(retryFind, 150); }
else {
if(form) bindSubmit();
setTimeout(loadProvider, 0);
}
})();
}
if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', init); }
else { setTimeout(init, 50); }
})();</script>
<?php
}