feat: 添加 AI 查询页面安全防护机制

- IP 访问频率限制:60秒内最多10次查询

- 单线程访问限制:同一 IP 同时只能有一个查询

- 查询结果缓存:成功的查询结果缓存1小时

- 支持 Cloudflare 等代理的真实 IP 获取

- 访问受限时显示友好的错误页面
This commit is contained in:
2026-01-20 23:11:24 +08:00
parent df56c8f611
commit 33d46ef9b1

View File

@@ -7,17 +7,120 @@ $wp_load_path = dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php';
if (!file_exists($wp_load_path)) $wp_load_path = $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php'; if (!file_exists($wp_load_path)) $wp_load_path = $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php';
require_once($wp_load_path); require_once($wp_load_path);
// ==========================================================================
// 安全防护IP 访问限制
// ==========================================================================
/**
* 获取客户端真实 IP
*/
function argon_ai_query_get_client_ip() {
$ip = '';
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return filter_var(trim($ip), FILTER_VALIDATE_IP) ? trim($ip) : '';
}
/**
* 检查 IP 访问频率限制
* @return bool|string true 表示允许访问,字符串表示错误信息
*/
function argon_ai_query_check_rate_limit() {
$client_ip = argon_ai_query_get_client_ip();
if (empty($client_ip)) {
return __('无法获取客户端 IP', 'argon');
}
$transient_key = 'ai_query_lock_' . md5($client_ip);
$rate_limit_key = 'ai_query_rate_' . md5($client_ip);
// 检查是否有正在进行的查询(单线程限制)
if (get_transient($transient_key)) {
return __('请等待上一次查询完成', 'argon');
}
// 检查访问频率60秒内最多10次
$access_count = get_transient($rate_limit_key);
if ($access_count === false) {
set_transient($rate_limit_key, 1, 60);
} else {
if ($access_count >= 10) {
return __('访问过于频繁,请稍后再试', 'argon');
}
set_transient($rate_limit_key, $access_count + 1, 60);
}
// 设置查询锁3秒超时
set_transient($transient_key, 1, 3);
return true;
}
// 执行访问限制检查
$rate_limit_check = argon_ai_query_check_rate_limit();
if ($rate_limit_check !== true) {
// 访问受限,显示错误页面
get_header();
?>
<div class="page-information-card-container"></div>
<?php get_sidebar(); ?>
<div id="primary" class="content-area">
<style id="ai-verify-page-style">
@media screen and (min-width: 901px) {
body.ai-verify-page #leftbar_part, body.ai-verify-page #leftbar { display: none !important; }
}
body.ai-verify-page #primary { width: 100% !important; max-width: 1000px !important; margin: 0 auto !important; float: none !important; }
body.ai-verify-page #content { margin-top: -50vh !important; }
body.ai-verify-page .site-footer { max-width: 1000px !important; margin: 0 auto !important; width: 100% !important; float: none !important; }
</style>
<script data-pjax>
document.body.classList.add('ai-verify-page');
</script>
<main id="main" class="site-main" role="main">
<article class="post card shadow-sm bg-white border-0" style="padding: 60px 40px; text-align: center; margin-bottom: 16px;">
<div style="font-size: 48px; color: #f59e0b; margin-bottom: 20px;"><i class="fa fa-exclamation-triangle"></i></div>
<h2 style="font-size: 20px; font-weight: 600; color: var(--color-text-deeper); margin-bottom: 12px;"><?php _e('访问受限', 'argon'); ?></h2>
<p style="color: var(--color-text-muted); margin-bottom: 24px;"><?php echo esc_html($rate_limit_check); ?></p>
<a href="<?php echo home_url('/ai-query'); ?>" class="btn btn-primary"><?php _e('返回查询页面', 'argon'); ?></a>
</article>
</main>
<?php
get_footer();
exit;
}
// ==========================================================================
// 查询逻辑
// ==========================================================================
$query_code = isset($_GET['code']) ? sanitize_text_field($_GET['code']) : ''; $query_code = isset($_GET['code']) ? sanitize_text_field($_GET['code']) : '';
if (empty($query_code)) { if (empty($query_code)) {
$query_code = get_query_var('code') ? sanitize_text_field(get_query_var('code')) : ''; $query_code = get_query_var('code') ? sanitize_text_field(get_query_var('code')) : '';
} }
$result = null; $result = null;
$error = ''; $error = '';
$from_cache = false;
if (!empty($query_code)) { if (!empty($query_code)) {
if (strlen($query_code) !== 8) { if (strlen($query_code) !== 8) {
$error = __('识别码格式无效', 'argon'); $error = __('识别码格式无效', 'argon');
} else { } else {
// 尝试从缓存获取
$cache_key = 'ai_query_result_' . $query_code;
$cached_result = get_transient($cache_key);
if ($cached_result !== false) {
$result = $cached_result;
$from_cache = true;
} else {
// 从数据库查询
global $wpdb; global $wpdb;
$post_id = $wpdb->get_var($wpdb->prepare( $post_id = $wpdb->get_var($wpdb->prepare(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_argon_ai_summary_code' AND meta_value = %s", "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_argon_ai_summary_code' AND meta_value = %s",
@@ -54,6 +157,9 @@ if (!empty($query_code)) {
]; ];
$result['provider_display'] = isset($provider_names[$result['provider']]) ? $provider_names[$result['provider']] : $result['provider']; $result['provider_display'] = isset($provider_names[$result['provider']]) ? $provider_names[$result['provider']] : $result['provider'];
// 缓存结果1小时
set_transient($cache_key, $result, 3600);
} else { } else {
$error = __('文章不存在或未发布', 'argon'); $error = __('文章不存在或未发布', 'argon');
} }
@@ -62,6 +168,13 @@ if (!empty($query_code)) {
} }
} }
} }
}
// 释放查询锁
$client_ip = argon_ai_query_get_client_ip();
if (!empty($client_ip)) {
delete_transient('ai_query_lock_' . md5($client_ip));
}
get_header(); get_header();
?> ?>