文章目录
-
- 在当今互联网时代,网站链接的完整性和可用性直接影响用户体验和搜索引擎排名。根据权威统计,一个拥有超过1000个页面的网站中,平均有5%-10%的链接可能失效或成为"死链"。对于使用WordPress构建的网站而言,随着内容不断积累和更新,死链问题会日益严重。 死链不仅导致访客无法访问目标内容,还会损害网站的专业形象,更重要的是,搜索引擎会将死链数量作为网站质量评估的指标之一,直接影响SEO排名。传统的手动检测方法耗时耗力,且难以实时发现新出现的死链问题。因此,为WordPress集成智能化的死链检测与自动修复工具显得尤为重要。 本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完善的死链检测与自动修复系统,让您的网站始终保持链接健康状态。
-
- 在开始开发之前,我们需要确保具备以下环境条件: WordPress 5.0及以上版本 PHP 7.2及以上版本(推荐PHP 7.4+) MySQL 5.6及以上版本 服务器支持cURL扩展 适当的服务器内存和CPU资源(检测过程可能消耗资源)
- 首先,我们需要创建一个独立的WordPress插件来承载我们的死链检测功能: 在WordPress的wp-content/plugins/目录下创建新文件夹smart-link-checker 在该文件夹中创建主插件文件smart-link-checker.php 添加插件基本信息: <?php /** * Plugin Name: Smart Link Checker & Fixer * Plugin URI: https://yourwebsite.com/smart-link-checker * Description: 智能化的网站死链检测与自动修复工具 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: smart-link-checker */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SLC_VERSION', '1.0.0'); define('SLC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SLC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SLC_MAX_LINKS_PER_RUN', 100); // 每次运行检测的最大链接数 define('SLC_REQUEST_TIMEOUT', 15); // 请求超时时间(秒)
- 我们需要创建数据库表来存储链接检测结果和历史记录: // 在插件激活时创建数据库表 register_activation_hook(__FILE__, 'slc_create_database_tables'); function slc_create_database_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'slc_links'; $history_table = $wpdb->prefix . 'slc_link_history'; // 主链接表 $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, url varchar(1000) NOT NULL, source_id bigint(20) NOT NULL, source_type varchar(50) NOT NULL, last_checked datetime DEFAULT NULL, status_code int(4) DEFAULT NULL, status varchar(50) DEFAULT 'pending', redirect_to varchar(1000) DEFAULT NULL, error_message text, check_count int(11) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY url_index (url(191)), KEY status_index (status), KEY source_index (source_type, source_id) ) $charset_collate;"; // 历史记录表 $sql2 = "CREATE TABLE IF NOT EXISTS $history_table ( id bigint(20) NOT NULL AUTO_INCREMENT, link_id bigint(20) NOT NULL, status_code int(4) DEFAULT NULL, response_time float DEFAULT NULL, checked_at datetime DEFAULT CURRENT_TIMESTAMP, notes text, PRIMARY KEY (id), KEY link_id_index (link_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); dbDelta($sql2); // 添加默认选项 add_option('slc_settings', array( 'check_frequency' => 'daily', 'auto_fix_redirects' => true, 'send_notifications' => true, 'notification_email' => get_option('admin_email'), 'excluded_domains' => array(), 'max_redirects' => 3 )); }
-
- 首先,我们需要从WordPress内容中提取所有链接: class SLC_Link_Extractor { /** * 从所有文章和页面中提取链接 */ public static function extract_all_links() { global $wpdb; $links = array(); // 从文章内容中提取链接 $posts = $wpdb->get_results("SELECT ID, post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post', 'page')"); foreach ($posts as $post) { $post_links = self::extract_from_content($post->post_content); foreach ($post_links as $link) { $links[] = array( 'url' => $link, 'source_id' => $post->ID, 'source_type' => 'post' ); } } // 从评论中提取链接 $comments = $wpdb->get_results("SELECT comment_ID, comment_content FROM {$wpdb->comments} WHERE comment_approved = '1'"); foreach ($comments as $comment) { $comment_links = self::extract_from_content($comment->comment_content); foreach ($comment_links as $link) { $links[] = array( 'url' => $link, 'source_id' => $comment->comment_ID, 'source_type' => 'comment' ); } } return $links; } /** * 从文本内容中提取URL */ private static function extract_from_content($content) { $pattern = '/https?://[^s'"<>]+/i'; preg_match_all($pattern, $content, $matches); $urls = array(); if (!empty($matches[0])) { foreach ($matches[0] as $url) { // 清理URL $url = rtrim($url, '.,;:!?'); $url = html_entity_decode($url); // 排除站内链接(可选) if (!self::is_internal_link($url)) { $urls[] = $url; } } } return array_unique($urls); } /** * 判断是否为站内链接 */ private static function is_internal_link($url) { $site_url = site_url(); $parsed_url = parse_url($url); $parsed_site = parse_url($site_url); if (!isset($parsed_url['host'])) { return false; } return $parsed_url['host'] === $parsed_site['host']; } }
- 接下来,我们创建链接检测的核心功能: class SLC_Link_Checker { private $timeout; private $user_agent; public function __construct() { $this->timeout = SLC_REQUEST_TIMEOUT; $this->user_agent = 'Mozilla/5.0 (compatible; SmartLinkChecker/1.0; +' . site_url() . ')'; } /** * 检测单个链接的状态 */ public function check_single_link($url) { $start_time = microtime(true); // 初始化cURL $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_USERAGENT => $this->user_agent, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, // 只获取头部信息,提高速度 )); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME); $effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); $redirect_count = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT); $error = curl_error($ch); curl_close($ch); $response_time = microtime(true) - $start_time; // 分析结果 $result = array( 'url' => $url, 'http_code' => $http_code, 'response_time' => $response_time, 'effective_url' => $effective_url, 'redirect_count' => $redirect_count, 'error' => $error, 'status' => $this->determine_status($http_code, $error) ); return $result; } /** * 根据HTTP状态码确定链接状态 */ private function determine_status($http_code, $error) { if (!empty($error)) { return 'error'; } if ($http_code >= 200 && $http_code < 300) { return 'working'; } elseif ($http_code >= 300 && $http_code < 400) { return 'redirect'; } elseif ($http_code == 404) { return 'broken'; } elseif ($http_code >= 400 && $http_code < 500) { return 'client_error'; } elseif ($http_code >= 500) { return 'server_error'; } else { return 'unknown'; } } /** * 批量检测链接 */ public function check_batch_links($links, $batch_size = 10) { $results = array(); $batches = array_chunk($links, $batch_size); foreach ($batches as $batch) { $batch_results = $this->check_batch_concurrently($batch); $results = array_merge($results, $batch_results); // 避免对目标服务器造成过大压力 sleep(1); } return $results; } /** * 并发检测(使用curl_multi) */ private function check_batch_concurrently($links) { $mh = curl_multi_init(); $channels = array(); foreach ($links as $i => $link) { $ch = curl_init(); curl_setopt_array($ch, array( CURLOPT_URL => $link['url'], CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_USERAGENT => $this->user_agent, CURLOPT_HEADER => true, CURLOPT_NOBODY => true, )); curl_multi_add_handle($mh, $ch); $channels[$i] = array( 'channel' => $ch, 'link' => $link ); } // 执行并发请求 $active = null; do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } // 收集结果 $results = array(); foreach ($channels as $i => $channel) { $ch = $channel['channel']; $link = $channel['link']; $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); $error = curl_error($ch); $results[] = array_merge($link, array( 'http_code' => $http_code, 'effective_url' => $effective_url, 'error' => $error, 'status' => $this->determine_status($http_code, $error) )); curl_multi_remove_handle($mh, $ch); curl_close($ch); } curl_multi_close($mh); return $results; } }
- 检测到死链后,我们需要尝试自动修复: class SLC_Link_Fixer { /** * 尝试自动修复死链 */ public static function try_auto_fix($link_data) { $url = $link_data['url']; $source_id = $link_data['source_id']; $source_type = $link_data['source_type']; // 检查是否为404错误 if ($link_data['http_code'] != 404) { return false; } // 尝试常见的修复策略 $fixed_url = self::try_fix_strategies($url); if ($fixed_url && $fixed_url !== $url) { // 验证修复后的链接是否有效 $checker = new SLC_Link_Checker(); $check_result = $checker->check_single_link($fixed_url); if ($check_result['status'] === 'working') { // 更新数据库中的链接 self::update_link_in_database($link_data['id'], $fixed_url, $check_result['http_code']); // 更新内容中的链接 self::update_link_in_content($source_id, $source_type, $url, $fixed_url); return array( 'original_url' => $url, 'fixed_url' => $fixed_url, 'http_code' => $check_result['http_code'], 'success' => true ); } } return false; } /** * 尝试多种修复策略 */ private static function try_fix_strategies($url) { $strategies = array( 'remove_www' => function($url) { return preg_replace('/^(https?://)www./i', '$1', $url); }, 'add_www' => function($url) { $parsed = parse_url($url); if (!preg_match('/^www./i', $parsed['host'])) { $parsed['host'] = 'www.' . $parsed['host']; return self::build_url($parsed); } return $url; }, 'force_https' => function($url) { return preg_replace('/^http:/i', 'https:', $url); }, 'force_http' => function($url) { return preg_replace('/^https:/i', 'http:', $url); }, 'remove_trailing_slash' => function($url) { return rtrim($url, '/'); }, 'add_trailing_slash' => function($url) { $parsed = parse_url($url); if (!isset($parsed['path']) || substr($parsed['path'], -1) !== '/') { $parsed['path'] = ($parsed['path'] ?? '') . '/'; return self::build_url($parsed); } return $url; } ); foreach ($strategies as $strategy) { $fixed_url = $strategy($url); if ($fixed_url !== $url) { // 简单验证URL格式 if (filter_var($fixed_url, FILTER_VALIDATE_URL)) { return $fixed_url; } } } return false; } /** * 从解析的URL部分重建完整URL */ private static function build_url($parts) { $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; $host = $parts['host'] ?? ''; $port = isset($parts['port']) ? ':' . $parts['port'] : ''; $path = $parts['path'] ?? ''; $query = isset($parts['query']) ? '?' . $parts['query'] : ''; $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; return $scheme . $host . $port . $path . $query . $fragment; } /** * 更新数据库中的链接记录 */ private static function update_link_in_database($link_id, $new_url, $http_code) { global $wpdb; $table_name = $wpdb->prefix . 'slc_links'; $wpdb->update( $table_name, array( 'url' => $new_url, 'status_code' => $http_code, 'status' => 'fixed', 'last_checked' => current_time('mysql') ), array('id' => $link_id), array('%s', '%d', '%s', '%s'), array('%d') ); // 记录修复历史 $history_table = $wpdb->prefix . 'slc_link_history'; $wpdb->insert( $history_table, array( 'link_id' => $link_id, 'status_code' => $http_code, 'checked_at' => current_time('mysql'), 'notes' => '自动修复: ' . $new_url ) ); } /** * 更新内容中的链接 */ private static function update_link_in_content($source_id, $source_type, $old_url, $new_url) { if ($source_type === 'post') { $post = get_post($source_id); if ($post) { $new_content = str_replace($old_url, $new_url, $post->post_content); wp_update_post(array( 'ID' => $source_id, 'post_content' => $new_content )); } } elseif ($source_type === 'comment') { $comment = get_comment($source_id); if ($comment) { $new_content = str_replace($old_url, $new_url, $comment->comment_content); wp_update_comment(array( 'comment_ID' => $source_id, 'comment_content' => $new_content )); } } } /** * 查找可能的替代链接(通过搜索引擎API) */ public static function find_alternative_url($url, $title = '') { // 这里可以集成搜索引擎API来查找替代链接 // 由于API需要密钥,这里仅提供框架代码 $parsed = parse_url($url); $domain = $parsed['host'] ?? ''; $path = $parsed['path'] ?? ''; // 尝试通过Wayback Machine查找存档 $wayback_url = "https://archive.org/wayback/available?url=" . urlencode($url); $response = wp_remote_get($wayback_url, array('timeout' => 10)); if (!is_wp_error($response)) { $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['archived_snapshots']['closest']['url'])) { return $data['archived_snapshots']['closest']['url']; } } return false; } } ## 第三章:后台管理与用户界面 ### 3.1 创建管理菜单和页面 class SLC_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('wp_ajax_slc_start_scan', array($this, 'ajax_start_scan')); add_action('wp_ajax_slc_get_stats', array($this, 'ajax_get_stats')); add_action('wp_ajax_slc_fix_link', array($this, 'ajax_fix_link')); } /** * 添加管理菜单 */ public function add_admin_menu() { add_menu_page( '智能死链检测', '死链检测', 'manage_options', 'smart-link-checker', array($this, 'render_main_page'), 'dashicons-admin-links', 30 ); add_submenu_page( 'smart-link-checker', '检测设置', '设置', 'manage_options', 'slc-settings', array($this, 'render_settings_page') ); add_submenu_page( 'smart-link-checker', '检测报告', '报告', 'manage_options', 'slc-reports', array($this, 'render_reports_page') ); } /** * 渲染主页面 */ public function render_main_page() { ?> <div class="wrap slc-admin"> <h1>智能死链检测与修复</h1> <div class="slc-dashboard"> <div class="slc-stats-cards"> <div class="card"> <h3>总链接数</h3> <p class="stat-number" id="total-links">0</p> </div> <div class="card"> <h3>正常链接</h3> <p class="stat-number" id="working-links">0</p> </div> <div class="card"> <h3>死链数</h3> <p class="stat-number" id="broken-links">0</p> </div> <div class="card"> <h3>已修复</h3> <p class="stat-number" id="fixed-links">0</p> </div> </div> <div class="slc-controls"> <button id="start-scan" class="button button-primary button-large"> <span class="dashicons dashicons-search"></span> 开始检测 </button> <button id="stop-scan" class="button button-secondary button-large" disabled> <span class="dashicons dashicons-controls-pause"></span> 停止检测 </button> <button id="export-report" class="button button-secondary"> <span class="dashicons dashicons-download"></span> 导出报告 </button> </div> <div class="slc-progress-container" style="display: none;"> <h3>检测进度</h3> <div class="progress-bar"> <div class="progress-fill" id="scan-progress" style="width: 0%"></div> </div> <p class="progress-text" id="progress-text">准备开始...</p> </div> <div class="slc-results"> <h2>检测结果</h2> <div class="tablenav top"> <div class="alignleft actions"> <select id="status-filter"> <option value="all">所有状态</option> <option value="broken">死链</option> <option value="working">正常</option> <option value="redirect">重定向</option> <option value="error">错误</option> </select> <button id="apply-filter" class="button">筛选</button> </div> <div class="tablenav-pages"> <span class="displaying-num" id="displaying-num">0个项目</span> <div class="pagination"> <button class="button" id="prev-page" disabled>上一页</button> <span id="current-page">1</span> / <span id="total-pages">1</span> <button class="button" id="next-page" disabled>下一页</button> </div> </div> </div> <table class="wp-list-table widefat fixed striped" id="links-table"> <thead> <tr> <th width="5%">ID</th> <th width="30%">URL</th> <th width="10%">状态码</th> <th width="15%">状态</th> <th width="20%">来源</th> <th width="15%">最后检测</th> <th width="15%">操作</th> </tr> </thead> <tbody id="links-tbody"> <!-- 动态加载数据 --> </tbody> </table> </div> </div> </div> <?php } /** * 渲染设置页面 */ public function render_settings_page() { $settings = get_option('slc_settings', array()); ?> <div class="wrap"> <h1>死链检测设置</h1> <form method="post" action="options.php"> <?php settings_fields('slc_settings_group'); ?> <table class="form-table"> <tr> <th scope="row">检测频率</th> <td> <select name="slc_settings[check_frequency]"> <option value="hourly" <?php selected($settings['check_frequency'], 'hourly'); ?>>每小时</option> <option value="twicedaily" <?php selected($settings['check_frequency'], 'twicedaily'); ?>>每天两次</option> <option value="daily" <?php selected($settings['check_frequency'], 'daily'); ?>>每天</option> <option value="weekly" <?php selected($settings['check_frequency'], 'weekly'); ?>>每周</option> <option value="monthly" <?php selected($settings['check_frequency'], 'monthly'); ?>>每月</option> </select> <p class="description">自动检测死链的频率</p> </td> </tr> <tr> <th scope="row">自动修复</th> <td> <label> <input type="checkbox" name="slc_settings[auto_fix_redirects]" value="1" <?php checked($settings['auto_fix_redirects'], true); ?>> 自动尝试修复重定向和简单错误 </label> </td> </tr> <tr> <th scope="row">邮件通知</th> <td> <label> <input type="checkbox" name="slc_settings[send_notifications]" value="1" <?php checked($settings['send_notifications'], true); ?>> 检测到死链时发送邮件通知 </label> <p class="description"> <input type="email" name="slc_settings[notification_email]" value="<?php echo esc_attr($settings['notification_email']); ?>" placeholder="通知邮箱地址"> </p> </td> </tr> <tr> <th scope="row">排除域名</th> <td> <textarea name="slc_settings[excluded_domains]" rows="5" cols="50" placeholder="每行一个域名,例如:example.com"><?php echo esc_textarea(implode("n", $settings['excluded_domains'] ?? array())); ?></textarea> <p class="description">不检测这些域名的链接</p> </td> </tr> <tr> <th scope="row">最大重定向次数</th> <td> <input type="number" name="slc_settings[max_redirects]" value="<?php echo esc_attr($settings['max_redirects']); ?>" min="1" max="10"> <p class="description">检测时允许的最大重定向次数</p> </td> </tr> <tr> <th scope="row">请求超时时间</th> <td> <input type="number" name="slc_settings[request_timeout]" value="<?php echo esc_attr($settings['request_timeout'] ?? SLC_REQUEST_TIMEOUT); ?>" min="5" max="60"> <span>秒</span> <p class="description">检测单个链接的最大等待时间</p> </td> </tr> </table> <?php submit_button(); ?> </form> </div> <?php } /** * 注册设置 */ public function register_settings() { register_setting('slc_settings_group', 'slc_settings', array( 'sanitize_callback' => array($this, 'sanitize_settings') )); } /** * 清理设置数据 */ public function sanitize_settings($input) { $sanitized = array(); $sanitized['check_frequency'] = sanitize_text_field($input['check_frequency']); $sanitized['auto_fix_redirects'] = isset($input['auto_fix_redirects']); $sanitized['send_notifications'] = isset($input['send_notifications']); $sanitized['notification_email'] = sanitize_email($input['notification_email']); // 处理排除域名 $excluded_domains = explode("n", $input['excluded_domains']); $sanitized['excluded_domains'] = array_map('trim', $excluded_domains); $sanitized['excluded_domains'] = array_filter($sanitized['excluded_domains']); $sanitized['max_redirects'] = absint($input['max_redirects']); if ($sanitized['max_redirects'] < 1 || $sanitized['max_redirects'] > 10) { $sanitized['max_redirects'] = 3; } $sanitized['request_timeout'] = absint($input['request_timeout']); if ($sanitized['request_timeout'] < 5 || $sanitized['request_timeout'] > 60) { $sanitized['request_timeout'] = SLC_REQUEST_TIMEOUT; } return $sanitized; } } ### 3.2 AJAX处理与前端交互 // 继续SLC_Admin类中的方法 /** * AJAX开始扫描 */ public function ajax_start_scan() { check_ajax_referer('slc_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('权限不足'); } // 获取所有链接 $links = SLC_Link_Extractor::extract_all_links(); // 保存到数据库 $this->save_links_to_db($links); // 开始后台处理 wp_schedule_single_event(time() + 1, 'slc_process_batch'); wp_send_json_success(array( 'message' => '扫描已开始', 'total_links' => count($links) )); } /** * 保存链接到数据库 */ private function save_links_to_db($links) { global $wpdb; $table_name = $wpdb->prefix . 'slc_links'; // 清空旧数据 $wpdb->query("TRUNCATE TABLE $table_name"); // 批量插入新数据 $values = array(); $placeholders = array(); foreach ($links as $link) { array_push( $values, $link['url'], $link['source_id'], $link['source_type'] ); $placeholders[] = "(%s, %d, %s, 'pending')"; } if (!empty($values)) { $query = "INSERT INTO $table_name (url, source_id, source_type, status) VALUES "; $query .= implode(', ', $placeholders); $wpdb->query($wpdb->prepare($query, $values)); } } /** * AJAX获取统计信息 */ public function ajax_get_stats() { global $wpdb; $table_name = $wpdb->prefix . 'slc_links'; $stats = array( 'total' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name"), 'working' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'working'"), 'broken' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'broken'"), 'fixed' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'fixed'"), 'pending' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'pending'") ); wp_send_json_success($stats); } /** * AJAX修复链接 */ public function ajax_fix_link() { check_ajax_referer('slc_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('权限不足'); } $link_id = intval($_POST['link_id']); $fix_type = sanitize_text_field($_POST['fix_type']); global $wpdb; $table_name = $wpdb->prefix . 'slc_links'; $link = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $link_id )); if (!$link) { wp_send_json_error('链接不存在'); } $result = false; switch ($fix_type) { case 'auto': $result = SLC_Link_Fixer::try_auto_fix((array)$link); break; case 'manual': $new_url = sanitize_text_field($_POST['new_url']); if ($new_url && filter_var($new_url, FILTER_VALIDATE_URL)) { $result = SLC_Link_Fixer::update_link_in_content( $link->source_id, $link->source_type, $link->url, $new_url ); } break; case 'remove': // 从内容中移除链接 $result = SLC_Link_Fixer::update_link_in_content( $link->source_id, $link->source_type, $link->url, '' ); break; } if ($result) { wp_send_json_success(array( 'message' => '修复成功', 'link_id' => $link_id )); } else { wp_send_json_error('修复失败'); } } /** * 加载管理脚本和样式 */ public function enqueue_admin_scripts($hook) {
在当今互联网时代,网站链接的完整性和可用性直接影响用户体验和搜索引擎排名。根据权威统计,一个拥有超过1000个页面的网站中,平均有5%-10%的链接可能失效或成为"死链"。对于使用WordPress构建的网站而言,随着内容不断积累和更新,死链问题会日益严重。
死链不仅导致访客无法访问目标内容,还会损害网站的专业形象,更重要的是,搜索引擎会将死链数量作为网站质量评估的指标之一,直接影响SEO排名。传统的手动检测方法耗时耗力,且难以实时发现新出现的死链问题。因此,为WordPress集成智能化的死链检测与自动修复工具显得尤为重要。
本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完善的死链检测与自动修复系统,让您的网站始终保持链接健康状态。
在开始开发之前,我们需要确保具备以下环境条件:
- WordPress 5.0及以上版本
- PHP 7.2及以上版本(推荐PHP 7.4+)
- MySQL 5.6及以上版本
- 服务器支持cURL扩展
- 适当的服务器内存和CPU资源(检测过程可能消耗资源)
首先,我们需要创建一个独立的WordPress插件来承载我们的死链检测功能:
- 在WordPress的
wp-content/plugins/目录下创建新文件夹smart-link-checker - 在该文件夹中创建主插件文件
smart-link-checker.php - 添加插件基本信息:
<?php
/**
* Plugin Name: Smart Link Checker & Fixer
* Plugin URI: https://yourwebsite.com/smart-link-checker
* Description: 智能化的网站死链检测与自动修复工具
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
* Text Domain: smart-link-checker
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('SLC_VERSION', '1.0.0');
define('SLC_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('SLC_PLUGIN_URL', plugin_dir_url(__FILE__));
define('SLC_MAX_LINKS_PER_RUN', 100); // 每次运行检测的最大链接数
define('SLC_REQUEST_TIMEOUT', 15); // 请求超时时间(秒)
我们需要创建数据库表来存储链接检测结果和历史记录:
// 在插件激活时创建数据库表
register_activation_hook(__FILE__, 'slc_create_database_tables');
function slc_create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'slc_links';
$history_table = $wpdb->prefix . 'slc_link_history';
// 主链接表
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
url varchar(1000) NOT NULL,
source_id bigint(20) NOT NULL,
source_type varchar(50) NOT NULL,
last_checked datetime DEFAULT NULL,
status_code int(4) DEFAULT NULL,
status varchar(50) DEFAULT 'pending',
redirect_to varchar(1000) DEFAULT NULL,
error_message text,
check_count int(11) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY url_index (url(191)),
KEY status_index (status),
KEY source_index (source_type, source_id)
) $charset_collate;";
// 历史记录表
$sql2 = "CREATE TABLE IF NOT EXISTS $history_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
link_id bigint(20) NOT NULL,
status_code int(4) DEFAULT NULL,
response_time float DEFAULT NULL,
checked_at datetime DEFAULT CURRENT_TIMESTAMP,
notes text,
PRIMARY KEY (id),
KEY link_id_index (link_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
dbDelta($sql2);
// 添加默认选项
add_option('slc_settings', array(
'check_frequency' => 'daily',
'auto_fix_redirects' => true,
'send_notifications' => true,
'notification_email' => get_option('admin_email'),
'excluded_domains' => array(),
'max_redirects' => 3
));
}
首先,我们需要从WordPress内容中提取所有链接:
class SLC_Link_Extractor {
/**
* 从所有文章和页面中提取链接
*/
public static function extract_all_links() {
global $wpdb;
$links = array();
// 从文章内容中提取链接
$posts = $wpdb->get_results("SELECT ID, post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post', 'page')");
foreach ($posts as $post) {
$post_links = self::extract_from_content($post->post_content);
foreach ($post_links as $link) {
$links[] = array(
'url' => $link,
'source_id' => $post->ID,
'source_type' => 'post'
);
}
}
// 从评论中提取链接
$comments = $wpdb->get_results("SELECT comment_ID, comment_content FROM {$wpdb->comments} WHERE comment_approved = '1'");
foreach ($comments as $comment) {
$comment_links = self::extract_from_content($comment->comment_content);
foreach ($comment_links as $link) {
$links[] = array(
'url' => $link,
'source_id' => $comment->comment_ID,
'source_type' => 'comment'
);
}
}
return $links;
}
/**
* 从文本内容中提取URL
*/
private static function extract_from_content($content) {
$pattern = '/https?://[^s'"<>]+/i';
preg_match_all($pattern, $content, $matches);
$urls = array();
if (!empty($matches[0])) {
foreach ($matches[0] as $url) {
// 清理URL
$url = rtrim($url, '.,;:!?');
$url = html_entity_decode($url);
// 排除站内链接(可选)
if (!self::is_internal_link($url)) {
$urls[] = $url;
}
}
}
return array_unique($urls);
}
/**
* 判断是否为站内链接
*/
private static function is_internal_link($url) {
$site_url = site_url();
$parsed_url = parse_url($url);
$parsed_site = parse_url($site_url);
if (!isset($parsed_url['host'])) {
return false;
}
return $parsed_url['host'] === $parsed_site['host'];
}
}
接下来,我们创建链接检测的核心功能:
class SLC_Link_Checker {
private $timeout;
private $user_agent;
public function __construct() {
$this->timeout = SLC_REQUEST_TIMEOUT;
$this->user_agent = 'Mozilla/5.0 (compatible; SmartLinkChecker/1.0; +' . site_url() . ')';
}
/**
* 检测单个链接的状态
*/
public function check_single_link($url) {
$start_time = microtime(true);
// 初始化cURL
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => $this->user_agent,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true, // 只获取头部信息,提高速度
));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
$effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$redirect_count = curl_getinfo($ch, CURLINFO_REDIRECT_COUNT);
$error = curl_error($ch);
curl_close($ch);
$response_time = microtime(true) - $start_time;
// 分析结果
$result = array(
'url' => $url,
'http_code' => $http_code,
'response_time' => $response_time,
'effective_url' => $effective_url,
'redirect_count' => $redirect_count,
'error' => $error,
'status' => $this->determine_status($http_code, $error)
);
return $result;
}
/**
* 根据HTTP状态码确定链接状态
*/
private function determine_status($http_code, $error) {
if (!empty($error)) {
return 'error';
}
if ($http_code >= 200 && $http_code < 300) {
return 'working';
} elseif ($http_code >= 300 && $http_code < 400) {
return 'redirect';
} elseif ($http_code == 404) {
return 'broken';
} elseif ($http_code >= 400 && $http_code < 500) {
return 'client_error';
} elseif ($http_code >= 500) {
return 'server_error';
} else {
return 'unknown';
}
}
/**
* 批量检测链接
*/
public function check_batch_links($links, $batch_size = 10) {
$results = array();
$batches = array_chunk($links, $batch_size);
foreach ($batches as $batch) {
$batch_results = $this->check_batch_concurrently($batch);
$results = array_merge($results, $batch_results);
// 避免对目标服务器造成过大压力
sleep(1);
}
return $results;
}
/**
* 并发检测(使用curl_multi)
*/
private function check_batch_concurrently($links) {
$mh = curl_multi_init();
$channels = array();
foreach ($links as $i => $link) {
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $link['url'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_USERAGENT => $this->user_agent,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
));
curl_multi_add_handle($mh, $ch);
$channels[$i] = array(
'channel' => $ch,
'link' => $link
);
}
// 执行并发请求
$active = null;
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
// 收集结果
$results = array();
foreach ($channels as $i => $channel) {
$ch = $channel['channel'];
$link = $channel['link'];
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$effective_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$error = curl_error($ch);
$results[] = array_merge($link, array(
'http_code' => $http_code,
'effective_url' => $effective_url,
'error' => $error,
'status' => $this->determine_status($http_code, $error)
));
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
}
检测到死链后,我们需要尝试自动修复:
class SLC_Link_Fixer {
/**
* 尝试自动修复死链
*/
public static function try_auto_fix($link_data) {
$url = $link_data['url'];
$source_id = $link_data['source_id'];
$source_type = $link_data['source_type'];
// 检查是否为404错误
if ($link_data['http_code'] != 404) {
return false;
}
// 尝试常见的修复策略
$fixed_url = self::try_fix_strategies($url);
if ($fixed_url && $fixed_url !== $url) {
// 验证修复后的链接是否有效
$checker = new SLC_Link_Checker();
$check_result = $checker->check_single_link($fixed_url);
if ($check_result['status'] === 'working') {
// 更新数据库中的链接
self::update_link_in_database($link_data['id'], $fixed_url, $check_result['http_code']);
// 更新内容中的链接
self::update_link_in_content($source_id, $source_type, $url, $fixed_url);
return array(
'original_url' => $url,
'fixed_url' => $fixed_url,
'http_code' => $check_result['http_code'],
'success' => true
);
}
}
return false;
}
/**
* 尝试多种修复策略
*/
private static function try_fix_strategies($url) {
$strategies = array(
'remove_www' => function($url) {
return preg_replace('/^(https?://)www./i', '$1', $url);
},
'add_www' => function($url) {
$parsed = parse_url($url);
if (!preg_match('/^www./i', $parsed['host'])) {
$parsed['host'] = 'www.' . $parsed['host'];
return self::build_url($parsed);
}
return $url;
},
'force_https' => function($url) {
return preg_replace('/^http:/i', 'https:', $url);
},
'force_http' => function($url) {
return preg_replace('/^https:/i', 'http:', $url);
},
'remove_trailing_slash' => function($url) {
return rtrim($url, '/');
},
'add_trailing_slash' => function($url) {
$parsed = parse_url($url);
if (!isset($parsed['path']) || substr($parsed['path'], -1) !== '/') {
$parsed['path'] = ($parsed['path'] ?? '') . '/';
return self::build_url($parsed);
}
return $url;
}
);
foreach ($strategies as $strategy) {
$fixed_url = $strategy($url);
if ($fixed_url !== $url) {
// 简单验证URL格式
if (filter_var($fixed_url, FILTER_VALIDATE_URL)) {
return $fixed_url;
}
}
}
return false;
}
/**
* 从解析的URL部分重建完整URL
*/
private static function build_url($parts) {
$scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : '';
$host = $parts['host'] ?? '';
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$path = $parts['path'] ?? '';
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
return $scheme . $host . $port . $path . $query . $fragment;
}
/**
* 更新数据库中的链接记录
*/
private static function update_link_in_database($link_id, $new_url, $http_code) {
global $wpdb;
$table_name = $wpdb->prefix . 'slc_links';
$wpdb->update(
$table_name,
array(
'url' => $new_url,
'status_code' => $http_code,
'status' => 'fixed',
'last_checked' => current_time('mysql')
),
array('id' => $link_id),
array('%s', '%d', '%s', '%s'),
array('%d')
);
// 记录修复历史
$history_table = $wpdb->prefix . 'slc_link_history';
$wpdb->insert(
$history_table,
array(
'link_id' => $link_id,
'status_code' => $http_code,
'checked_at' => current_time('mysql'),
'notes' => '自动修复: ' . $new_url
)
);
}
/**
* 更新内容中的链接
*/
private static function update_link_in_content($source_id, $source_type, $old_url, $new_url) {
if ($source_type === 'post') {
$post = get_post($source_id);
if ($post) {
$new_content = str_replace($old_url, $new_url, $post->post_content);
wp_update_post(array(
'ID' => $source_id,
'post_content' => $new_content
));
}
} elseif ($source_type === 'comment') {
$comment = get_comment($source_id);
if ($comment) {
$new_content = str_replace($old_url, $new_url, $comment->comment_content);
wp_update_comment(array(
'comment_ID' => $source_id,
'comment_content' => $new_content
));
}
}
}
/**
* 查找可能的替代链接(通过搜索引擎API)
*/
public static function find_alternative_url($url, $title = '') {
// 这里可以集成搜索引擎API来查找替代链接
// 由于API需要密钥,这里仅提供框架代码
$parsed = parse_url($url);
$domain = $parsed['host'] ?? '';
$path = $parsed['path'] ?? '';
// 尝试通过Wayback Machine查找存档
$wayback_url = "https://archive.org/wayback/available?url=" . urlencode($url);
$response = wp_remote_get($wayback_url, array('timeout' => 10));
if (!is_wp_error($response)) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['archived_snapshots']['closest']['url'])) {
return $data['archived_snapshots']['closest']['url'];
}
}
return false;
}
}
## 第三章:后台管理与用户界面
### 3.1 创建管理菜单和页面
class SLC_Admin {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
add_action('wp_ajax_slc_start_scan', array($this, 'ajax_start_scan'));
add_action('wp_ajax_slc_get_stats', array($this, 'ajax_get_stats'));
add_action('wp_ajax_slc_fix_link', array($this, 'ajax_fix_link'));
}
/**
* 添加管理菜单
*/
public function add_admin_menu() {
add_menu_page(
'智能死链检测',
'死链检测',
'manage_options',
'smart-link-checker',
array($this, 'render_main_page'),
'dashicons-admin-links',
30
);
add_submenu_page(
'smart-link-checker',
'检测设置',
'设置',
'manage_options',
'slc-settings',
array($this, 'render_settings_page')
);
add_submenu_page(
'smart-link-checker',
'检测报告',
'报告',
'manage_options',
'slc-reports',
array($this, 'render_reports_page')
);
}
/**
* 渲染主页面
*/
public function render_main_page() {
?>
<div class="wrap slc-admin">
<h1>智能死链检测与修复</h1>
<div class="slc-dashboard">
<div class="slc-stats-cards">
<div class="card">
<h3>总链接数</h3>
<p class="stat-number" id="total-links">0</p>
</div>
<div class="card">
<h3>正常链接</h3>
<p class="stat-number" id="working-links">0</p>
</div>
<div class="card">
<h3>死链数</h3>
<p class="stat-number" id="broken-links">0</p>
</div>
<div class="card">
<h3>已修复</h3>
<p class="stat-number" id="fixed-links">0</p>
</div>
</div>
<div class="slc-controls">
<button id="start-scan" class="button button-primary button-large">
<span class="dashicons dashicons-search"></span>
开始检测
</button>
<button id="stop-scan" class="button button-secondary button-large" disabled>
<span class="dashicons dashicons-controls-pause"></span>
停止检测
</button>
<button id="export-report" class="button button-secondary">
<span class="dashicons dashicons-download"></span>
导出报告
</button>
</div>
<div class="slc-progress-container" style="display: none;">
<h3>检测进度</h3>
<div class="progress-bar">
<div class="progress-fill" id="scan-progress" style="width: 0%"></div>
</div>
<p class="progress-text" id="progress-text">准备开始...</p>
</div>
<div class="slc-results">
<h2>检测结果</h2>
<div class="tablenav top">
<div class="alignleft actions">
<select id="status-filter">
<option value="all">所有状态</option>
<option value="broken">死链</option>
<option value="working">正常</option>
<option value="redirect">重定向</option>
<option value="error">错误</option>
</select>
<button id="apply-filter" class="button">筛选</button>
</div>
<div class="tablenav-pages">
<span class="displaying-num" id="displaying-num">0个项目</span>
<div class="pagination">
<button class="button" id="prev-page" disabled>上一页</button>
<span id="current-page">1</span> / <span id="total-pages">1</span>
<button class="button" id="next-page" disabled>下一页</button>
</div>
</div>
</div>
<table class="wp-list-table widefat fixed striped" id="links-table">
<thead>
<tr>
<th width="5%">ID</th>
<th width="30%">URL</th>
<th width="10%">状态码</th>
<th width="15%">状态</th>
<th width="20%">来源</th>
<th width="15%">最后检测</th>
<th width="15%">操作</th>
</tr>
</thead>
<tbody id="links-tbody">
<!-- 动态加载数据 -->
</tbody>
</table>
</div>
</div>
</div>
<?php
}
/**
* 渲染设置页面
*/
public function render_settings_page() {
$settings = get_option('slc_settings', array());
?>
<div class="wrap">
<h1>死链检测设置</h1>
<form method="post" action="options.php">
<?php settings_fields('slc_settings_group'); ?>
<table class="form-table">
<tr>
<th scope="row">检测频率</th>
<td>
<select name="slc_settings[check_frequency]">
<option value="hourly" <?php selected($settings['check_frequency'], 'hourly'); ?>>每小时</option>
<option value="twicedaily" <?php selected($settings['check_frequency'], 'twicedaily'); ?>>每天两次</option>
<option value="daily" <?php selected($settings['check_frequency'], 'daily'); ?>>每天</option>
<option value="weekly" <?php selected($settings['check_frequency'], 'weekly'); ?>>每周</option>
<option value="monthly" <?php selected($settings['check_frequency'], 'monthly'); ?>>每月</option>
</select>
<p class="description">自动检测死链的频率</p>
</td>
</tr>
<tr>
<th scope="row">自动修复</th>
<td>
<label>
<input type="checkbox" name="slc_settings[auto_fix_redirects]" value="1"
<?php checked($settings['auto_fix_redirects'], true); ?>>
自动尝试修复重定向和简单错误
</label>
</td>
</tr>
<tr>
<th scope="row">邮件通知</th>
<td>
<label>
<input type="checkbox" name="slc_settings[send_notifications]" value="1"
<?php checked($settings['send_notifications'], true); ?>>
检测到死链时发送邮件通知
</label>
<p class="description">
<input type="email" name="slc_settings[notification_email]"
value="<?php echo esc_attr($settings['notification_email']); ?>"
placeholder="通知邮箱地址">
</p>
</td>
</tr>
<tr>
<th scope="row">排除域名</th>
<td>
<textarea name="slc_settings[excluded_domains]" rows="5" cols="50"
placeholder="每行一个域名,例如:example.com"><?php
echo esc_textarea(implode("n", $settings['excluded_domains'] ?? array()));
?></textarea>
<p class="description">不检测这些域名的链接</p>
</td>
</tr>
<tr>
<th scope="row">最大重定向次数</th>
<td>
<input type="number" name="slc_settings[max_redirects]"
value="<?php echo esc_attr($settings['max_redirects']); ?>" min="1" max="10">
<p class="description">检测时允许的最大重定向次数</p>
</td>
</tr>
<tr>
<th scope="row">请求超时时间</th>
<td>
<input type="number" name="slc_settings[request_timeout]"
value="<?php echo esc_attr($settings['request_timeout'] ?? SLC_REQUEST_TIMEOUT); ?>" min="5" max="60">
<span>秒</span>
<p class="description">检测单个链接的最大等待时间</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
/**
* 注册设置
*/
public function register_settings() {
register_setting('slc_settings_group', 'slc_settings', array(
'sanitize_callback' => array($this, 'sanitize_settings')
));
}
/**
* 清理设置数据
*/
public function sanitize_settings($input) {
$sanitized = array();
$sanitized['check_frequency'] = sanitize_text_field($input['check_frequency']);
$sanitized['auto_fix_redirects'] = isset($input['auto_fix_redirects']);
$sanitized['send_notifications'] = isset($input['send_notifications']);
$sanitized['notification_email'] = sanitize_email($input['notification_email']);
// 处理排除域名
$excluded_domains = explode("n", $input['excluded_domains']);
$sanitized['excluded_domains'] = array_map('trim', $excluded_domains);
$sanitized['excluded_domains'] = array_filter($sanitized['excluded_domains']);
$sanitized['max_redirects'] = absint($input['max_redirects']);
if ($sanitized['max_redirects'] < 1 || $sanitized['max_redirects'] > 10) {
$sanitized['max_redirects'] = 3;
}
$sanitized['request_timeout'] = absint($input['request_timeout']);
if ($sanitized['request_timeout'] < 5 || $sanitized['request_timeout'] > 60) {
$sanitized['request_timeout'] = SLC_REQUEST_TIMEOUT;
}
return $sanitized;
}
}
### 3.2 AJAX处理与前端交互
// 继续SLC_Admin类中的方法
/**
* AJAX开始扫描
*/
public function ajax_start_scan() {
check_ajax_referer('slc_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('权限不足');
}
// 获取所有链接
$links = SLC_Link_Extractor::extract_all_links();
// 保存到数据库
$this->save_links_to_db($links);
// 开始后台处理
wp_schedule_single_event(time() + 1, 'slc_process_batch');
wp_send_json_success(array(
'message' => '扫描已开始',
'total_links' => count($links)
));
}
/**
* 保存链接到数据库
*/
private function save_links_to_db($links) {
global $wpdb;
$table_name = $wpdb->prefix . 'slc_links';
// 清空旧数据
$wpdb->query("TRUNCATE TABLE $table_name");
// 批量插入新数据
$values = array();
$placeholders = array();
foreach ($links as $link) {
array_push(
$values,
$link['url'],
$link['source_id'],
$link['source_type']
);
$placeholders[] = "(%s, %d, %s, 'pending')";
}
if (!empty($values)) {
$query = "INSERT INTO $table_name (url, source_id, source_type, status) VALUES ";
$query .= implode(', ', $placeholders);
$wpdb->query($wpdb->prepare($query, $values));
}
}
/**
* AJAX获取统计信息
*/
public function ajax_get_stats() {
global $wpdb;
$table_name = $wpdb->prefix . 'slc_links';
$stats = array(
'total' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name"),
'working' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'working'"),
'broken' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'broken'"),
'fixed' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'fixed'"),
'pending' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE status = 'pending'")
);
wp_send_json_success($stats);
}
/**
* AJAX修复链接
*/
public function ajax_fix_link() {
check_ajax_referer('slc_ajax_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die('权限不足');
}
$link_id = intval($_POST['link_id']);
$fix_type = sanitize_text_field($_POST['fix_type']);
global $wpdb;
$table_name = $wpdb->prefix . 'slc_links';
$link = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d", $link_id
));
if (!$link) {
wp_send_json_error('链接不存在');
}
$result = false;
switch ($fix_type) {
case 'auto':
$result = SLC_Link_Fixer::try_auto_fix((array)$link);
break;
case 'manual':
$new_url = sanitize_text_field($_POST['new_url']);
if ($new_url && filter_var($new_url, FILTER_VALIDATE_URL)) {
$result = SLC_Link_Fixer::update_link_in_content(
$link->source_id,
$link->source_type,
$link->url,
$new_url
);
}
break;
case 'remove':
// 从内容中移除链接
$result = SLC_Link_Fixer::update_link_in_content(
$link->source_id,
$link->source_type,
$link->url,
''
);
break;
}
if ($result) {
wp_send_json_success(array(
'message' => '修复成功',
'link_id' => $link_id
));
} else {
wp_send_json_error('修复失败');
}
}
/**
* 加载管理脚本和样式
*/
public function enqueue_admin_scripts($hook) {


