chore(debug): 增加 ajax 调试工具定位 admin-ajax 400 "0" 来源

- assets/js/core/ajax-debug.js:ajaxSend 记录发往 admin-ajax 的
  action 与参数,ajaxError/fetch 记录失败请求的 url/status/响应
- inc/aksrt-ajax-debug.php:admin_init 时记录每个 admin-ajax 请求的
  action、是否有 wp_ajax_/wp_ajax_nopriv_ 处理器、POST 载荷至
  wp-content/aksrt-ajax-debug.log(handled=0 即 400 "0" 的来源)
- 开关:WP_DEBUG 或 URL 带 ?aksrt_ajax_debug=1(或常量 AKSRT_AJAX_DEBUG)
This commit is contained in:
2026-08-22 13:39:13 +08:00
parent 4d6fd641e5
commit 8d4f07656a
4 changed files with 167 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* AKSRT AJAX Debug — 排查 vendor main.min.js / zib_ajax 产生的失败请求
*
* 启用条件(见 inc/aksrt-ajax-debug.php):WP_DEBUG 开启,或 URL 带 ?aksrt_ajax_debug=1
*
* 输出:
* - [aksrt→ajax] 所有发往 admin-ajax.php 的 jQuery 请求(含 action 与参数)
* - [aksrt✗ajax] 所有失败的 jQuery 请求(url / data / status / responseText)
* - [aksrt✗fetch] 所有非 2xx 的 fetch 请求
*/
(function () {
'use strict';
var MAX_BODY = 500;
function clip(str) {
str = String(str == null ? '' : str);
return str.length > MAX_BODY ? str.slice(0, MAX_BODY) + '…(' + str.length + ')' : str;
}
function paramsToObject(data) {
if (!data) return null;
if (typeof data === 'string') {
try { return JSON.parse(data); } catch (e) { /* urlencoded */ }
var out = {};
new URLSearchParams(data).forEach(function (v, k) { out[k] = v; });
return out;
}
return data;
}
console.log('%c[aksrt] ajax 调试已开启(?aksrt_ajax_debug=1 或 WP_DEBUG)', 'color:#2abd4e;background:#16171a;padding:2px 6px;');
if (window.jQuery) {
jQuery(document).ajaxSend(function (e, jqXHR, settings) {
if (!settings || !settings.url || settings.url.indexOf('admin-ajax.php') === -1) return;
console.log('[aksrt→ajax]', settings.type || 'GET', settings.url, paramsToObject(settings.data));
});
jQuery(document).ajaxError(function (e, jqXHR, settings, thrownError) {
console.warn('[aksrt✗ajax]', (settings && settings.type) || '?', (settings && settings.url) || '?',
'data:', paramsToObject(settings && settings.data),
'status:', jqXHR && jqXHR.status,
'response:', clip(jqXHR && jqXHR.responseText),
'thrown:', thrownError);
// 定位触发元素:记录调用栈帮助回溯 zib_ajax 调用来源
if (settings && settings.url && settings.url.indexOf('admin-ajax.php') !== -1) {
console.warn('[aksrt✗ajax] 发出的 action 参数见上方 data —— 在 admin-ajax 无对应处理器时 WP 返回 400 "0"');
}
});
} else {
console.warn('[aksrt] jQuery 未加载,ajax 调试仅覆盖 fetch');
}
var rawFetch = window.fetch;
if (typeof rawFetch === 'function') {
window.fetch = function () {
var args = arguments;
var url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url) || '';
var body = args[1] && args[1].body;
return rawFetch.apply(this, args).then(function (res) {
if (!res.ok) {
res.clone().text().then(function (text) {
console.warn('[aksrt✗fetch]', res.status, url, 'body:', clip(body), 'response:', clip(text));
}).catch(function () {});
}
return res;
});
};
}
})();
+82
View File
@@ -0,0 +1,82 @@
<?php
/**
* AJAX Debug — 记录 admin-ajax 收到的每个请求,用于定位未知 action(WP 返回 400 "0")。
*
* 启用条件:WP_DEBUG 开启,或请求带 ?aksrt_ajax_debug=1
* 日志位置:wp-content/aksrt-ajax-debug.log
*
* 每行格式:
* [时间] action=xxx handled=0/1(nopriv) login=0/1 post={...} referer=...
* handled=0 表示 WP 没有任何 wp_ajax_{action} / wp_ajax_nopriv_{action} 处理器,
* 这种请求 admin-ajax 会直接 die('0') 并返回 400 —— 即控制台看到的报错来源。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_ajax_debug_enabled')) :
/**
* Whether ajax debugging is on for this request.
*
* @return bool
*/
function aksrt_ajax_debug_enabled(): bool {
if (defined('AKSRT_AJAX_DEBUG') && AKSRT_AJAX_DEBUG) {
return true;
}
if (defined('WP_DEBUG') && WP_DEBUG) {
return true;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- 调试开关,无副作用。
return isset($_GET['aksrt_ajax_debug']) && '1' === (string) wp_unslash($_GET['aksrt_ajax_debug']);
}
endif;
if (!function_exists('aksrt_ajax_debug_log')) :
/**
* Append a line to the ajax debug log.
*
* @param string $line Log line.
*/
function aksrt_ajax_debug_log(string $line): void {
$file = WP_CONTENT_DIR . '/aksrt-ajax-debug.log';
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- 调试日志。
@file_put_contents($file, '[' . current_time('Y-m-d H:i:s') . '] ' . $line . "\n", FILE_APPEND | LOCK_EX);
}
endif;
if (!function_exists('aksrt_ajax_debug_boot')) :
/**
* Hook the admin-ajax logger.
*/
function aksrt_ajax_debug_boot(): void {
if (!aksrt_ajax_debug_enabled()) {
return;
}
add_action('admin_init', static function (): void {
if (!defined('DOING_AJAX') || !DOING_AJAX) {
return;
}
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- 只读记录,不修改状态。
$action = isset($_REQUEST['action']) ? sanitize_key((string) wp_unslash($_REQUEST['action'])) : '';
$has_logged = $action !== '' && has_action('wp_ajax_' . $action);
$has_nopriv = $action !== '' && has_action('wp_ajax_nopriv_' . $action);
$payload = $_POST;
unset($payload['_wpnonce'], $payload['nonce'], $payload['_ajax_nonce']);
aksrt_ajax_debug_log(sprintf(
'action=%s handled=%d|%d login=%d post=%s referer=%s',
$action !== '' ? $action : '(empty)',
$has_logged ? 1 : 0,
$has_nopriv ? 1 : 0,
is_user_logged_in() ? 1 : 0,
wp_json_encode($payload, JSON_UNESCAPED_UNICODE),
isset($_SERVER['HTTP_REFERER']) ? (string) wp_unslash($_SERVER['HTTP_REFERER']) : ''
));
// phpcs:enable
}, 1);
}
endif;
aksrt_ajax_debug_boot();
+1
View File
@@ -12,6 +12,7 @@
defined('ABSPATH') || exit;
// Stage 1: helpers
require_once AKSRT_DIR . '/inc/aksrt-ajax-debug.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-icons.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-avatar.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-domain-model.php';
+13
View File
@@ -119,6 +119,19 @@ trait ScriptsPart1c {
true
);
// AJAX 调试(WP_DEBUG 或 ?aksrt_ajax_debug=1):记录所有失败的
// jQuery ajax / fetch 请求,用于定位 vendor main.min.js 的 400 "0" 来源。
if (function_exists('aksrt_ajax_debug_enabled') && aksrt_ajax_debug_enabled()) {
$ajax_debug_path = get_template_directory() . '/assets/js/core/ajax-debug.js';
wp_enqueue_script(
'aksrt-ajax-debug',
$base_uri . '/assets/js/core/ajax-debug.js',
['loader_js'],
file_exists($ajax_debug_path) ? (string) filemtime($ajax_debug_path) : $version,
true
);
}
if ($needs_post_list_media) {
$grade_js = get_template_directory() . '/assets/js/grade.min.js';
wp_enqueue_script(