feat: 添加全站扫描垃圾评论功能

- 在设置页面添加全站扫描 UI(扫描所有评论/仅扫描待审核)
- 实现批量扫描逻辑,每批处理 10 条评论避免超时
- 自动跳过已有 AI 审核结果的评论(检查 _argon_spam_detection_result 元数据)
- 实时显示扫描进度条和统计信息(已扫描/已跳过/发现垃圾评论)
- 扫描完成后展示详细结果,包括垃圾评论列表和置信度
- 根据置信度阈值自动处理垃圾评论(移入回收站/标记待审核/仅标记)
- 添加 AJAX 处理函数 argon_ajax_spam_scan_comments
This commit is contained in:
2026-01-27 11:33:24 +08:00
parent bc9f2e18bf
commit 86231cbf14
2 changed files with 290 additions and 17 deletions

View File

@@ -13101,3 +13101,118 @@ function argon_ajax_clear_keyword_optimization_log() {
wp_send_json_success(['message' => '关键字优化日志已清除']);
}
add_action('wp_ajax_argon_clear_keyword_optimization_log', 'argon_ajax_clear_keyword_optimization_log');
/**
* AJAX: 全站扫描垃圾评论
*/
function argon_ajax_spam_scan_comments() {
check_ajax_referer('argon_spam_scan_comments', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => '权限不足']);
}
$scan_type = isset($_POST['scan_type']) ? sanitize_text_field($_POST['scan_type']) : 'all';
$offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
$batch_size = isset($_POST['batch_size']) ? intval($_POST['batch_size']) : 10;
// 构建查询参数
$args = [
'number' => $batch_size,
'offset' => $offset,
'orderby' => 'comment_date',
'order' => 'DESC'
];
// 如果只扫描待审核评论
if ($scan_type === 'pending') {
$args['status'] = 'hold';
}
// 获取评论
$comments = get_comments($args);
// 获取总数(用于计算进度)
$total_args = $args;
unset($total_args['number']);
unset($total_args['offset']);
$total_args['count'] = true;
$total_comments = get_comments($total_args);
$scanned = 0;
$skipped = 0;
$spam_found = 0;
$spam_comments = [];
foreach ($comments as $comment) {
// 检查是否已经过 AI 审核
$existing_result = get_comment_meta($comment->comment_ID, '_argon_spam_detection_result', true);
if (!empty($existing_result)) {
// 已审核,跳过
$skipped++;
continue;
}
// 使用 AI 检测
$detection_result = argon_detect_spam_with_ai($comment->comment_content, $comment->comment_author, $comment->comment_author_email);
if ($detection_result && isset($detection_result['is_spam'])) {
// 保存检测结果
update_comment_meta($comment->comment_ID, '_argon_spam_detection_result', $detection_result);
$scanned++;
if ($detection_result['is_spam']) {
$spam_found++;
// 添加到垃圾评论列表
$spam_comments[] = [
'id' => $comment->comment_ID,
'author' => esc_html($comment->comment_author),
'content' => esc_html(mb_substr($comment->comment_content, 0, 200)),
'date' => get_comment_date('Y-m-d H:i', $comment->comment_ID),
'confidence' => isset($detection_result['confidence']) ? intval($detection_result['confidence']) : 0,
'reason' => isset($detection_result['reason']) ? esc_html($detection_result['reason']) : '',
'edit_link' => admin_url('comment.php?action=editcomment&c=' . $comment->comment_ID)
];
// 根据设置自动处理
$auto_action = get_option('argon_comment_spam_detection_auto_action', 'trash');
$confidence_threshold = intval(get_option('argon_comment_spam_detection_confidence_threshold', 85));
$confidence = isset($detection_result['confidence']) ? intval($detection_result['confidence']) : 0;
if ($confidence >= $confidence_threshold) {
if ($auto_action === 'trash') {
wp_trash_comment($comment->comment_ID);
} elseif ($auto_action === 'hold') {
wp_set_comment_status($comment->comment_ID, 'hold');
}
// 'mark' 选项只标记不处理
}
}
} else {
$scanned++;
}
}
// 计算进度
$progress = 0;
if ($total_comments > 0) {
$progress = min(100, round((($offset + count($comments)) / $total_comments) * 100));
}
// 判断是否完成
$completed = (count($comments) < $batch_size) || (($offset + $batch_size) >= $total_comments);
wp_send_json_success([
'scanned' => $scanned,
'skipped' => $skipped,
'spam_found' => $spam_found,
'spam_comments' => $spam_comments,
'progress' => $progress,
'completed' => $completed,
'total' => $total_comments
]);
}
add_action('wp_ajax_argon_spam_scan_comments', 'argon_ajax_spam_scan_comments');

View File

@@ -2521,26 +2521,34 @@ function themeoptions_page(){
</p>
</td>
</tr>
<td>
<select name="argon_comment_spam_detection_prompt_mode">
<?php $argon_comment_spam_detection_prompt_mode = get_option('argon_comment_spam_detection_prompt_mode', 'standard'); ?>
<option value="minimal" <?php if ($argon_comment_spam_detection_prompt_mode=='minimal'){echo 'selected';} ?>><?php _e('极简模式(省 token', 'argon');?></option>
<option value="standard" <?php if ($argon_comment_spam_detection_prompt_mode=='standard'){echo 'selected';} ?>><?php _e('标准模式(推荐)', 'argon');?></option>
<option value="enhanced" <?php if ($argon_comment_spam_detection_prompt_mode=='enhanced'){echo 'selected';} ?>><?php _e('增强模式(更准确)', 'argon');?></option>
<option value="custom" <?php if ($argon_comment_spam_detection_prompt_mode=='custom'){echo 'selected';} ?>><?php _e('自定义 Prompt', 'argon');?></option>
</select>
<p class="description">
<?php _e('不同模式使用不同的 Prompt平衡准确性和 API 成本', 'argon');?>
</p>
</td>
</tr>
<tr class="argon-custom-prompt-row" style="<?php echo ($argon_comment_spam_detection_prompt_mode !== 'custom') ? 'display:none;' : ''; ?>">
<th><label><?php _e('自定义 Prompt', 'argon');?></label></th>
<tr>
<th><label><?php _e('全站扫描', 'argon');?></label></th>
<td>
<textarea name="argon_comment_spam_detection_custom_prompt" rows="8" cols="70" style="font-family: monospace;"><?php echo get_option('argon_comment_spam_detection_custom_prompt', ''); ?></textarea>
<button type="button" class="button button-primary" id="argon-spam-scan-all">
<span class="dashicons dashicons-search" style="margin-top: 3px;"></span>
<?php _e('扫描所有评论', 'argon'); ?>
</button>
<button type="button" class="button" id="argon-spam-scan-pending" style="margin-left: 10px;">
<span class="dashicons dashicons-filter" style="margin-top: 3px;"></span>
<?php _e('仅扫描待审核', 'argon'); ?>
</button>
<span id="argon-spam-scan-status" style="margin-left: 15px; color: #666;"></span>
<div id="argon-spam-scan-progress" style="display: none; margin: 15px 0;">
<div style="background: #f0f0f1; border-radius: 4px; height: 24px; position: relative; overflow: hidden;">
<div id="argon-spam-scan-progress-bar" style="background: var(--themecolor, #5e72e4); height: 100%; width: 0%; transition: width 0.3s;"></div>
<span id="argon-spam-scan-progress-text" style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 12px; font-weight: 600; color: #333;"></span>
</div>
</div>
<div id="argon-spam-scan-results" style="display: none; margin-top: 15px; padding: 15px; background: #fff; border: 1px solid #ddd; border-radius: 4px; max-height: 400px; overflow-y: auto;">
<div id="argon-spam-scan-results-content"></div>
</div>
<p class="description">
<?php _e('自定义 AI 检测时使用的 Prompt。评论内容会自动附加在 Prompt 后面。', 'argon');?>
<?php _e('扫描现有评论,识别垃圾评论。自动跳过已经过 AI 审核的评论,避免重复检测。', 'argon');?><br/>
<?php _e('检测到的垃圾评论会被标记,您可以选择性地移入回收站。', 'argon');?>
</p>
</td>
</tr>
@@ -2868,6 +2876,156 @@ function themeoptions_page(){
$('.argon-custom-prompt-row').slideUp();
}
});
// ========== 全站扫描 ==========
let scanInProgress = false;
function startSpamScan(scanType) {
if (scanInProgress) {
alert('<?php _e('扫描正在进行中,请稍候...', 'argon'); ?>');
return;
}
if (!confirm('<?php _e('确定要开始扫描吗?这可能需要一些时间。', 'argon'); ?>')) {
return;
}
scanInProgress = true;
// 禁用按钮
$('#argon-spam-scan-all, #argon-spam-scan-pending').prop('disabled', true);
// 显示进度条
$('#argon-spam-scan-progress').show();
$('#argon-spam-scan-progress-bar').css('width', '0%');
$('#argon-spam-scan-progress-text').text('0%');
$('#argon-spam-scan-status').text('<?php _e('正在初始化...', 'argon'); ?>');
// 隐藏之前的结果
$('#argon-spam-scan-results').hide();
// 开始扫描
scanBatch(scanType, 0, 0, 0, 0, []);
}
function scanBatch(scanType, offset, totalScanned, totalSkipped, totalSpam, spamComments) {
$.post(ajaxurl, {
action: 'argon_spam_scan_comments',
nonce: '<?php echo wp_create_nonce('argon_spam_scan_comments'); ?>',
scan_type: scanType,
offset: offset,
batch_size: 10
}, function(response) {
if (response.success) {
let data = response.data;
totalScanned += data.scanned;
totalSkipped += data.skipped;
totalSpam += data.spam_found;
// 合并垃圾评论列表
if (data.spam_comments && data.spam_comments.length > 0) {
spamComments = spamComments.concat(data.spam_comments);
}
// 更新进度
let progress = data.progress || 0;
$('#argon-spam-scan-progress-bar').css('width', progress + '%');
$('#argon-spam-scan-progress-text').text(progress + '%');
$('#argon-spam-scan-status').text('<?php _e('已扫描', 'argon'); ?> ' + totalScanned + ' <?php _e('条,跳过', 'argon'); ?> ' + totalSkipped + ' <?php _e('条,发现垃圾评论', 'argon'); ?> ' + totalSpam + ' <?php _e('条', 'argon'); ?>');
// 检查是否完成
if (data.completed) {
// 扫描完成
scanInProgress = false;
$('#argon-spam-scan-all, #argon-spam-scan-pending').prop('disabled', false);
$('#argon-spam-scan-status').html('<span style="color: #4caf50; font-weight: 600;">✓ <?php _e('扫描完成!', 'argon'); ?></span>');
// 显示结果
displayScanResults(totalScanned, totalSkipped, totalSpam, spamComments);
} else {
// 继续下一批
scanBatch(scanType, offset + 10, totalScanned, totalSkipped, totalSpam, spamComments);
}
} else {
// 错误处理
scanInProgress = false;
$('#argon-spam-scan-all, #argon-spam-scan-pending').prop('disabled', false);
let errorMsg = (response.data && response.data.message) ? response.data.message : '<?php _e('未知错误', 'argon'); ?>';
$('#argon-spam-scan-status').html('<span style="color: #d63638; font-weight: 600;">✗ <?php _e('扫描失败:', 'argon'); ?> ' + errorMsg + '</span>');
}
}).fail(function(xhr, status, error) {
scanInProgress = false;
$('#argon-spam-scan-all, #argon-spam-scan-pending').prop('disabled', false);
$('#argon-spam-scan-status').html('<span style="color: #d63638; font-weight: 600;">✗ <?php _e('请求失败:', 'argon'); ?> ' + error + '</span>');
});
}
function displayScanResults(totalScanned, totalSkipped, totalSpam, spamComments) {
let html = '<h3 style="margin-top: 0; color: #333;"><?php _e('扫描结果', 'argon'); ?></h3>';
html += '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 20px;">';
html += '<div style="background: #e3f2fd; padding: 15px; border-radius: 4px; text-align: center;">';
html += '<div style="font-size: 28px; font-weight: 700; color: #1976d2;">' + totalScanned + '</div>';
html += '<div style="color: #666; margin-top: 5px;"><?php _e('已扫描', 'argon'); ?></div>';
html += '</div>';
html += '<div style="background: #fff3e0; padding: 15px; border-radius: 4px; text-align: center;">';
html += '<div style="font-size: 28px; font-weight: 700; color: #f57c00;">' + totalSkipped + '</div>';
html += '<div style="color: #666; margin-top: 5px;"><?php _e('已跳过', 'argon'); ?></div>';
html += '</div>';
html += '<div style="background: #ffebee; padding: 15px; border-radius: 4px; text-align: center;">';
html += '<div style="font-size: 28px; font-weight: 700; color: #d32f2f;">' + totalSpam + '</div>';
html += '<div style="color: #666; margin-top: 5px;"><?php _e('垃圾评论', 'argon'); ?></div>';
html += '</div>';
html += '</div>';
if (totalSpam > 0 && spamComments.length > 0) {
html += '<h4 style="margin: 20px 0 10px 0; color: #333;"><?php _e('发现的垃圾评论', 'argon'); ?></h4>';
html += '<div style="max-height: 300px; overflow-y: auto;">';
spamComments.forEach(function(comment) {
let confidenceColor = comment.confidence >= 90 ? '#d32f2f' : (comment.confidence >= 70 ? '#f57c00' : '#fbc02d');
html += '<div style="background: #fff; border: 1px solid #ddd; border-left: 3px solid ' + confidenceColor + '; padding: 12px; margin-bottom: 10px; border-radius: 3px;">';
html += '<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">';
html += '<div>';
html += '<strong style="color: #333;">' + comment.author + '</strong>';
html += '<span style="color: #999; margin-left: 10px; font-size: 12px;">' + comment.date + '</span>';
html += '</div>';
html += '<div>';
html += '<span style="background: ' + confidenceColor + '; color: white; padding: 3px 8px; border-radius: 3px; font-size: 12px; font-weight: 600;"><?php _e('置信度', 'argon'); ?> ' + comment.confidence + '%</span>';
html += '</div>';
html += '</div>';
html += '<div style="color: #666; font-size: 14px; line-height: 1.6;">' + comment.content + '</div>';
if (comment.reason) {
html += '<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #eee; color: #999; font-size: 12px;">';
html += '<strong><?php _e('原因:', 'argon'); ?></strong> ' + comment.reason;
html += '</div>';
}
html += '<div style="margin-top: 10px;">';
html += '<a href="' + comment.edit_link + '" target="_blank" class="button button-small"><?php _e('查看', 'argon'); ?></a>';
html += '</div>';
html += '</div>';
});
html += '</div>';
html += '<p style="margin-top: 15px; color: #666; font-size: 13px;">';
html += '<?php _e('提示:您可以在评论管理页面查看和处理这些垃圾评论。', 'argon'); ?>';
html += '</p>';
} else if (totalScanned > 0) {
html += '<div style="background: #e8f5e9; padding: 20px; border-radius: 4px; text-align: center; color: #2e7d32;">';
html += '<span class="dashicons dashicons-yes-alt" style="font-size: 48px; width: 48px; height: 48px;"></span>';
html += '<p style="margin: 10px 0 0 0; font-size: 16px; font-weight: 600;"><?php _e('太棒了!没有发现垃圾评论。', 'argon'); ?></p>';
html += '</div>';
}
$('#argon-spam-scan-results-content').html(html);
$('#argon-spam-scan-results').slideDown();
}
// 绑定扫描按钮事件
$('#argon-spam-scan-all').on('click', function() {
startSpamScan('all');
});
$('#argon-spam-scan-pending').on('click', function() {
startSpamScan('pending');
});
});
</script>