refactor: 优化 AI 垃圾评论识别为批量检测模式

- 改为一次性将所有评论打包发送给 AI 检测
- 大幅降低 API 调用次数和成本
- 移除逐条检测和进度轮询机制
- 优化前端交互,直接等待批量检测结果
- 增加超时时间以适应批量处理
- 优化结果展示界面,增加视觉反馈
This commit is contained in:
2026-01-22 12:49:24 +08:00
parent 55d10e8c20
commit 2b1bcbf8f9
2 changed files with 243 additions and 112 deletions

View File

@@ -7504,7 +7504,7 @@ function argon_async_spam_detection_handler($comment_id) {
add_action('argon_async_spam_detection', 'argon_async_spam_detection_handler'); add_action('argon_async_spam_detection', 'argon_async_spam_detection_handler');
/** /**
* AJAX: 开始批量扫描 * AJAX: 开始批量扫描(一次性发送所有评论)
*/ */
function argon_spam_detection_scan() { function argon_spam_detection_scan() {
check_ajax_referer('argon_spam_detection_scan', 'nonce'); check_ajax_referer('argon_spam_detection_scan', 'nonce');
@@ -7524,73 +7524,237 @@ function argon_spam_detection_scan() {
]; ];
$comments = get_comments($args); $comments = get_comments($args);
$comment_ids = array_map(function($comment) {
return $comment->comment_ID;
}, $comments);
// 保存扫描任务 if (empty($comments)) {
set_transient('argon_spam_scan_task', [ wp_send_json_success([
'status' => 'running', 'status' => 'completed',
'total' => count($comment_ids), 'total' => 0,
'processed' => 0, 'results' => []
'comment_ids' => $comment_ids, ]);
'results' => [], return;
'current_index' => 0 }
], 3600);
// 启动后台处理 // 构建评论数据
wp_schedule_single_event(time(), 'argon_spam_scan_process'); $comments_data = [];
foreach ($comments as $comment) {
$comments_data[] = [
'id' => $comment->comment_ID,
'author' => $comment->comment_author,
'email' => $comment->comment_author_email,
'url' => $comment->comment_author_url,
'content' => strip_tags($comment->comment_content)
];
}
wp_send_json_success(['message' => __('扫描已开始', 'argon')]); // 调用 AI 进行批量检测
$result = argon_batch_detect_spam_comments($comments_data);
if ($result === false) {
wp_send_json_error(__('AI 检测失败,请检查 API 配置', 'argon'));
return;
}
// 处理结果
$spam_results = [];
foreach ($result as $item) {
if (isset($item['is_spam']) && $item['is_spam']) {
$comment = get_comment($item['id']);
if ($comment) {
$spam_results[] = [
'comment_id' => $item['id'],
'author' => $comment->comment_author,
'content' => mb_substr(strip_tags($comment->comment_content), 0, 100),
'reason' => isset($item['reason']) ? $item['reason'] : __('未知原因', 'argon')
];
// 保存检测结果
update_comment_meta($item['id'], '_argon_spam_detection_result', [
'is_spam' => true,
'reason' => $item['reason']
]);
update_comment_meta($item['id'], '_argon_spam_detection_time', time());
}
}
}
wp_send_json_success([
'status' => 'completed',
'total' => count($comments),
'results' => $spam_results
]);
} }
add_action('wp_ajax_argon_spam_detection_scan', 'argon_spam_detection_scan'); add_action('wp_ajax_argon_spam_detection_scan', 'argon_spam_detection_scan');
/** /**
* 后台处理扫描任务 * 批量检测垃圾评论(一次性发送所有评论)
* @param array $comments_data 评论数据数组
* @return array|false 检测结果或 false
*/ */
function argon_spam_scan_process_handler() { function argon_batch_detect_spam_comments($comments_data) {
$task = get_transient('argon_spam_scan_task'); // 获取 AI 配置
if (!$task || $task['status'] !== 'running') { $provider = get_option('argon_ai_summary_provider', 'openai');
return; $api_key = get_option('argon_ai_summary_api_key', '');
$model = get_option('argon_ai_summary_model', '');
$prompt = get_option('argon_comment_spam_detection_prompt', '');
if (empty($api_key)) {
return false;
} }
$batch_size = 5; // 每批处理 5 条 if (empty($prompt)) {
$end_index = min($task['current_index'] + $batch_size, $task['total']); $prompt = '你是一个专业的内容审核助手。请判断以下评论是否为垃圾评论。垃圾评论包括但不限于:广告推广、反动言论、错误政治观点、时政敏感内容、违法信息、色情暴力、恶意攻击等。
for ($i = $task['current_index']; $i < $end_index; $i++) { 请仅返回 JSON 格式:{"is_spam": true/false, "reason": "理由(25字以内)"}
$comment_id = $task['comment_ids'][$i];
$result = argon_detect_spam_comment($comment_id);
if ($result && $result['is_spam']) { 如果是正常评论reason 填写 "正常"。如果是垃圾评论,简要说明原因。';
$comment = get_comment($comment_id); }
$task['results'][] = [
'comment_id' => $comment_id, // 构建批量检测内容
'author' => $comment->comment_author, $batch_content = "请逐一检查以下评论,对每条评论返回检测结果。\n\n";
'content' => mb_substr(strip_tags($comment->comment_content), 0, 100), foreach ($comments_data as $comment) {
'reason' => $result['reason'] $batch_content .= sprintf(
"[评论ID: %d]\n作者: %s\n邮箱: %s\n网站: %s\n内容: %s\n\n",
$comment['id'],
$comment['author'],
$comment['email'],
$comment['url'],
$comment['content']
);
}
$batch_content .= "\n请返回 JSON 数组格式:[{\"id\": 评论ID, \"is_spam\": true/false, \"reason\": \"理由(25字以内)\"}]";
// 调用 AI API
$ai_response = argon_call_ai_api_for_batch_spam_detection($provider, $api_key, $model, $prompt, $batch_content);
if (!$ai_response) {
return false;
}
// 解析 JSON 响应
$result = json_decode($ai_response, true);
if (!$result || !is_array($result)) {
// 尝试从文本中提取 JSON 数组
if (preg_match('/\[\s*\{[^\]]*\}\s*\]/s', $ai_response, $matches)) {
$result = json_decode($matches[0], true);
}
}
if (!$result || !is_array($result)) {
return false;
}
return $result;
}
/**
* 调用 AI API 进行批量垃圾评论检测
*/
function argon_call_ai_api_for_batch_spam_detection($provider, $api_key, $model, $prompt, $content) {
$endpoint = get_option('argon_ai_summary_api_endpoint', '');
// 根据不同服务商设置默认端点和模型
$default_models = [
'openai' => 'gpt-4o-mini',
'anthropic' => 'claude-3-5-haiku-20241022',
'deepseek' => 'deepseek-chat',
'qianwen' => 'qwen-turbo',
'wenxin' => 'ernie-4.0-turbo-8k',
'doubao' => 'doubao-pro-32k',
'kimi' => 'moonshot-v1-8k',
'zhipu' => 'glm-4-flash',
'siliconflow' => 'Qwen/Qwen2.5-7B-Instruct'
];
if (empty($model) && isset($default_models[$provider])) {
$model = $default_models[$provider];
}
// 构建请求
$messages = [
['role' => 'system', 'content' => $prompt],
['role' => 'user', 'content' => $content]
];
$body = [
'model' => $model,
'messages' => $messages,
'temperature' => 0.3,
'max_tokens' => 4000 // 批量检测需要更多 token
];
// 根据服务商设置端点
if (empty($endpoint)) {
$endpoints = [
'openai' => 'https://api.openai.com/v1/chat/completions',
'anthropic' => 'https://api.anthropic.com/v1/messages',
'deepseek' => 'https://api.deepseek.com/v1/chat/completions',
'qianwen' => 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
'wenxin' => 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions',
'doubao' => 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
'kimi' => 'https://api.moonshot.cn/v1/chat/completions',
'zhipu' => 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
'siliconflow' => 'https://api.siliconflow.cn/v1/chat/completions'
];
$endpoint = isset($endpoints[$provider]) ? $endpoints[$provider] : $endpoints['openai'];
}
// Anthropic 特殊处理
if ($provider === 'anthropic') {
$body = [
'model' => $model,
'messages' => [['role' => 'user', 'content' => $prompt . "\n\n" . $content]],
'max_tokens' => 4000
];
$headers = [
'x-api-key' => $api_key,
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json'
];
} else {
$headers = [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json'
]; ];
} }
$task['processed']++; $response = wp_remote_post($endpoint, [
'headers' => $headers,
'body' => json_encode($body),
'timeout' => 60 // 批量检测需要更长超时时间
]);
if (is_wp_error($response)) {
return false;
} }
$task['current_index'] = $end_index; $response_body = json_decode(wp_remote_retrieve_body($response), true);
if ($task['current_index'] >= $task['total']) { // 解析响应
$task['status'] = 'completed'; $ai_response = '';
if ($provider === 'anthropic') {
if (isset($response_body['content'][0]['text'])) {
$ai_response = $response_body['content'][0]['text'];
}
} else {
if (isset($response_body['choices'][0]['message']['content'])) {
$ai_response = $response_body['choices'][0]['message']['content'];
}
} }
set_transient('argon_spam_scan_task', $task, 3600); return $ai_response;
// 继续处理
if ($task['status'] === 'running') {
wp_schedule_single_event(time() + 2, 'argon_spam_scan_process');
} }
/**
* 后台处理扫描任务(已废弃,保留以兼容旧代码)
*/
function argon_spam_scan_process_handler() {
// 此函数已废弃,批量检测改为一次性完成
} }
add_action('argon_spam_scan_process', 'argon_spam_scan_process_handler'); add_action('argon_spam_scan_process', 'argon_spam_scan_process_handler');
/** /**
* AJAX: 获取扫描进度 * AJAX: 获取扫描进度(已废弃,保留以兼容)
*/ */
function argon_spam_detection_get_progress() { function argon_spam_detection_get_progress() {
check_ajax_referer('argon_spam_detection_get_progress', 'nonce'); check_ajax_referer('argon_spam_detection_get_progress', 'nonce');
@@ -7599,16 +7763,12 @@ function argon_spam_detection_get_progress() {
wp_send_json_error(__('权限不足', 'argon')); wp_send_json_error(__('权限不足', 'argon'));
} }
$task = get_transient('argon_spam_scan_task'); // 批量检测已改为同步完成,此接口保留以兼容前端
if (!$task) {
wp_send_json_error(__('未找到扫描任务', 'argon'));
}
wp_send_json_success([ wp_send_json_success([
'status' => $task['status'], 'status' => 'completed',
'total' => $task['total'], 'total' => 0,
'processed' => $task['processed'], 'processed' => 0,
'results' => isset($task['results']) ? $task['results'] : [] 'results' => []
]); ]);
} }
add_action('wp_ajax_argon_spam_detection_get_progress', 'argon_spam_detection_get_progress'); add_action('wp_ajax_argon_spam_detection_get_progress', 'argon_spam_detection_get_progress');

View File

@@ -3945,7 +3945,6 @@ window.pjaxLoaded = function(){
<script> <script>
jQuery(document).ready(function($) { jQuery(document).ready(function($) {
let isScanning = false; let isScanning = false;
let scanAborted = false;
function startScan(scanType) { function startScan(scanType) {
if (isScanning) { if (isScanning) {
@@ -3953,87 +3952,59 @@ window.pjaxLoaded = function(){
return; return;
} }
if (!confirm('<?php _e('确定要开始扫描吗?这可能需要一些时间。', 'argon');?>')) { if (!confirm('<?php _e('确定要开始扫描吗?这将一次性检测所有评论,可能需要较长时间。', 'argon');?>')) {
return; return;
} }
isScanning = true; isScanning = true;
scanAborted = false;
$('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', true); $('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', true);
$('#argon-spam-detection-status').text('<?php _e('正在扫描...', 'argon');?>'); $('#argon-spam-detection-status').text('<?php _e('正在扫描...请耐心等待', 'argon');?>');
$('#argon-spam-detection-progress').show(); $('#argon-spam-detection-progress').show();
$('#argon-spam-detection-results').hide(); $('#argon-spam-detection-results').hide();
$('#argon-spam-detection-progress-bar').css('width', '0%'); $('#argon-spam-detection-progress-bar').css('width', '50%');
$('#argon-spam-detection-progress-text').text('0%'); $('#argon-spam-detection-progress-text').text('<?php _e('处理中...', 'argon');?>');
$.post(ajaxurl, { $.post(ajaxurl, {
action: 'argon_spam_detection_scan', action: 'argon_spam_detection_scan',
nonce: '<?php echo wp_create_nonce('argon_spam_detection_scan'); ?>', nonce: '<?php echo wp_create_nonce('argon_spam_detection_scan'); ?>',
scan_type: scanType scan_type: scanType
}, function(response) { }, function(response) {
if (response.success) {
pollScanProgress();
} else {
isScanning = false; isScanning = false;
$('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false); $('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false);
$('#argon-spam-detection-progress-bar').css('width', '100%');
$('#argon-spam-detection-progress-text').text('100%');
if (response.success) {
const data = response.data;
$('#argon-spam-detection-status').text('<?php _e('扫描完成', 'argon');?> - <?php _e('共检测', 'argon');?> ' + data.total + ' <?php _e('条评论', 'argon');?>');
displayResults(data.results);
} else {
$('#argon-spam-detection-status').text('<?php _e('扫描失败', 'argon');?>: ' + (response.data || '')); $('#argon-spam-detection-status').text('<?php _e('扫描失败', 'argon');?>: ' + (response.data || ''));
$('#argon-spam-detection-progress').hide(); $('#argon-spam-detection-progress').hide();
} }
}).fail(function() { }).fail(function(xhr, status, error) {
isScanning = false; isScanning = false;
$('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false); $('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false);
$('#argon-spam-detection-status').text('<?php _e('请求失败', 'argon');?>'); $('#argon-spam-detection-status').text('<?php _e('请求失败', 'argon');?>: ' + error);
$('#argon-spam-detection-progress').hide(); $('#argon-spam-detection-progress').hide();
}); });
} }
function pollScanProgress() {
if (scanAborted) {
return;
}
$.post(ajaxurl, {
action: 'argon_spam_detection_get_progress',
nonce: '<?php echo wp_create_nonce('argon_spam_detection_get_progress'); ?>'
}, function(response) {
if (response.success) {
const data = response.data;
const percent = Math.round((data.processed / data.total) * 100);
$('#argon-spam-detection-progress-bar').css('width', percent + '%');
$('#argon-spam-detection-progress-text').text(percent + '% (' + data.processed + '/' + data.total + ')');
$('#argon-spam-detection-status').text('<?php _e('已处理', 'argon');?> ' + data.processed + ' / ' + data.total);
if (data.status === 'completed') {
isScanning = false;
$('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false);
$('#argon-spam-detection-status').text('<?php _e('扫描完成', 'argon');?>');
displayResults(data.results);
} else if (data.status === 'error') {
isScanning = false;
$('#argon-spam-detection-scan-all, #argon-spam-detection-scan-pending').prop('disabled', false);
$('#argon-spam-detection-status').text('<?php _e('扫描出错', 'argon');?>: ' + data.error);
$('#argon-spam-detection-progress').hide();
} else {
setTimeout(pollScanProgress, 1000);
}
}
});
}
function displayResults(results) { function displayResults(results) {
if (!results || results.length === 0) { if (!results || results.length === 0) {
$('#argon-spam-detection-results-content').html('<p><?php _e('未发现垃圾评论', 'argon');?></p>'); $('#argon-spam-detection-results-content').html('<p style="color: #46b450; font-weight: 600;"><span class="dashicons dashicons-yes-alt" style="font-size: 20px; vertical-align: middle;"></span> <?php _e('未发现垃圾评论,所有评论都是正常的!', 'argon');?></p>');
$('#argon-spam-detection-results').show(); $('#argon-spam-detection-results').show();
return; return;
} }
let html = '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;"><thead><tr><th><?php _e('评论 ID', 'argon');?></th><th><?php _e('作者', 'argon');?></th><th><?php _e('内容', 'argon');?></th><th><?php _e('识别理由', 'argon');?></th><th><?php _e('操作', 'argon');?></th></tr></thead><tbody>'; let html = '<p style="color: #dc3232; font-weight: 600; margin-bottom: 15px;"><span class="dashicons dashicons-warning" style="font-size: 20px; vertical-align: middle;"></span> <?php _e('发现', 'argon');?> ' + results.length + ' <?php _e('条疑似垃圾评论', 'argon');?></p>';
html += '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;"><thead><tr><th style="width: 80px;"><?php _e('评论 ID', 'argon');?></th><th style="width: 120px;"><?php _e('作者', 'argon');?></th><th><?php _e('内容', 'argon');?></th><th style="width: 150px;"><?php _e('识别理由', 'argon');?></th><th style="width: 100px;"><?php _e('操作', 'argon');?></th></tr></thead><tbody>';
results.forEach(function(item) { results.forEach(function(item) {
html += '<tr data-comment-id="' + item.comment_id + '">'; html += '<tr data-comment-id="' + item.comment_id + '">';
html += '<td>' + item.comment_id + '</td>'; html += '<td>' + item.comment_id + '</td>';
html += '<td>' + $('<div>').text(item.author).html() + '</td>'; html += '<td>' + $('<div>').text(item.author).html() + '</td>';
html += '<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">' + $('<div>').text(item.content).html() + '</td>'; html += '<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="' + $('<div>').text(item.content).html() + '">' + $('<div>').text(item.content).html() + '</td>';
html += '<td>' + $('<div>').text(item.reason).html() + '</td>'; html += '<td>' + $('<div>').text(item.reason).html() + '</td>';
html += '<td><button type="button" class="button button-small argon-spam-trash-btn" data-comment-id="' + item.comment_id + '"><?php _e('移入回收站', 'argon');?></button></td>'; html += '<td><button type="button" class="button button-small argon-spam-trash-btn" data-comment-id="' + item.comment_id + '"><?php _e('移入回收站', 'argon');?></button></td>';
html += '</tr>'; html += '</tr>';
@@ -4063,7 +4034,7 @@ window.pjaxLoaded = function(){
btn.closest('tr').fadeOut(300, function() { btn.closest('tr').fadeOut(300, function() {
$(this).remove(); $(this).remove();
if ($('#argon-spam-detection-results-content tbody tr').length === 0) { if ($('#argon-spam-detection-results-content tbody tr').length === 0) {
$('#argon-spam-detection-results-content').html('<p><?php _e('所有标记的垃圾评论已处理', 'argon');?></p>'); $('#argon-spam-detection-results-content').html('<p style="color: #46b450; font-weight: 600;"><span class="dashicons dashicons-yes-alt" style="font-size: 20px; vertical-align: middle;"></span> <?php _e('所有标记的垃圾评论已处理', 'argon');?></p>');
} }
}); });
} else { } else {