WordPress防止发表重复标题文章:2026年完整代码解决方案

本文针对WordPress内容管理系统,阐述了防止发表重复标题文章的重要性,包括提升SEO排名、优化用户体验等。文章提供了2026年的完整代码解决方案,重点介绍了基于发布前检查的技术实现思路,并给出了核心的查重函数代码示例,用于在文章保存时进行标题重复性验证。

文章作者:曾凤祥
阅读时间: 597 分钟
更新时间:2026年3月19日

在内容管理系统中,防止发表重复标题的文章是确保内容质量和SEO表现的重要环节。本文提供2026年WordPress中防止重复标题文章的完整代码解决方案。

一、问题分析与设计思路

为什么需要防止重复标题?

  1. SEO优化:重复标题导致内容重复,影响搜索引擎排名
  2. 用户体验:用户混淆相似内容,降低阅读体验
  3. 数据管理:避免数据冗余,便于内容管理
  4. 品牌形象:专业网站应避免重复内容

技术实现思路

用户提交 → 标题检查 → 查重验证 → 阻止/警告 → 处理完成
       ↓
   数据库查询
       ↓
   相似度计算
       ↓
   智能建议

二、基础防止重复方案

方案1:发布前检查(最常用)

核心检查函数

/**
 * 检查文章标题是否重复
 * @param string $title 文章标题
 * @param int $post_id 当前文章ID(编辑时使用)
 * @param string $post_type 文章类型
 * @return array 检查结果
 */
function check_duplicate_post_title($title, $post_id = 0, $post_type = 'post') {
    global $wpdb;
    
    // 清理标题
    $clean_title = sanitize_text_field($title);
    $clean_title = trim($clean_title);
    
    if (empty($clean_title)) {
        return [
            'is_duplicate' => false,
            'message' => '标题不能为空',
            'similar_posts' => []
        ];
    }
    
    // 准备SQL查询
    $query = $wpdb->prepare("
        SELECT ID, post_title, post_status, post_date
        FROM {$wpdb->posts}
        WHERE post_title = %s
        AND post_type = %s
        AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
    ", $clean_title, $post_type);
    
    // 如果是编辑文章,排除自己
    if ($post_id > 0) {
        $query .= $wpdb->prepare(" AND ID != %d", $post_id);
    }
    
    $existing_posts = $wpdb->get_results($query);
    
    if (!empty($existing_posts)) {
        $formatted_posts = [];
        
        foreach ($existing_posts as $post) {
            $formatted_posts[] = [
                'id' => $post->ID,
                'title' => $post->post_title,
                'status' => $post->post_status,
                'date' => $post->post_date,
                'edit_link' => get_edit_post_link($post->ID),
                'view_link' => get_permalink($post->ID)
            ];
        }
        
        return [
            'is_duplicate' => true,
            'message' => '发现重复标题的文章',
            'similar_posts' => $formatted_posts,
            'count' => count($existing_posts)
        ];
    }
    
    return [
        'is_duplicate' => false,
        'message' => '标题可用',
        'similar_posts' => []
    ];
}

添加到文章保存钩子

/**
 * 保存文章时检查重复标题
 */
function prevent_duplicate_title_on_save($data, $postarr) {
    // 只检查特定文章类型
    $check_post_types = apply_filters('duplicate_title_check_post_types', ['post', 'page']);
    
    if (!in_array($data['post_type'], $check_post_types)) {
        return $data;
    }
    
    // 获取标题
    $title = isset($data['post_title']) ? $data['post_title'] : '';
    
    if (empty($title)) {
        return $data;
    }
    
    // 检查重复
    $post_id = isset($postarr['ID']) ? intval($postarr['ID']) : 0;
    $result = check_duplicate_post_title($title, $post_id, $data['post_type']);
    
    if ($result['is_duplicate']) {
        // 阻止保存并显示错误
        $error_message = '错误:存在重复标题的文章。<br>';
        
        foreach ($result['similar_posts'] as $similar) {
            $status_text = get_post_status_object($similar['status'])->label;
            $error_message .= sprintf(
                '• <a href="%s" target="_blank">%s</a> (%s, 发布于%s)<br>',
                esc_url($similar['edit_link']),
                esc_html($similar['title']),
                $status_text,
                date('Y-m-d', strtotime($similar['date']))
            );
        }
        
        // 保存错误到会话或transient
        set_transient('duplicate_title_error_' . get_current_user_id(), $error_message, 30);
        
        // 添加管理错误
        if (is_admin()) {
            add_action('admin_notices', function() use ($error_message) {
                echo '<div class="notice notice-error is-dismissible"><p>' . $error_message . '</p></div>';
            });
        }
        
        // 返回错误阻止保存
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return $data;
        }
        
        // 在后台直接阻止
        if (is_admin()) {
            wp_die($error_message, '重复标题错误', ['back_link' => true]);
        }
    }
    
    return $data;
}

// 高优先级,在保存前检查
add_filter('wp_insert_post_data', 'prevent_duplicate_title_on_save', 99, 2);

方案2:AJAX实时检查

/**
 * 添加AJAX实时标题检查
 */
function enqueue_title_check_scripts() {
    global $pagenow, $post_type;
    
    $check_pages = ['post.php', 'post-new.php'];
    
    if (in_array($pagenow, $check_pages)) {
        wp_enqueue_script('title-check-js', 
            get_template_directory_uri() . '/js/title-check.js', 
            ['jquery'], 
            '1.0.0', 
            true
        );
        
        wp_localize_script('title-check-js', 'titleCheckAjax', [
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('title_check_nonce'),
            'checking_text' => '检查标题中...',
            'available_text' => '标题可用',
            'duplicate_text' => '标题已存在',
            'similar_text' => '发现相似标题'
        ]);
    }
}
add_action('admin_enqueue_scripts', 'enqueue_title_check_scripts');

// AJAX处理
add_action('wp_ajax_check_post_title', 'ajax_check_post_title');
function ajax_check_post_title() {
    // 验证nonce
    check_ajax_referer('title_check_nonce', 'nonce');
    
    $title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
    $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
    $post_type = isset($_POST['post_type']) ? sanitize_text_field($_POST['post_type']) : 'post';
    
    if (empty($title)) {
        wp_send_json_error(['message' => '标题不能为空']);
    }
    
    $result = check_duplicate_post_title($title, $post_id, $post_type);
    
    if ($result['is_duplicate']) {
        wp_send_json_success([
            'is_duplicate' => true,
            'message' => '发现重复标题',
            'similar_posts' => $result['similar_posts']
        ]);
    } else {
        wp_send_json_success([
            'is_duplicate' => false,
            'message' => '标题可用'
        ]);
    }
}

前端JavaScript

// title-check.js
(function($) {
    'use strict';
    
    var titleCheck = {
        init: function() {
            this.cacheElements();
            this.bindEvents();
        },
        
        cacheElements: function() {
            this.$titleInput = $('#title');
            this.$titleWrapper = $('#titlediv');
            this.checkTimer = null;
            this.currentTitle = '';
        },
        
        bindEvents: function() {
            if (!this.$titleInput.length) return;
            
            // 输入时检查
            this.$titleInput.on('input', $.proxy(this.onTitleChange, this));
            
            // 发布时最后检查
            $('#publish').on('click', $.proxy(this.onPublishClick, this));
        },
        
        onTitleChange: function(e) {
            var newTitle = $(e.target).val().trim();
            
            // 避免频繁检查
            if (newTitle === this.currentTitle || newTitle.length < 3) {
                this.removeStatus();
                return;
            }
            
            this.currentTitle = newTitle;
            
            clearTimeout(this.checkTimer);
            this.checkTimer = setTimeout($.proxy(this.checkTitle, this), 500);
        },
        
        checkTitle: function() {
            var title = this.currentTitle;
            
            if (title.length < 3) {
                this.removeStatus();
                return;
            }
            
            this.showChecking();
            
            $.ajax({
                url: titleCheckAjax.ajax_url,
                type: 'POST',
                data: {
                    action: 'check_post_title',
                    nonce: titleCheckAjax.nonce,
                    title: title,
                    post_id: $('#post_ID').val() || 0,
                    post_type: $('#post_type').val() || 'post'
                },
                success: $.proxy(this.onCheckSuccess, this),
                error: $.proxy(this.onCheckError, this)
            });
        },
        
        onCheckSuccess: function(response) {
            if (!response.success) {
                this.showError('检查失败');
                return;
            }
            
            var data = response.data;
            
            if (data.is_duplicate) {
                this.showDuplicate(data);
            } else {
                this.showAvailable();
            }
        },
        
        onCheckError: function() {
            this.showError('网络错误,请重试');
        },
        
        showChecking: function() {
            this.removeStatus();
            this.$titleWrapper.append(
                '<div class="title-check-status checking">' + 
                '<span class="dashicons dashicons-update"></span> ' + 
                titleCheckAjax.checking_text + 
                '</div>'
            );
        },
        
        showAvailable: function() {
            this.removeStatus();
            this.$titleWrapper.append(
                '<div class="title-check-status available">' + 
                '<span class="dashicons dashicons-yes"></span> ' + 
                titleCheckAjax.available_text + 
                '</div>'
            );
        },
        
        showDuplicate: function(data) {
            this.removeStatus();
            
            var $status = $(
                '<div class="title-check-status duplicate">' +
                '<span class="dashicons dashicons-no"></span> ' + 
                titleCheckAjax.duplicate_text +
                '</div>'
            );
            
            if (data.similar_posts && data.similar_posts.length > 0) {
                var $list = $('<ul class="duplicate-posts-list"></ul>');
                
                $.each(data.similar_posts, function(i, post) {
                    var statusText = post.status === 'publish' ? '已发布' : 
                                    post.status === 'draft' ? '草稿' : 
                                    post.status === 'pending' ? '待审核' : post.status;
                    
                    $list.append(
                        '<li>' +
                        '<a href="' + post.edit_link + '" target="_blank" class="edit-link">编辑</a> | ' +
                        '<a href="' + post.view_link + '" target="_blank" class="view-link">查看</a> | ' +
                        '<span class="post-status">' + statusText + '</span> | ' +
                        '<span class="post-date">' + post.date + '</span>' +
                        '</li>'
                    );
                });
                
                $status.append($list);
            }
            
            this.$titleWrapper.append($status);
        },
        
        showError: function(message) {
            this.removeStatus();
            this.$titleWrapper.append(
                '<div class="title-check-status error">' + 
                '<span class="dashicons dashicons-warning"></span> ' + 
                message + 
                '</div>'
            );
        },
        
        removeStatus: function() {
            $('.title-check-status').remove();
        },
        
        onPublishClick: function(e) {
            var title = this.$titleInput.val().trim();
            
            if (title.length < 3) {
                e.preventDefault();
                alert('请输入有效的标题');
                return;
            }
            
            // 同步检查
            $.ajax({
                url: titleCheckAjax.ajax_url,
                type: 'POST',
                async: false,
                data: {
                    action: 'check_post_title',
                    nonce: titleCheckAjax.nonce,
                    title: title,
                    post_id: $('#post_ID').val() || 0,
                    post_type: $('#post_type').val() || 'post'
                },
                success: function(response) {
                    if (response.success && response.data.is_duplicate) {
                        e.preventDefault();
                        
                        var message = '存在重复标题,请修改后重试。\n\n重复文章:\n';
                        $.each(response.data.similar_posts, function(i, post) {
                            message += (i+1) + '. ' + post.title + '\n';
                        });
                        
                        alert(message);
                    }
                }
            });
        }
    };
    
    $(document).ready(function() {
        titleCheck.init();
    });
    
})(jQuery);

CSS样式

.title-check-status {
    padding: 8px 12px;
    margin: 10px 0;
    border-radius: 4px;
    font-size: 13px;
}

.title-check-status.checking {
    background-color: #f0f0f1;
    border-left: 4px solid #72aee6;
}

.title-check-status.available {
    background-color: #edfaef;
    border-left: 4px solid #00a32a;
}

.title-check-status.duplicate {
    background-color: #fcf0f1;
    border-left: 4px solid #d63638;
}

.title-check-status.error {
    background-color: #fcf9e8;
    border-left: 4px solid #dba617;
}

.title-check-status .dashicons {
    margin-right: 5px;
    vertical-align: middle;
}

.duplicate-posts-list {
    margin: 8px 0 0 0;
    padding-left: 20px;
}

.duplicate-posts-list li {
    margin: 5px 0;
    padding: 3px 0;
    border-bottom: 1px dashed #ddd;
}

.duplicate-posts-list li:last-child {
    border-bottom: none;
}

.duplicate-posts-list a {
    text-decoration: none;
}

.duplicate-posts-list .edit-link {
    color: #2271b1;
}

.duplicate-posts-list .view-link {
    color: #50575e;
}

.duplicate-posts-list .post-status,
.duplicate-posts-list .post-date {
    color: #8c8f94;
    font-size: 12px;
    margin-left: 8px;
}

三、高级查重方案

方案3:智能相似度检查

/**
 * 智能标题相似度检查
 * 使用多种算法计算相似度
 */
class TitleSimilarityChecker {
    
    /**
     * 计算标题相似度
     * @param string $title1 标题1
     * @param string $title2 标题2
     * @return float 相似度分数 0-1
     */
    public static function calculate_similarity($title1, $title2) {
        $title1 = self::normalize_title($title1);
        $title2 = self::normalize_title($title2);
        
        if ($title1 === $title2) {
            return 1.0;
        }
        
        // 使用多种算法计算相似度
        $algorithms = [
            'levenshtein' => self::similarity_levenshtein($title1, $title2),
            'jaro_winkler' => self::similarity_jaro_winkler($title1, $title2),
            'cosine' => self::similarity_cosine($title1, $title2)
        ];
        
        // 加权平均
        $weights = [
            'levenshtein' => 0.3,
            'jaro_winkler' => 0.4,
            'cosine' => 0.3
        ];
        
        $total_score = 0;
        foreach ($algorithms as $algorithm => $score) {
            $total_score += $score * $weights[$algorithm];
        }
        
        return $total_score;
    }
    
    /**
     * 标准化标题
     */
    private static function normalize_title($title) {
        $title = strtolower(trim($title));
        
        // 移除标点符号
        $title = preg_replace('/[^\p{L}\p{N}\s]/u', '', $title);
        
        // 移除多余空格
        $title = preg_replace('/\s+/', ' ', $title);
        
        // 移除停用词
        $stop_words = ['的', '了', '在', '是', '我', '有', '和', '就', 
                      '不', '人', '都', '一', '一个', '上', '也', '很', 
                      '到', '说', '要', '去', '你', '会', '着', '没有', 
                      '看', '好', '自己', '这'];
        
        $words = explode(' ', $title);
        $words = array_diff($words, $stop_words);
        
        return implode(' ', $words);
    }
    
    /**
     * Levenshtein距离算法
     */
    private static function similarity_levenshtein($str1, $str2) {
        $len1 = mb_strlen($str1, 'UTF-8');
        $len2 = mb_strlen($str2, 'UTF-8');
        
        if ($len1 == 0 || $len2 == 0) {
            return 0;
        }
        
        $max_len = max($len1, $len2);
        $distance = levenshtein($str1, $str2);
        
        return 1 - ($distance / $max_len);
    }
    
    /**
     * Jaro-Winkler算法
     */
    private static function similarity_jaro_winkler($str1, $str2) {
        $len1 = mb_strlen($str1, 'UTF-8');
        $len2 = mb_strlen($str2, 'UTF-8');
        
        if ($len1 == 0 || $len2 == 0) {
            return 0;
        }
        
        $match_distance = (int) floor(max($len1, $len2) / 2) - 1;
        
        $str1_matches = array_fill(0, $len1, false);
        $str2_matches = array_fill(0, $len2, false);
        
        $matches = 0;
        $transpositions = 0;
        
        for ($i = 0; $i < $len1; $i++) {
            $start = max(0, $i - $match_distance);
            $end = min($i + $match_distance + 1, $len2);
            
            for ($j = $start; $j < $end; $j++) {
                if ($str2_matches[$j]) continue;
                
                if (mb_substr($str1, $i, 1, 'UTF-8') == mb_substr($str2, $j, 1, 'UTF-8')) {
                    $str1_matches[$i] = true;
                    $str2_matches[$j] = true;
                    $matches++;
                    break;
                }
            }
        }
        
        if ($matches == 0) return 0;
        
        $k = 0;
        for ($i = 0; $i < $len1; $i++) {
            if (!$str1_matches[$i]) continue;
            
            while (!$str2_matches[$k]) $k++;
            
            if (mb_substr($str1, $i, 1, 'UTF-8') != mb_substr($str2, $k, 1, 'UTF-8')) {
                $transpositions++;
            }
            
            $k++;
        }
        
        $transpositions /= 2;
        
        $jaro = (($matches / $len1) + ($matches / $len2) + (($matches - $transpositions) / $matches)) / 3;
        
        // Winkler调整
        $prefix = 0;
        $max_prefix = min(4, min($len1, $len2));
        
        for ($i = 0; $i < $max_prefix; $i++) {
            if (mb_substr($str1, $i, 1, 'UTF-8') == mb_substr($str2, $i, 1, 'UTF-8')) {
                $prefix++;
            } else {
                break;
            }
        }
        
        $jaro_winkler = $jaro + ($prefix * 0.1 * (1 - $jaro));
        
        return $jaro_winkler;
    }
    
    /**
     * 余弦相似度算法
     */
    private static function similarity_cosine($str1, $str2) {
        $words1 = explode(' ', $str1);
        $words2 = explode(' ', $str2);
        
        $all_words = array_unique(array_merge($words1, $words2));
        
        $vector1 = array_fill_keys($all_words, 0);
        $vector2 = array_fill_keys($all_words, 0);
        
        foreach ($words1 as $word) {
            $vector1[$word]++;
        }
        
        foreach ($words2 as $word) {
            $vector2[$word]++;
        }
        
        $dot_product = 0;
        $magnitude1 = 0;
        $magnitude2 = 0;
        
        foreach ($all_words as $word) {
            $dot_product += $vector1[$word] * $vector2[$word];
            $magnitude1 += pow($vector1[$word], 2);
            $magnitude2 += pow($vector2[$word], 2);
        }
        
        $magnitude1 = sqrt($magnitude1);
        $magnitude2 = sqrt($magnitude2);
        
        if ($magnitude1 == 0 || $magnitude2 == 0) {
            return 0;
        }
        
        return $dot_product / ($magnitude1 * $magnitude2);
    }
    
    /**
     * 查找相似标题的文章
     */
    public static function find_similar_titles($title, $threshold = 0.8, $limit = 5, $exclude_id = 0) {
        global $wpdb;
        
        $normalized_title = self::normalize_title($title);
        
        // 获取所有文章标题进行比较
        $query = $wpdb->prepare("
            SELECT ID, post_title, post_status, post_date
            FROM {$wpdb->posts}
            WHERE post_type = 'post'
            AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
        ");
        
        if ($exclude_id > 0) {
            $query .= $wpdb->prepare(" AND ID != %d", $exclude_id);
        }
        
        $all_posts = $wpdb->get_results($query);
        
        $similar_posts = [];
        
        foreach ($all_posts as $post) {
            $similarity = self::calculate_similarity($title, $post->post_title);
            
            if ($similarity >= $threshold) {
                $similar_posts[] = [
                    'post' => $post,
                    'similarity' => round($similarity * 100, 1)
                ];
            }
        }
        
        // 按相似度排序
        usort($similar_posts, function($a, $b) {
            return $b['similarity'] <=> $a['similarity'];
        });
        
        // 限制数量
        $similar_posts = array_slice($similar_posts, 0, $limit);
        
        return $similar_posts;
    }
}

方案4:基于相似度的保存检查

/**
 * 使用智能相似度检查防止重复
 */
function prevent_similar_titles_on_save($data, $postarr) {
    $check_post_types = apply_filters('similar_title_check_post_types', ['post', 'page']);
    
    if (!in_array($data['post_type'], $check_post_types)) {
        return $data;
    }
    
    $title = isset($data['post_title']) ? $data['post_title'] : '';
    
    if (empty($title) || strlen($title) < 5) {
        return $data;
    }
    
    $post_id = isset($postarr['ID']) ? intval($postarr['ID']) : 0;
    $threshold = get_option('title_similarity_threshold', 0.85);
    
    $similar_posts = TitleSimilarityChecker::find_similar_titles($title, $threshold, 5, $post_id);
    
    if (!empty($similar_posts)) {
        $highest_similarity = $similar_posts[0]['similarity'];
        
        // 根据相似度阈值决定是否阻止
        if ($highest_similarity >= ($threshold * 100)) {
            $error_message = sprintf(
                '警告:发现高度相似的文章标题(相似度%.1f%%)。<br>',
                $highest_similarity
            );
            
            $error_message .= '相似文章:<br>';
            
            foreach ($similar_posts as $item) {
                $post = $item['post'];
                $similarity = $item['similarity'];
                
                $status_text = get_post_status_object($post->post_status)->label;
                $edit_link = get_edit_post_link($post->ID);
                
                $error_message .= sprintf(
                    '• <a href="%s" target="_blank">%s</a> (相似度%.1f%%, %s)<br>',
                    esc_url($edit_link),
                    esc_html($post->post_title),
                    $similarity,
                    $status_text
                );
            }
            
            $error_message .= '<br>是否继续保存?<br>';
            $error_message .= '<button type="button" class="button force-save">强制保存</button>';
            
            set_transient('similar_title_error_' . get_current_user_id(), $error_message, 30);
            
            // 在后台显示警告但不阻止
            if (is_admin()) {
                add_action('admin_notices', function() use ($error_message) {
                    ?>
                    <div class="notice notice-warning is-dismissible similar-title-warning">
                        <p><?php echo $error_message; ?></p>
                    </div>
                    <script>
                    jQuery(document).ready(function($) {
                        $('.similar-title-warning .force-save').on('click', function() {
                            $(this).closest('.notice').fadeOut();
                            // 添加隐藏字段标记强制保存
                            $('#title').after('<input type="hidden" name="force_save_similar_title" value="1">');
                        });
                    });
                    </script>
                    <?php
                });
                
                // 检查是否强制保存
                if (!isset($_POST['force_save_similar_title']) || $_POST['force_save_similar_title'] != '1') {
                    // 不阻止保存,但记录日志
                    error_log(sprintf(
                        '相似标题警告:文章"%s" (ID: %s) 与已有文章相似',
                        $title,
                        $post_id
                    ));
                }
            }
        }
    }
    
    return $data;
}
add_filter('wp_insert_post_data', 'prevent_similar_titles_on_save', 100, 2);

四、批量检查和清理

方案5:批量查重工具

/**
 * 批量查重工具类
 */
class BulkTitleDuplicateChecker {
    
    /**
     * 扫描所有文章的重复标题
     */
    public static function scan_all_duplicates($post_type = 'post') {
        global $wpdb;
        
        $query = $wpdb->prepare("
            SELECT post_title, COUNT(*) as count, 
                   GROUP_CONCAT(ID ORDER BY post_date DESC) as post_ids
            FROM {$wpdb->posts}
            WHERE post_type = %s
            AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
            GROUP BY post_title
            HAVING count > 1
            ORDER BY count DESC
        ", $post_type);
        
        $results = $wpdb->get_results($query);
        
        $duplicates = [];
        
        foreach ($results as $row) {
            $post_ids = explode(',', $row->post_ids);
            $post_details = [];
            
            foreach ($post_ids as $post_id) {
                $post = get_post($post_id);
                if ($post) {
                    $post_details[] = [
                        'id' => $post->ID,
                        'title' => $post->post_title,
                        'status' => $post->post_status,
                        'date' => $post->post_date,
                        'edit_link' => get_edit_post_link($post->ID),
                        'view_link' => get_permalink($post->ID)
                    ];
                }
            }
            
            $duplicates[] = [
                'title' => $row->post_title,
                'count' => $row->count,
                'posts' => $post_details
            ];
        }
        
        return $duplicates;
    }
    
    /**
     * 生成查重报告
     */
    public static function generate_report($duplicates) {
        $report = "文章标题查重报告\n";
        $report .= "生成时间: " . current_time('Y-m-d H:i:s') . "\n";
        $report .= "总共发现重复组: " . count($duplicates) . "\n";
        $report .= "====================\n\n";
        
        foreach ($duplicates as $index => $group) {
            $report .= sprintf("第%d组: %s (重复%d次)\n", 
                $index + 1, 
                $group['title'], 
                $group['count']
            );
            
            foreach ($group['posts'] as $post) {
                $status_text = get_post_status_object($post['status'])->label;
                $report .= sprintf("  - ID: %d, 状态: %s, 发布时间: %s\n",
                    $post['id'],
                    $status_text,
                    $post['date']
                );
            }
            
            $report .= "\n";
        }
        
        return $report;
    }
    
    /**
     * 批量合并建议
     */
    public static function suggest_merge($duplicate_group) {
        $posts = $duplicate_group['posts'];
        
        // 按状态和日期排序,保留最新发布的文章
        usort($posts, function($a, $b) {
            // 已发布的优先
            if ($a['status'] === 'publish' && $b['status'] !== 'publish') return -1;
            if ($b['status'] === 'publish' && $a['status'] !== 'publish') return 1;
            
            // 按日期倒序
            return strtotime($b['date']) <=> strtotime($a['date']);
        });
        
        $keep_post = $posts[0];
        $merge_posts = array_slice($posts, 1);
        
        return [
            'keep' => $keep_post,
            'merge' => $merge_posts,
            'suggest_action' => '保留最新发布的文章(ID: ' . $keep_post['id'] . '),合并或删除其他文章'
        ];
    }
}

/**
 * 管理后台批量查重页面
 */
function add_duplicate_checker_page() {
    add_management_page(
        '文章标题查重',
        '标题查重',
        'manage_options',
        'title-duplicate-checker',
        'display_duplicate_checker_page'
    );
}
add_action('admin_menu', 'add_duplicate_checker_page');

function display_duplicate_checker_page() {
    ?>
    <div class="wrap">
        <h1>文章标题查重工具</h1>
        
        <?php
        if (isset($_GET['action']) && $_GET['action'] === 'scan') {
            $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : 'post';
            $duplicates = BulkTitleDuplicateChecker::scan_all_duplicates($post_type);
            
            if (empty($duplicates)) {
                echo '<div class="notice notice-success"><p>未发现重复标题的文章。</p></div>';
            } else {
                echo '<div class="notice notice-warning"><p>发现' . count($duplicates) . '组重复标题。</p></div>';
                
                echo '<table class="wp-list-table widefat fixed striped">';
                echo '<thead>';
                echo '<tr>';
                echo '<th width="30%">标题</th>';
                echo '<th width="10%">重复数</th>';
                echo '<th width="40%">文章列表</th>';
                echo '<th width="20%">操作</th>';
                echo '</tr>';
                echo '</thead>';
                echo '<tbody>';
                
                foreach ($duplicates as $group) {
                    echo '<tr>';
                    echo '<td><strong>' . esc_html($group['title']) . '</strong></td>';
                    echo '<td>' . $group['count'] . '</td>';
                    echo '<td>';
                    
                    foreach ($group['posts'] as $post) {
                        $status_text = get_post_status_object($post['status'])->label;
                        echo '<div>';
                        echo '<a href="' . esc_url($post['edit_link']) . '">编辑</a> | ';
                        echo '<a href="' . esc_url($post['view_link']) . '" target="_blank">查看</a> | ';
                        echo 'ID: ' . $post['id'] . ' | ';
                        echo '状态: ' . $status_text . ' | ';
                        echo '时间: ' . $post['date'];
                        echo '</div>';
                    }
                    
                    echo '</td>';
                    echo '<td>';
                    
                    $suggestion = BulkTitleDuplicateChecker::suggest_merge($group);
                    echo '<button class="button button-small show-suggestion" 
                          data-keep-id="' . $suggestion['keep']['id'] . '"
                          data-merge-ids="' . implode(',', array_column($suggestion['merge'], 'id')) . '">查看建议</button>';
                    
                    echo '</td>';
                    echo '</tr>';
                }
                
                echo '</tbody>';
                echo '</table>';
                
                // 下载报告按钮
                echo '<form method="post" style="margin-top: 20px;">';
                echo '<input type="hidden" name="download_report" value="1">';
                echo '<input type="hidden" name="report_data" value=\'' . json_encode($duplicates) . '\'>';
                wp_nonce_field('download_report_nonce', 'report_nonce');
                echo '<button type="submit" class="button button-primary">下载查重报告</button>';
                echo '</form>';
            }
        }
        ?>
        
        <div class="card" style="max-width: 600px; margin-top: 20px;">
            <h2>扫描重复标题</h2>
            <form method="get">
                <input type="hidden" name="page" value="title-duplicate-checker">
                <input type="hidden" name="action" value="scan">
                
                <table class="form-table">
                    <tr>
                        <th scope="row">文章类型</th>
                        <td>
                            <select name="post_type">
                                <option value="post">文章</option>
                                <option value="page">页面</option>
                                <?php
                                $custom_post_types = get_post_types(['public' => true, '_builtin' => false]);
                                foreach ($custom_post_types as $post_type) {
                                    echo '<option value="' . esc_attr($post_type) . '">' . esc_html($post_type) . '</option>';
                                }
                                ?>
                            </select>
                        </td>
                    </tr>
                </table>
                
                <?php submit_button('开始扫描'); ?>
            </form>
        </div>
    </div>
    
    <script>
    jQuery(document).ready(function($) {
        $('.show-suggestion').on('click', function() {
            var keepId = $(this).data('keep-id');
            var mergeIds = $(this).data('merge-ids').split(',');
            
            var message = '建议操作:\n';
            message += '保留文章(ID: ' + keepId + ')\n';
            message += '处理以下重复文章:\n';
            
            mergeIds.forEach(function(id) {
                message += '  - ID: ' + id + '\n';
            });
            
            message += '\n是否执行合并操作?';
            
            if (confirm(message)) {
                // 这里可以添加AJAX合并操作
                alert('合并功能需要进一步开发');
            }
        });
    });
    </script>
    <?php
}

// 处理报告下载
add_action('admin_init', function() {
    if (isset($_POST['download_report']) && $_POST['download_report'] == '1') {
        check_admin_referer('download_report_nonce', 'report_nonce');
        
        $report_data = json_decode(stripslashes($_POST['report_data']), true);
        $report = BulkTitleDuplicateChecker::generate_report($report_data);
        
        header('Content-Type: text/plain');
        header('Content-Disposition: attachment; filename="title-duplicates-report-' . date('Ymd-His') . '.txt"');
        echo $report;
        exit;
    }
});

五、数据库层面防止重复

方案6:数据库唯一约束

/**
 * 添加数据库唯一约束
 * 注意:这会影响现有数据,需要谨慎操作
 */
function add_unique_title_constraint() {
    global $wpdb;
    
    $table_name = $wpdb->posts;
    
    // 检查是否已存在约束
    $result = $wpdb->get_var("
        SELECT COUNT(*)
        FROM information_schema.table_constraints
        WHERE table_name = '{$wpdb->posts}'
        AND constraint_name = 'unique_post_title'
        AND constraint_type = 'UNIQUE'
    ");
    
    if ($result == 0) {
        // 添加唯一约束
        $sql = "ALTER TABLE {$table_name} 
                ADD CONSTRAINT unique_post_title 
                UNIQUE (post_title, post_type, post_status)";
        
        $wpdb->query($sql);
        
        return true;
    }
    
    return false;
}

// 更安全的方案:添加唯一索引而不是约束
function add_unique_title_index() {
    global $wpdb;
    
    $index_name = 'idx_unique_post_title';
    
    // 检查是否已存在索引
    $result = $wpdb->get_var("
        SHOW INDEX FROM {$wpdb->posts} 
        WHERE Key_name = '{$index_name}'
    ");
    
    if (!$result) {
        // 创建唯一索引
        $sql = "CREATE UNIQUE INDEX {$index_name} 
                ON {$wpdb->posts} (post_title(191), post_type, post_status)";
        
        $wpdb->query($sql);
    }
}

// 在插件激活时执行
register_activation_hook(__FILE__, 'add_unique_title_index');

六、完整插件实现

Title Duplicate Preventer 2026 完整插件

<?php
/**
 * Plugin Name: Title Duplicate Preventer 2026
 * Plugin URI: https://yourwebsite.com/
 * Description: 防止发表重复或相似标题的文章
 * Version: 2.0.0
 * Author: Your Name
 * License: GPL v2 or later
 * Text Domain: title-duplicate-preventer
 */

// 防止直接访问
if (!defined('ABSPATH')) {
    exit;
}

class Title_Duplicate_Preventer_2026 {
    
    private static $instance = null;
    private $similarity_threshold = 0.85;
    private $check_exact_match = true;
    private $check_similar_match = true;
    private $force_check = false;
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        $this->init_hooks();
        $this->load_settings();
    }
    
    private function init_hooks() {
        // 核心检查钩子
        add_filter('wp_insert_post_data', [$this, 'check_duplicate_on_save'], 99, 2);
        
        // AJAX检查
        add_action('wp_ajax_tdp_check_title', [$this, 'ajax_check_title']);
        add_action('wp_ajax_nopriv_tdp_check_title', [$this, 'ajax_check_title_nopriv']);
        
        // 管理界面
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
        add_action('admin_menu', [$this, 'add_admin_menu']);
        add_action('admin_init', [$this, 'register_settings']);
        
        // 批量工具
        add_action('admin_post_tdp_bulk_check', [$this, 'handle_bulk_check']);
        
        // 短代码
        add_shortcode('tdp_title_check', [$this, 'shortcode_title_check']);
        
        // REST API
        add_action('rest_api_init', [$this, 'register_rest_routes']);
    }
    
    private function load_settings() {
        $this->similarity_threshold = get_option('tdp_similarity_threshold', 0.85);
        $this->check_exact_match = get_option('tdp_check_exact', true);
        $this->check_similar_match = get_option('tdp_check_similar', true);
        $this->force_check = get_option('tdp_force_check', false);
    }
    
    public function check_duplicate_on_save($data, $postarr) {
        // 跳过自动保存、修订等
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return $data;
        }
        
        if (wp_is_post_revision($postarr['ID'])) {
            return $data;
        }
        
        // 检查是否应该跳过
        if (isset($_POST['tdp_skip_check']) && $_POST['tdp_skip_check'] == '1') {
            return $data;
        }
        
        $post_type = $data['post_type'];
        $title = isset($data['post_title']) ? trim($data['post_title']) : '';
        $post_id = isset($postarr['ID']) ? intval($postarr['ID']) : 0;
        
        // 检查是否在允许的类型中
        $allowed_types = apply_filters('tdp_allowed_post_types', ['post', 'page']);
        if (!in_array($post_type, $allowed_types)) {
            return $data;
        }
        
        if (empty($title)) {
            return $data;
        }
        
        $errors = [];
        
        // 检查完全重复
        if ($this->check_exact_match) {
            $exact_result = $this->check_exact_duplicate($title, $post_id, $post_type);
            if ($exact_result['found']) {
                $errors[] = [
                    'type' => 'exact',
                    'message' => '发现完全相同的标题',
                    'posts' => $exact_result['posts']
                ];
            }
        }
        
        // 检查相似标题
        if ($this->check_similar_match && empty($errors)) {
            $similar_result = $this->check_similar_titles($title, $post_id, $post_type);
            if (!empty($similar_result['posts'])) {
                $errors[] = [
                    'type' => 'similar',
                    'message' => sprintf('发现相似标题(相似度%.1f%%)', $similar_result['highest_similarity']),
                    'posts' => $similar_result['posts'],
                    'similarity' => $similar_result['highest_similarity']
                ];
            }
        }
        
        // 处理错误
        if (!empty($errors)) {
            $this->handle_duplicate_errors($errors, $title, $post_id);
            
            // 如果强制检查,阻止保存
            if ($this->force_check) {
                $error_message = '无法保存:存在重复或相似标题。';
                if (defined('DOING_AJAX') && DOING_AJAX) {
                    wp_send_json_error(['message' => $error_message]);
                } else {
                    wp_die($error_message, '标题重复错误', ['back_link' => true]);
                }
            }
        }
        
        return $data;
    }
    
    private function check_exact_duplicate($title, $exclude_id = 0, $post_type = 'post') {
        global $wpdb;
        
        $query = $wpdb->prepare("
            SELECT ID, post_title, post_status, post_date
            FROM {$wpdb->posts}
            WHERE post_title = %s
            AND post_type = %s
            AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
        ", $title, $post_type);
        
        if ($exclude_id > 0) {
            $query .= $wpdb->prepare(" AND ID != %d", $exclude_id);
        }
        
        $posts = $wpdb->get_results($query);
        
        if (empty($posts)) {
            return ['found' => false, 'posts' => []];
        }
        
        $formatted_posts = [];
        foreach ($posts as $post) {
            $formatted_posts[] = [
                'id' => $post->ID,
                'title' => $post->post_title,
                'status' => $post->post_status,
                'date' => $post->post_date,
                'edit_link' => get_edit_post_link($post->ID)
            ];
        }
        
        return ['found' => true, 'posts' => $formatted_posts];
    }
    
    private function check_similar_titles($title, $exclude_id = 0, $post_type = 'post', $threshold = null) {
        if ($threshold === null) {
            $threshold = $this->similarity_threshold;
        }
        
        global $wpdb;
        
        // 获取所有同类型文章
        $query = $wpdb->prepare("
            SELECT ID, post_title, post_status, post_date
            FROM {$wpdb->posts}
            WHERE post_type = %s
            AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
        ", $post_type);
        
        if ($exclude_id > 0) {
            $query .= $wpdb->prepare(" AND ID != %d", $exclude_id);
        }
        
        $all_posts = $wpdb->get_results($query);
        
        $similar_posts = [];
        $highest_similarity = 0;
        
        foreach ($all_posts as $post) {
            $similarity = $this->calculate_similarity($title, $post->post_title);
            
            if ($similarity >= $threshold) {
                $similar_posts[] = [
                    'id' => $post->ID,
                    'title' => $post->post_title,
                    'status' => $post->post_status,
                    'date' => $post->post_date,
                    'edit_link' => get_edit_post_link($post->ID),
                    'similarity' => round($similarity * 100, 1)
                ];
                
                if ($similarity > $highest_similarity) {
                    $highest_similarity = $similarity;
                }
            }
        }
        
        // 按相似度排序
        usort($similar_posts, function($a, $b) {
            return $b['similarity'] <=> $a['similarity'];
        });
        
        return [
            'posts' => $similar_posts,
            'highest_similarity' => round($highest_similarity * 100, 1)
        ];
    }
    
    private function calculate_similarity($str1, $str2) {
        // 简化版相似度计算
        similar_text($str1, $str2, $percent);
        return $percent / 100;
    }
    
    private function handle_duplicate_errors($errors, $title, $post_id) {
        $error_html = '<div class="tdp-error-notice">';
        $error_html .= '<h3>标题重复警告</h3>';
        $error_html .= '<p>您尝试发布的标题 "<strong>' . esc_html($title) . '</strong>" 存在以下问题:</p>';
        
        foreach ($errors as $error) {
            $error_html .= '<div class="error-type ' . $error['type'] . '">';
            $error_html .= '<h4>' . $error['message'] . '</h4>';
            
            if (!empty($error['posts'])) {
                $error_html .= '<ul class="duplicate-posts">';
                foreach ($error['posts'] as $post) {
                    $status_text = get_post_status_object($post['status'])->label;
                    $similarity_text = isset($post['similarity']) ? ' (相似度: ' . $post['similarity'] . '%)' : '';
                    
                    $error_html .= sprintf(
                        '<li>%s%s - <a href="%s" target="_blank">编辑</a> | 状态: %s | 发布时间: %s</li>',
                        esc_html($post['title']),
                        $similarity_text,
                        esc_url($post['edit_link']),
                        $status_text,
                        $post['date']
                    );
                }
                $error_html .= '</ul>';
            }
            $error_html .= '</div>';
        }
        
        $error_html .= '<p>请修改标题或选择以下操作:</p>';
        $error_html .= '<form method="post" id="tdp-error-form">';
        $error_html .= '<input type="hidden" name="tdp_skip_check" value="0">';
        $error_html .= '<button type="button" class="button" onclick="tdpSkipCheck()">仍然发布</button>';
        $error_html .= ' <button type="button" class="button button-primary" onclick="location.reload()">修改标题</button>';
        $error_html .= '</form>';
        $error_html .= '</div>';
        
        $error_html .= '<script>
        function tdpSkipCheck() {
            document.getElementById("tdp-error-form").tdp_skip_check.value = "1";
            document.getElementById("publish").click();
        }
        </script>';
        
        // 保存到会话
        set_transient('tdp_last_error_' . get_current_user_id(), $error_html, 300);
        
        // 添加管理通知
        if (is_admin()) {
            add_action('admin_notices', function() use ($error_html) {
                echo '<div class="notice notice-error is-dismissible">' . $error_html . '</div>';
            });
        }
        
        // 记录日志
        error_log(sprintf(
            'TDP: Duplicate title detected. Title: "%s", Post ID: %s, Error count: %s',
            $title,
            $post_id,
            count($errors)
        ));
    }
    
    public function ajax_check_title() {
        check_ajax_referer('tdp_ajax_nonce', 'nonce');
        
        $title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
        $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
        $post_type = isset($_POST['post_type']) ? sanitize_text_field($_POST['post_type']) : 'post';
        
        if (empty($title)) {
            wp_send_json_error(['message' => '标题不能为空']);
        }
        
        $response = [
            'exact_match' => ['found' => false, 'posts' => []],
            'similar_match' => ['found' => false, 'posts' => []]
        ];
        
        // 检查完全重复
        if ($this->check_exact_match) {
            $exact_result = $this->check_exact_duplicate($title, $post_id, $post_type);
            $response['exact_match'] = $exact_result;
        }
        
        // 检查相似标题
        if ($this->check_similar_match) {
            $similar_result = $this->check_similar_titles($title, $post_id, $post_type);
            $response['similar_match'] = [
                'found' => !empty($similar_result['posts']),
                'posts' => $similar_result['posts'],
                'highest_similarity' => $similar_result['highest_similarity']
            ];
        }
        
        wp_send_json_success($response);
    }
    
    public function ajax_check_title_nopriv() {
        wp_send_json_error(['message' => '未授权访问']);
    }
    
    public function enqueue_admin_scripts($hook) {
        if (!in_array($hook, ['post.php', 'post-new.php', 'settings_page_tdp-settings'])) {
            return;
        }
        
        wp_enqueue_script('tdp-admin-js', 
            plugin_dir_url(__FILE__) . 'assets/js/admin.js', 
            ['jquery'], 
            '1.0.0', 
            true
        );
        
        wp_enqueue_style('tdp-admin-css', 
            plugin_dir_url(__FILE__) . 'assets/css/admin.css', 
            [], 
            '1.0.0'
        );
        
        wp_localize_script('tdp-admin-js', 'tdp_ajax', [
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('tdp_ajax_nonce'),
            'checking' => '检查标题中...',
            'exact_warning' => '发现完全相同的标题',
            'similar_warning' => '发现相似标题',
            'safe' => '标题可用'
        ]);
    }
    
    public function add_admin_menu() {
        add_options_page(
            '重复标题检查设置',
            '标题查重',
            'manage_options',
            'tdp-settings',
            [$this, 'display_settings_page']
        );
    }
    
    public function register_settings() {
        register_setting('tdp_settings', 'tdp_similarity_threshold', [
            'type' => 'float',
            'default' => 0.85,
            'sanitize_callback' => 'floatval'
        ]);
        
        register_setting('tdp_settings', 'tdp_check_exact', [
            'type' => 'boolean',
            'default' => true
        ]);
        
        register_setting('tdp_settings', 'tdp_check_similar', [
            'type' => 'boolean',
            'default' => true
        ]);
        
        register_setting('tdp_settings', 'tdp_force_check', [
            'type' => 'boolean',
            'default' => false
        ]);
    }
    
    public function display_settings_page() {
        ?>
        <div class="wrap">
            <h1>重复标题检查设置</h1>
            
            <form method="post" action="options.php">
                <?php settings_fields('tdp_settings'); ?>
                
                <table class="form-table">
                    <tr>
                        <th scope="row">检查完全重复</th>
                        <td>
                            <label>
                                <input type="checkbox" name="tdp_check_exact" value="1" 
                                    <?php checked(get_option('tdp_check_exact', true)); ?>>
                                启用完全重复标题检查
                            </label>
                            <p class="description">检查是否存在完全相同的标题</p>
                        </td>
                    </tr>
                    
                    <tr>
                        <th scope="row">检查相似标题</th>
                        <td>
                            <label>
                                <input type="checkbox" name="tdp_check_similar" value="1" 
                                    <?php checked(get_option('tdp_check_similar', true)); ?>>
                                启用相似标题检查
                            </label>
                            <p class="description">检查是否存在相似的标题</p>
                        </td>
                    </tr>
                    
                    <tr>
                        <th scope="row">相似度阈值</th>
                        <td>
                            <input type="range" name="tdp_similarity_threshold" 
                                   min="0.1" max="1.0" step="0.05" 
                                   value="<?php echo esc_attr(get_option('tdp_similarity_threshold', 0.85)); ?>"
                                   oninput="document.getElementById('thresholdValue').textContent = this.value">
                            <span id="thresholdValue"><?php echo get_option('tdp_similarity_threshold', 0.85); ?></span>
                            <p class="description">相似度达到此值将触发警告(0-1,值越小越严格)</p>
                        </td>
                    </tr>
                    
                    <tr>
                        <th scope="row">强制检查</th>
                        <td>
                            <label>
                                <input type="checkbox" name="tdp_force_check" value="1" 
                                    <?php checked(get_option('tdp_force_check', false)); ?>>
                                强制阻止重复标题保存
                            </label>
                            <p class="description">启用后,发现重复标题将无法保存(需修改标题)</p>
                        </td>
                    </tr>
                </table>
                
                <?php submit_button(); ?>
            </form>
            
            <div class="card" style="margin-top: 20px;">
                <h2>批量查重工具</h2>
                <p>扫描网站中所有重复标题的文章</p>
                <form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
                    <input type="hidden" name="action" value="tdp_bulk_check">
                    <?php wp_nonce_field('tdp_bulk_check_nonce'); ?>
                    <button type="submit" class="button button-primary">开始批量扫描</button>
                </form>
            </div>
        </div>
        <?php
    }
    
    public function handle_bulk_check() {
        check_admin_referer('tdp_bulk_check_nonce');
        
        // 批量检查逻辑
        $duplicates = $this->bulk_find_duplicates();
        
        set_transient('tdp_bulk_results', $duplicates, 3600);
        
        wp_redirect(admin_url('options-general.php?page=tdp-settings&tab=results'));
        exit;
    }
    
    public function shortcode_title_check($atts) {
        $atts = shortcode_atts([
            'placeholder' => '输入标题进行检查',
            'button_text' => '检查标题'
        ], $atts);
        
        ob_start();
        ?>
        <div class="tdp-frontend-checker">
            <input type="text" class="tdp-title-input" placeholder="<?php echo esc_attr($atts['placeholder']); ?>">
            <button class="tdp-check-button"><?php echo esc_html($atts['button_text']); ?></button>
            <div class="tdp-results"></div>
        </div>
        
        <script>
        jQuery(document).ready(function($) {
            $('.tdp-check-button').on('click', function() {
                var title = $(this).siblings('.tdp-title-input').val();
                var $results = $(this).siblings('.tdp-results');
                
                if (!title.trim()) {
                    $results.html('<p class="error">请输入标题</p>');
                    return;
                }
                
                $results.html('<p>检查中...</p>');
                
                $.ajax({
                    url: '<?php echo admin_url('admin-ajax.php'); ?>',
                    type: 'POST',
                    data: {
                        action: 'tdp_check_title',
                        title: title,
                        nonce: '<?php echo wp_create_nonce('tdp_ajax_nonce'); ?>'
                    },
                    success: function(response) {
                        if (response.success) {
                            var html = '<div class="tdp-result">';
                            
                            if (response.data.exact_match.found) {
                                html += '<p class="warning">发现完全相同的标题</p>';
                                html += '<ul>';
                                response.data.exact_match.posts.forEach(function(post) {
                                    html += '<li>' + post.title + ' (<a href="' + post.edit_link + '">查看</a>)</li>';
                                });
                                html += '</ul>';
                            } else if (response.data.similar_match.found) {
                                html += '<p class="warning">发现相似标题(相似度' + response.data.similar_match.highest_similarity + '%)</p>';
                                html += '<ul>';
                                response.data.similar_match.posts.forEach(function(post) {
                                    html += '<li>' + post.title + ' (相似度: ' + post.similarity + '%, <a href="' + post.edit_link + '">查看</a>)</li>';
                                });
                                html += '</ul>';
                            } else {
                                html += '<p class="success">标题可用</p>';
                            }
                            
                            html += '</div>';
                            $results.html(html);
                        }
                    }
                });
            });
        });
        </script>
        <?php
        return ob_get_clean();
    }
    
    public function register_rest_routes() {
        register_rest_route('tdp/v1', '/check', [
            'methods' => 'POST',
            'callback' => [$this, 'rest_check_title'],
            'permission_callback' => '__return_true',
            'args' => [
                'title' => [
                    'required' => true,
                    'validate_callback' => function($param) {
                        return !empty($param);
                    }
                ],
                'post_id' => [
                    'required' => false,
                    'default' => 0
                ],
                'post_type' => [
                    'required' => false,
                    'default' => 'post'
                ]
            ]
        ]);
    }
    
    public function rest_check_title($request) {
        $title = sanitize_text_field($request['title']);
        $post_id = intval($request['post_id']);
        $post_type = sanitize_text_field($request['post_type']);
        
        $result = [
            'exact_duplicates' => [],
            'similar_titles' => []
        ];
        
        // 检查完全重复
        $exact_result = $this->check_exact_duplicate($title, $post_id, $post_type);
        if ($exact_result['found']) {
            $result['exact_duplicates'] = $exact_result['posts'];
        }
        
        // 检查相似标题
        $similar_result = $this->check_similar_titles($title, $post_id, $post_type);
        if (!empty($similar_result['posts'])) {
            $result['similar_titles'] = $similar_result['posts'];
        }
        
        $result['has_duplicates'] = !empty($result['exact_duplicates']) || !empty($result['similar_titles']);
        
        return rest_ensure_response($result);
    }
    
    private function bulk_find_duplicates() {
        global $wpdb;
        
        $query = "
            SELECT post_title, COUNT(*) as count, 
                   GROUP_CONCAT(ID ORDER BY post_date DESC) as post_ids
            FROM {$wpdb->posts}
            WHERE post_type IN ('post', 'page')
            AND post_status IN ('publish', 'pending', 'draft', 'future', 'private')
            GROUP BY post_title
            HAVING count > 1
            ORDER BY count DESC, post_title
        ";
        
        $results = $wpdb->get_results($query);
        
        $duplicates = [];
        
        foreach ($results as $row) {
            $post_ids = explode(',', $row->post_ids);
            $post_details = [];
            
            foreach ($post_ids as $post_id) {
                $post = get_post($post_id);
                if ($post) {
                    $post_details[] = [
                        'id' => $post->ID,
                        'title' => $post->post_title,
                        'status' => $post->post_status,
                        'date' => $post->post_date,
                        'edit_link' => get_edit_post_link($post->ID)
                    ];
                }
            }
            
            $duplicates[] = [
                'title' => $row->post_title,
                'count' => $row->count,
                'posts' => $post_details
            ];
        }
        
        return $duplicates;
    }
}

// 初始化插件
function tdp_init() {
    return Title_Duplicate_Preventer_2026::get_instance();
}
add_action('plugins_loaded', 'tdp_init');

// 插件激活时创建数据库表
register_activation_hook(__FILE__, function() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'tdp_logs';
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE IF NOT EXISTS $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        post_id bigint(20) DEFAULT 0,
        title varchar(255) NOT NULL,
        duplicate_type varchar(20) NOT NULL,
        similarity float DEFAULT 0,
        matched_post_id bigint(20) DEFAULT 0,
        user_id bigint(20) DEFAULT 0,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        KEY post_id (post_id),
        KEY duplicate_type (duplicate_type),
        KEY created_at (created_at)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
    
    // 设置默认选项
    add_option('tdp_similarity_threshold', 0.85);
    add_option('tdp_check_exact', 1);
    add_option('tdp_check_similar', 1);
    add_option('tdp_force_check', 0);
});

七、最佳实践与建议

1. 性能优化建议

  1. 索引优化:确保posts表的post_title字段有索引
  2. 缓存结果:频繁检查的结果应该缓存
  3. 分批处理:批量操作时分批进行
  4. 异步检查:非关键检查使用异步AJAX
  5. 限制范围:只检查必要的内容类型

2. 用户体验建议

  1. 实时反馈:输入时实时检查
  2. 清晰提示:明确显示重复信息
  3. 智能建议:提供修改建议
  4. 操作选项:允许强制保存
  5. 学习模式:记录用户习惯

3. 安全注意事项

  1. SQL注入防护:使用WordPress数据库API
  2. XSS防护:输出时转义HTML
  3. 权限验证:检查用户权限
  4. 频率限制:防止滥用检查
  5. 日志记录:记录重要操作

4. 2026年技术趋势

  1. AI智能检测:使用机器学习识别重复
  2. 语义分析:理解内容而不仅是文字
  3. 多语言支持:跨语言重复检测
  4. 图像识别:检测重复的图片内容
  5. 区块链验证:内容原创性验证

八、总结

通过本文提供的完整代码解决方案,你可以根据实际需求选择合适的重复标题防止方案。建议从基础方案开始,逐步增加高级功能。关键是要在内容质量和用户体验之间找到平衡。

推荐实施步骤

  1. 从方案1开始,实现基本检查
  2. 添加AJAX实时检查(方案2)
  3. 考虑智能相似度检查(方案3)
  4. 定期运行批量检查(方案5)
  5. 根据需求选择完整插件方案

记住:技术是手段,提升内容质量才是目的。合理的重复检查可以帮助创建更好的内容,但不应过度限制创作自由。

这篇文章有用吗?

点击星号为它评分!

平均评分 0 / 5. 投票数: 0

到目前为止还没有投票!成为第一位评论此文章。

在AI里面继续讨论:

曾凤祥

曾凤祥

WordPress技术负责人
小兽WordPress凭借15年的WordPress企业网站开发经验,坚持以“为企业而生的WordPress服务”为宗旨,累计为10万多家客户提供高品质WordPress建站服务,得到了客户的一致好评。我们一直用心对待每一个客户,我们坚信:“善待客户,将会成为终身客户”。小兽WordPress能坚持多年,是因为我们一直诚信。

相关文章

如何让线上业务更上一层楼

还没有WordPress网站

还没有WordPress网站

不管你从事什么行业,WordPress都会为你提供一个专业的主题模板。在WordPress市场上有成千上万的免费主题,适合很多中小企业。

查看所有模板
已经有WordPress网站

已经有WordPress网站

小兽WordPress诚邀你一起学习WordPress,愿与各方携手升级改善您的WordPress网站,一起交流网站加速,网站优化等问题。

马上交个朋友
微信联系
chat 扫码联系
模板建站
挑选模板
网站定制
免费诊断
咨询热线
咨询热线

189-0733-7671

返回顶部