WordPress插件开发教程:实现多维度网站用户分群与精准推送 引言:为什么需要用户分群与精准推送? 在当今互联网时代,网站访问者不再满足于千篇一律的内容体验。根据Salesforce的研究,个性化内容可以将转化率提高20%。对于WordPress网站运营者而言,理解用户行为并提供定制化内容已成为提升用户参与度和转化率的关键策略。 传统的WordPress网站通常向所有访问者展示相同的内容,无论他们的兴趣、行为或背景如何。这种"一刀切"的方法已经无法满足现代用户的需求。通过开发自定义插件实现多维度用户分群与精准推送,我们可以: 提高用户参与度和页面停留时间 增加内容相关性和用户满意度 提升转化率和商业价值 收集有价值的用户行为数据 本教程将引导您从零开始开发一个功能完整的WordPress插件,实现多维度用户分群与精准推送功能,同时融入常用互联网小工具,增强网站互动性。 第一章:开发环境准备与插件基础框架 1.1 开发环境配置 在开始插件开发前,确保您已准备好以下环境: WordPress 5.0+ 本地或测试环境 PHP 7.2+ 版本 MySQL 5.6+ 或 MariaDB 10.0+ 代码编辑器(推荐VS Code或PHPStorm) 浏览器开发者工具 1.2 创建插件基础结构 首先,在WordPress的wp-content/plugins/目录下创建新文件夹user-segmentation-tools,并创建以下基础文件: user-segmentation-tools/ ├── user-segmentation-tools.php # 主插件文件 ├── includes/ # 包含核心功能文件 │ ├── class-user-segmenter.php # 用户分群核心类 │ ├── class-content-pusher.php # 内容推送类 │ ├── class-data-collector.php # 数据收集类 │ └── class-tools-integration.php # 小工具集成类 ├── admin/ # 后台管理文件 │ ├── css/ │ ├── js/ │ └── views/ ├── public/ # 前端文件 │ ├── css/ │ ├── js/ │ └── views/ ├── assets/ # 静态资源 └── uninstall.php # 插件卸载脚本 1.3 主插件文件配置 编辑user-segmentation-tools.php,添加插件基本信息: <?php /** * Plugin Name: 用户分群与精准推送工具 * Plugin URI: https://yourwebsite.com/ * Description: 实现多维度网站用户分群与精准推送功能,集成常用互联网小工具 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: user-segmentation-tools * Domain Path: /languages */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('UST_VERSION', '1.0.0'); define('UST_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('UST_PLUGIN_URL', plugin_dir_url(__FILE__)); define('UST_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'UST_'; $base_dir = UST_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class_name, $len) !== 0) { return; } $relative_class = substr($class_name, $len); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function ust_init_plugin() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('用户分群与精准推送工具需要WordPress 5.0或更高版本。', 'user-segmentation-tools'); echo '</p></div>'; }); return; } // 初始化核心类 $user_segmenter = new UST_User_Segmenter(); $content_pusher = new UST_Content_Pusher(); $data_collector = new UST_Data_Collector(); $tools_integration = new UST_Tools_Integration(); // 注册激活/停用钩子 register_activation_hook(__FILE__, ['UST_User_Segmenter', 'activate']); register_deactivation_hook(__FILE__, ['UST_User_Segmenter', 'deactivate']); // 加载文本域 load_plugin_textdomain('user-segmentation-tools', false, dirname(UST_PLUGIN_BASENAME) . '/languages'); } add_action('plugins_loaded', 'ust_init_plugin'); 第二章:用户数据收集与存储机制 2.1 设计用户数据表结构 为了实现多维度用户分群,我们需要收集和存储用户行为数据。在class-user-segmenter.php中创建数据库表: class UST_User_Segmenter { public static function activate() { self::create_tables(); self::create_default_segments(); } private static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name_users = $wpdb->prefix . 'ust_user_data'; $table_name_behavior = $wpdb->prefix . 'ust_user_behavior'; $table_name_segments = $wpdb->prefix . 'ust_user_segments'; // 用户数据表 $sql_users = "CREATE TABLE IF NOT EXISTS $table_name_users ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT(20) UNSIGNED DEFAULT NULL, session_id VARCHAR(64) NOT NULL, first_visit DATETIME NOT NULL, last_visit DATETIME NOT NULL, visit_count INT(11) DEFAULT 1, device_type VARCHAR(50), browser VARCHAR(100), location VARCHAR(200), referrer VARCHAR(500), interests TEXT, PRIMARY KEY (id), UNIQUE KEY session_id (session_id), KEY user_id (user_id), KEY last_visit (last_visit) ) $charset_collate;"; // 用户行为表 $sql_behavior = "CREATE TABLE IF NOT EXISTS $table_name_behavior ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, user_data_id BIGINT(20) UNSIGNED NOT NULL, behavior_type VARCHAR(50) NOT NULL, behavior_value TEXT, page_url VARCHAR(500), timestamp DATETIME NOT NULL, metadata TEXT, PRIMARY KEY (id), KEY user_data_id (user_data_id), KEY behavior_type (behavior_type), KEY timestamp (timestamp) ) $charset_collate;"; // 用户分群表 $sql_segments = "CREATE TABLE IF NOT EXISTS $table_name_segments ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, segment_name VARCHAR(100) NOT NULL, segment_slug VARCHAR(100) NOT NULL, segment_rules TEXT NOT NULL, user_count INT(11) DEFAULT 0, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, is_active TINYINT(1) DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY segment_slug (segment_slug) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_users); dbDelta($sql_behavior); dbDelta($sql_segments); } } 2.2 实现用户数据收集 创建class-data-collector.php,实现用户行为跟踪: class UST_Data_Collector { private $user_data_id; private $session_id; public function __construct() { $this->init_session(); $this->track_user(); add_action('wp_footer', [$this, 'add_tracking_script']); } private function init_session() { if (!session_id()) { session_start(); } if (!isset($_SESSION['ust_session_id'])) { $_SESSION['ust_session_id'] = $this->generate_session_id(); } $this->session_id = $_SESSION['ust_session_id']; } private function generate_session_id() { return md5(uniqid(mt_rand(), true) . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); } private function track_user() { global $wpdb; // 获取用户信息 $user_id = is_user_logged_in() ? get_current_user_id() : null; $current_time = current_time('mysql'); // 检查是否已存在该会话记录 $table_name = $wpdb->prefix . 'ust_user_data'; $existing = $wpdb->get_row($wpdb->prepare( "SELECT id FROM $table_name WHERE session_id = %s", $this->session_id )); if ($existing) { $this->user_data_id = $existing->id; // 更新最后访问时间 $wpdb->update( $table_name, [ 'last_visit' => $current_time, 'visit_count' => $wpdb->get_var($wpdb->prepare( "SELECT visit_count + 1 FROM $table_name WHERE id = %d", $this->user_data_id )) ], ['id' => $this->user_data_id] ); } else { // 收集设备信息 $device_info = $this->get_device_info(); $location = $this->get_user_location(); // 插入新用户记录 $wpdb->insert( $table_name, [ 'user_id' => $user_id, 'session_id' => $this->session_id, 'first_visit' => $current_time, 'last_visit' => $current_time, 'device_type' => $device_info['device_type'], 'browser' => $device_info['browser'], 'location' => $location, 'referrer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '直接访问' ] ); $this->user_data_id = $wpdb->insert_id; } // 记录页面访问行为 $this->record_behavior('page_view', [ 'page_title' => wp_get_document_title(), 'page_url' => home_url($_SERVER['REQUEST_URI']) ]); } private function get_device_info() { $user_agent = $_SERVER['HTTP_USER_AGENT']; // 简单设备检测 $device_type = 'desktop'; if (preg_match('/(android|webos|iphone|ipad|ipod|blackberry|windows phone)/i', $user_agent)) { $device_type = 'mobile'; } elseif (preg_match('/(tablet|ipad|playbook|silk)/i', $user_agent)) { $device_type = 'tablet'; } // 浏览器检测 $browser = '未知浏览器'; if (preg_match('/MSIE|Trident/i', $user_agent)) { $browser = 'Internet Explorer'; } elseif (preg_match('/Firefox/i', $user_agent)) { $browser = 'Firefox'; } elseif (preg_match('/Chrome/i', $user_agent)) { $browser = 'Chrome'; } elseif (preg_match('/Safari/i', $user_agent)) { $browser = 'Safari'; } elseif (preg_match('/Edge/i', $user_agent)) { $browser = 'Edge'; } return [ 'device_type' => $device_type, 'browser' => $browser ]; } private function get_user_location() { // 简化版地理位置获取 // 实际应用中可使用IP地理位置API $ip = $_SERVER['REMOTE_ADDR']; // 如果是本地环境,返回模拟位置 if ($ip === '127.0.0.1' || $ip === '::1') { return '本地环境'; } // 这里可以集成IP地理位置API // 例如使用ipapi.co或ipstack.com的API return '未知位置'; } public function record_behavior($type, $value = null, $metadata = []) { global $wpdb; if (!$this->user_data_id) { return false; } $table_name = $wpdb->prefix . 'ust_user_behavior'; return $wpdb->insert( $table_name, [ 'user_data_id' => $this->user_data_id, 'behavior_type' => $type, 'behavior_value' => is_array($value) ? json_encode($value) : $value, 'page_url' => home_url($_SERVER['REQUEST_URI']), 'timestamp' => current_time('mysql'), 'metadata' => json_encode($metadata) ] ); } public function add_tracking_script() { ?> <script type="text/javascript"> (function() { // 跟踪用户交互行为 document.addEventListener('DOMContentLoaded', function() { // 跟踪链接点击 document.querySelectorAll('a').forEach(function(link) { link.addEventListener('click', function(e) { var linkData = { href: this.href, text: this.textContent.substring(0, 100), class: this.className }; // 发送AJAX请求记录行为 fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=ust_track_behavior&type=link_click&value=' + encodeURIComponent(JSON.stringify(linkData)) }); }); }); // 跟踪表单交互 document.querySelectorAll('form').forEach(function(form) { form.addEventListener('submit', function(e) { var formData = { form_id: this.id || 'unnamed_form', action: this.action }; fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=ust_track_behavior&type=form_submit&value=' + encodeURIComponent(JSON.stringify(formData)) }); }); }); // 跟踪滚动深度 var scrollTracked = false; window.addEventListener('scroll', function() { var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100; if (scrollPercent > 50 && !scrollTracked) { scrollTracked = true; fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=ust_track_behavior&type=scroll_depth&value=50' }); } }); }); })(); </script> <?php } } 第三章:多维度用户分群算法实现 3.1 设计分群规则引擎 在class-user-segmenter.php中扩展分群功能: class UST_User_Segmenter { private $segments = []; public function __construct() { $this->load_segments(); add_action('init', [$this, 'evaluate_user_segments']); add_action('admin_menu', [$this, 'add_admin_menu']); } private function load_segments() { global $wpdb; $table_name = $wpdb->prefix . 'ust_user_segments'; $this->segments = $wpdb->get_results( "SELECT * FROM $table_name WHERE is_active = 1" ); } public function evaluate_user_segments() { if (is_admin()) { return; } $user_segments = $this->get_user_segments(); setcookie('ust_user_segments', json_encode($user_segments), time() + 86400 * 30, '/'); $_SESSION['ust_user_segments'] = $user_segments; } private function get_user_segments() { global $wpdb; $user_segments = []; $session_id = isset($_SESSION['ust_session_id']) ? $_SESSION['ust_session_id'] : ''; if (empty($session_id)) { return $user_segments; } // 获取当前用户数据ID $user_data_table = $wpdb->prefix . 'ust_user_data'; $user_data = $wpdb->get_row($wpdb->prepare( "SELECT id, user_id, visit_count, device_type, location FROM $user_data_table WHERE session_id = %s", $session_id )); if (!$user_data) { return $user_segments; } // 获取用户行为数据 $behavior_table = $wpdb->prefix . 'ust_user_behavior'; get_results($wpdb->prepare( "SELECT behavior_type, COUNT(*) as count, MAX(timestamp) as last_time FROM $behavior_table WHERE user_data_id = %d GROUP BY behavior_type", $user_data->id )); $behavior_summary = []; foreach ($user_behaviors as $behavior) { $behavior_summary[$behavior->behavior_type] = [ 'count' => $behavior->count, 'last_time' => $behavior->last_time ]; } // 评估每个分群规则 foreach ($this->segments as $segment) { $rules = json_decode($segment->segment_rules, true); if ($this->evaluate_rules($rules, $user_data, $behavior_summary)) { $user_segments[] = $segment->segment_slug; // 记录用户分群关系 $this->record_user_segment($user_data->id, $segment->id); } } return $user_segments; } private function evaluate_rules($rules, $user_data, $behavior_summary) { foreach ($rules as $rule_group) { $group_result = true; foreach ($rule_group as $rule) { $rule_result = $this->evaluate_single_rule($rule, $user_data, $behavior_summary); if (!$rule_result) { $group_result = false; break; } } if ($group_result) { return true; } } return false; } private function evaluate_single_rule($rule, $user_data, $behavior_summary) { $field = $rule['field']; $operator = $rule['operator']; $value = $rule['value']; switch ($field) { case 'visit_count': $actual_value = $user_data->visit_count; return $this->compare_values($actual_value, $operator, $value); case 'device_type': $actual_value = $user_data->device_type; return $this->compare_strings($actual_value, $operator, $value); case 'user_role': $actual_value = $user_data->user_id ? $this->get_user_role($user_data->user_id) : 'guest'; return $this->compare_strings($actual_value, $operator, $value); case 'behavior_count': $behavior_type = $rule['behavior_type'] ?? 'page_view'; $actual_value = isset($behavior_summary[$behavior_type]) ? $behavior_summary[$behavior_type]['count'] : 0; return $this->compare_values($actual_value, $operator, $value); case 'last_visit_days': $last_visit = strtotime($user_data->last_visit); $days_diff = floor((time() - $last_visit) / (60 * 60 * 24)); return $this->compare_values($days_diff, $operator, $value); case 'location': $actual_value = $user_data->location; return $this->compare_strings($actual_value, $operator, $value); default: return false; } } private function compare_values($actual, $operator, $expected) { switch ($operator) { case 'equals': return $actual == $expected; case 'not_equals': return $actual != $expected; case 'greater_than': return $actual > $expected; case 'less_than': return $actual < $expected; case 'greater_than_equals': return $actual >= $expected; case 'less_than_equals': return $actual <= $expected; default: return false; } } private function compare_strings($actual, $operator, $expected) { switch ($operator) { case 'equals': return strtolower($actual) == strtolower($expected); case 'not_equals': return strtolower($actual) != strtolower($expected); case 'contains': return stripos($actual, $expected) !== false; case 'not_contains': return stripos($actual, $expected) === false; case 'starts_with': return stripos($actual, $expected) === 0; case 'ends_with': $expected_length = strlen($expected); return substr_compare($actual, $expected, -$expected_length, $expected_length, true) === 0; default: return false; } } private function get_user_role($user_id) { $user = get_userdata($user_id); if ($user && !empty($user->roles)) { return $user->roles[0]; } return 'guest'; } private function record_user_segment($user_data_id, $segment_id) { global $wpdb; $table_name = $wpdb->prefix . 'ust_user_segment_relationships'; // 检查是否已存在关系 $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE user_data_id = %d AND segment_id = %d", $user_data_id, $segment_id )); if (!$existing) { $wpdb->insert( $table_name, [ 'user_data_id' => $user_data_id, 'segment_id' => $segment_id, 'assigned_at' => current_time('mysql') ] ); } } public function add_admin_menu() { add_menu_page( '用户分群管理', '用户分群', 'manage_options', 'user-segmentation', [$this, 'render_admin_page'], 'dashicons-groups', 30 ); add_submenu_page( 'user-segmentation', '分群规则管理', '分群规则', 'manage_options', 'user-segmentation-rules', [$this, 'render_rules_page'] ); add_submenu_page( 'user-segmentation', '用户行为分析', '行为分析', 'manage_options', 'user-segmentation-analytics', [$this, 'render_analytics_page'] ); } public function render_admin_page() { include UST_PLUGIN_DIR . 'admin/views/admin-main.php'; } public function render_rules_page() { include UST_PLUGIN_DIR . 'admin/views/rules-management.php'; } public function render_analytics_page() { include UST_PLUGIN_DIR . 'admin/views/analytics-dashboard.php'; } } ## 第四章:精准内容推送系统 ### 4.1 内容推送引擎设计 创建`class-content-pusher.php`: class UST_Content_Pusher { private $push_rules = []; public function __construct() { $this->load_push_rules(); add_filter('the_content', [$this, 'inject_targeted_content']); add_action('wp_footer', [$this, 'inject_floating_widgets']); add_action('admin_menu', [$this, 'add_push_rules_menu']); } private function load_push_rules() { global $wpdb; $table_name = $wpdb->prefix . 'ust_push_rules'; $this->push_rules = $wpdb->get_results( "SELECT * FROM $table_name WHERE is_active = 1 ORDER BY priority DESC" ); } public function inject_targeted_content($content) { if (is_admin() || !is_singular()) { return $content; } $user_segments = $this->get_current_user_segments(); $post_id = get_the_ID(); $post_type = get_post_type(); foreach ($this->push_rules as $rule) { if ($this->should_apply_rule($rule, $user_segments, $post_id, $post_type)) { $content = $this->apply_push_rule($content, $rule); } } return $content; } private function should_apply_rule($rule, $user_segments, $post_id, $post_type) { $rule_data = json_decode($rule->rule_conditions, true); // 检查目标分群 if (!empty($rule_data['target_segments'])) { $intersection = array_intersect($user_segments, $rule_data['target_segments']); if (empty($intersection)) { return false; } } // 检查内容类型 if (!empty($rule_data['post_types']) && !in_array($post_type, $rule_data['post_types'])) { return false; } // 检查特定文章/页面 if (!empty($rule_data['specific_posts']) && !in_array($post_id, $rule_data['specific_posts'])) { return false; } // 检查分类条件 if (!empty($rule_data['categories'])) { $post_categories = wp_get_post_categories($post_id, ['fields' => 'ids']); $intersection = array_intersect($post_categories, $rule_data['categories']); if (empty($intersection)) { return false; } } // 检查标签条件 if (!empty($rule_data['tags'])) { $post_tags = wp_get_post_tags($post_id, ['fields' => 'ids']); $intersection = array_intersect($post_tags, $rule_data['tags']); if (empty($intersection)) { return false; } } // 检查显示频率限制 if ($rule->display_limit > 0) { $display_count = $this->get_rule_display_count($rule->id); if ($display_count >= $rule->display_limit) { return false; } } return true; } private function apply_push_rule($content, $rule) { $rule_data = json_decode($rule->rule_conditions, true); $push_content = $this->get_push_content($rule->content_type, $rule->content_value); if (!$push_content) { return $content; } // 记录显示次数 $this->record_rule_display($rule->id); // 根据位置插入内容 switch ($rule_data['position']) { case 'before_content': return $push_content . $content; case 'after_content': return $content . $push_content; case 'after_paragraph': $paragraph_number = $rule_data['paragraph_number'] ?? 3; return $this->insert_after_paragraph($content, $push_content, $paragraph_number); case 'replace_content': $selector = $rule_data['css_selector'] ?? ''; if ($selector) { return $this->replace_by_selector($content, $push_content, $selector); } return $content; default: return $content; } } private function get_push_content($type, $value) { switch ($type) { case 'custom_html': return do_shortcode($value); case 'shortcode': return do_shortcode($value); case 'post': $post = get_post($value); if ($post) { return apply_filters('the_content', $post->post_content); } break; case 'widget': return $this->render_widget($value); case 'call_to_action': return $this->render_cta($value); } return ''; } private function insert_after_paragraph($content, $insertion, $paragraph_number) { $paragraphs = explode('</p>', $content); if (count($paragraphs) >= $paragraph_number) { $paragraphs[$paragraph_number - 1] .= $insertion; return implode('</p>', $paragraphs); } return $content . $insertion; } public function inject_floating_widgets() { if (is_admin()) { return; } $user_segments = $this->get_current_user_segments(); foreach ($this->push_rules as $rule) { $rule_data = json_decode($rule->rule_conditions, true); if ($rule_data['display_type'] === 'floating' && $this->should_apply_rule($rule, $user_segments, get_the_ID(), get_post_type())) { $this->render_floating_widget($rule); } } } private function render_floating_widget($rule) { $content = $this->get_push_content($rule->content_type, $rule->content_value); $rule_data = json_decode($rule->rule_conditions, true); ?> <div class="ust-floating-widget" data-position="<?php echo esc_attr($rule_data['float_position']); ?>" data-rule-id="<?php echo esc_attr($rule->id); ?>"> <div class="ust-floating-content"> <?php echo $content; ?> </div> <button class="ust-close-widget" aria-label="关闭">×</button> </div> <style> .ust-floating-widget { position: fixed; z-index: 999999; max-width: 300px; background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); padding: 20px; animation: ust-fade-in 0.3s ease; } .ust-floating-widget[data-position="bottom-right"] { bottom: 20px; right: 20px; } .ust-floating-widget[data-position="bottom-left"] { bottom: 20px; left: 20px; } .ust-close-widget { position: absolute; top: 5px; right: 10px; background: none; border: none; font-size: 20px; cursor: pointer; color: #666; } @keyframes ust-fade-in { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } </style> <script> document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.ust-close-widget').forEach(function(button) { button.addEventListener('click', function() { var widget = this.closest('.ust-floating-widget'); widget.style.display = 'none'; // 记录关闭行为 var ruleId = widget.getAttribute('data-rule-id'); fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=ust_track_widget_close&rule_id=' + ruleId }); }); }); }); </script> <?php } private function get_current_user_segments() { if (isset($_SESSION['ust_user_segments'])) { return $_SESSION['ust_user_segments']; } if (isset($_COOKIE['ust_user_segments'])) { return json_decode(stripslashes($_COOKIE['ust_user_segments']), true); } return []; } } ## 第五章:常用互联网小工具集成 ### 5.1 小工具管理器实现 创建`class-tools-integration.php`: class UST_Tools_Integration { private $available_tools = []; public function __construct() { $this->register_tools(); add_action('widgets_init', [$this, 'register_widgets']); add_shortcode('ust_tool', [$this, 'render_tool_shortcode']); add_action('wp_ajax_ust_tool_action', [$this, 'handle_tool_ajax']); add_action('wp_ajax_nopriv_ust_tool_action', [$this, 'handle_tool_ajax']); } private function register_tools() { $this->available_tools = [ 'poll' => [ 'name' => '投票工具', 'description' => '创建简单的投票调查', 'callback' => [$this, 'render_poll_tool'], 'settings' => [ 'question' => ['type' => 'text', 'label' => '问题'], 'options' => ['type' => 'textarea', 'label' => '选项(每行一个)'], 'multiple' => ['type' => 'checkbox', 'label' => '允许多选'] ] ], 'calculator' => [ 'name' => '计算器工具', 'description' => '简单的计算器功能', 'callback' => [$this, 'render_calculator_tool'], 'settings' => [ 'type' => [ 'type' => 'select', 'label' => '计算器类型', 'options' => [ 'basic' => '基础计算器', 'loan' => '贷款计算器', 'currency' => '货币换算器' ] ] ] ], 'countdown' => [ 'name' => '倒计时工具', 'description' => '创建活动倒计时', 'callback' => [$this, 'render_countdown_tool'], 'settings' => [ 'target_date' => ['type' => 'date', 'label' => '目标日期'], 'title' => ['type' => 'text', 'label' => '标题'], 'show_days' => ['type' => 'checkbox', 'label' => '显示天数', 'default' => true] ] ], 'social_share' => [ 'name' => '社交分享工具', 'description' => '增强的社交分享按钮', 'callback' => [$this, 'render_social_share_tool'],
发表评论分类: 网站建设
实战教程:在WordPress网站中添加基于位置的附近门店与服务查询功能 引言:位置服务在互联网应用中的重要性 在移动互联网时代,基于位置的服务(LBS)已成为各类网站和应用程序的标配功能。无论是电商平台、服务型企业还是内容网站,为用户提供附近门店查询、地理位置搜索等功能,都能显著提升用户体验和商业转化率。根据谷歌的研究,超过80%的消费者在寻找本地服务时会使用“附近”搜索功能,而其中76%的用户会在一天内访问相关门店。 WordPress作为全球最流行的内容管理系统,占据了互联网上超过43%的网站份额。然而,许多WordPress网站所有者并未充分利用位置服务的潜力。本教程将详细介绍如何通过代码二次开发,在WordPress网站中实现专业的附近门店与服务查询功能,无需依赖昂贵的第三方插件,同时保持对功能的完全控制。 第一部分:前期准备与环境配置 1.1 功能需求分析与设计 在开始编码之前,我们需要明确功能需求。一个完整的附近门店查询系统应包含以下核心功能: 门店信息管理(名称、地址、坐标、联系方式、营业时间等) 用户位置获取(自动检测或手动输入) 距离计算与排序算法 地图可视化展示 筛选与搜索功能 响应式设计,支持移动设备 1.2 开发环境搭建 确保你的WordPress开发环境满足以下要求: WordPress 5.0以上版本 PHP 7.4以上(推荐8.0+) MySQL 5.6以上或MariaDB 10.1以上 启用Apache/Nginx的rewrite模块 代码编辑器(VS Code、PHPStorm等) 1.3 获取地图API密钥 我们将使用Google Maps API作为地图服务,你也可以选择百度地图、高德地图等国内服务。以Google Maps为例: 访问Google Cloud Console(console.cloud.google.com) 创建新项目或选择现有项目 启用"Maps JavaScript API"、"Places API"和"Geocoding API" 创建API密钥并设置限制(推荐限制HTTP引用来源) 第二部分:数据库设计与门店管理 2.1 创建自定义数据表 虽然可以使用WordPress的自定义文章类型(CPT)存储门店信息,但为了更好的性能和查询效率,我们创建独立的数据表: // 在插件激活时创建数据表 function create_store_locations_table() { global $wpdb; $table_name = $wpdb->prefix . 'store_locations'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, store_name varchar(255) NOT NULL, address text NOT NULL, city varchar(100), state varchar(100), country varchar(100), postal_code varchar(20), latitude decimal(10,8) NOT NULL, longitude decimal(11,8) NOT NULL, phone varchar(30), email varchar(100), website varchar(255), description text, opening_hours text, categories varchar(255), featured tinyint(1) DEFAULT 0, active tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY latitude_longitude (latitude, longitude), KEY city_state (city, state), KEY categories (categories(191)) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } register_activation_hook(__FILE__, 'create_store_locations_table'); 2.2 创建后台管理界面 为方便管理门店信息,我们需要在WordPress后台创建管理界面: // 添加管理菜单 add_action('admin_menu', 'register_store_locations_menu'); function register_store_locations_menu() { add_menu_page( '门店管理', '门店位置', 'manage_options', 'store-locations', 'store_locations_admin_page', 'dashicons-location-alt', 30 ); add_submenu_page( 'store-locations', '添加新门店', '添加新门店', 'manage_options', 'add-new-store', 'add_new_store_page' ); add_submenu_page( 'store-locations', '门店分类', '分类管理', 'manage_options', 'store-categories', 'store_categories_page' ); } // 主管理页面 function store_locations_admin_page() { global $wpdb; $table_name = $wpdb->prefix . 'store_locations'; // 处理批量操作 if (isset($_POST['action']) && $_POST['action'] == 'bulk_delete') { // 验证nonce和权限 check_admin_referer('bulk_action_nonce'); if (!current_user_can('manage_options')) { wp_die('权限不足'); } if (isset($_POST['store_ids'])) { $ids = array_map('intval', $_POST['store_ids']); $ids_placeholder = implode(',', array_fill(0, count($ids), '%d')); $wpdb->query($wpdb->prepare( "DELETE FROM $table_name WHERE id IN ($ids_placeholder)", $ids )); echo '<div class="notice notice-success"><p>已删除选中的门店</p></div>'; } } // 分页查询 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; $total_items = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $stores = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name ORDER BY created_at DESC LIMIT %d OFFSET %d", $per_page, $offset )); // 显示管理界面 ?> <div class="wrap"> <h1 class="wp-heading-inline">门店管理</h1> <a href="<?php echo admin_url('admin.php?page=add-new-store'); ?>" class="page-title-action">添加新门店</a> <form method="post" action=""> <?php wp_nonce_field('bulk_action_nonce'); ?> <input type="hidden" name="action" value="bulk_delete"> <div class="tablenav top"> <div class="alignleft actions bulkactions"> <select name="bulk_action"> <option value="-1">批量操作</option> <option value="delete">删除</option> </select> <input type="submit" class="button action" value="应用"> </div> <div class="tablenav-pages"> <?php $total_pages = ceil($total_items / $per_page); echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $current_page )); ?> </div> </div> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <td class="manage-column column-cb check-column"> <input type="checkbox" id="cb-select-all-1"> </td> <th>ID</th> <th>门店名称</th> <th>地址</th> <th>城市</th> <th>电话</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($stores as $store): ?> <tr> <th scope="row" class="check-column"> <input type="checkbox" name="store_ids[]" value="<?php echo $store->id; ?>"> </th> <td><?php echo $store->id; ?></td> <td> <strong><?php echo esc_html($store->store_name); ?></strong> <?php if ($store->featured): ?> <span class="dashicons dashicons-star-filled" style="color:#ffb900;"></span> <?php endif; ?> </td> <td><?php echo esc_html($store->address); ?></td> <td><?php echo esc_html($store->city); ?></td> <td><?php echo esc_html($store->phone); ?></td> <td> <?php if ($store->active): ?> <span class="status-active">启用</span> <?php else: ?> <span class="status-inactive">禁用</span> <?php endif; ?> </td> <td> <a href="<?php echo admin_url('admin.php?page=add-new-store&id=' . $store->id); ?>">编辑</a> | <a href="#" class="delete-store" data-id="<?php echo $store->id; ?>">删除</a> </td> </tr> <?php endforeach; ?> </tbody> </table> </form> </div> <script> jQuery(document).ready(function($) { $('.delete-store').on('click', function(e) { e.preventDefault(); if (confirm('确定要删除这个门店吗?')) { var storeId = $(this).data('id'); $.post(ajaxurl, { action: 'delete_store', store_id: storeId, nonce: '<?php echo wp_create_nonce("delete_store_nonce"); ?>' }, function(response) { if (response.success) { location.reload(); } else { alert('删除失败:' + response.data); } }); } }); }); </script> <?php } 第三部分:核心功能开发 3.1 地理位置编码与坐标获取 将地址转换为地理坐标(经纬度)是位置服务的基础: class LocationGeocoder { private $api_key; private $cache_time = 604800; // 缓存7天 public function __construct($api_key) { $this->api_key = $api_key; } public function geocode_address($address) { // 检查缓存 $cache_key = 'geocode_' . md5($address); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // 调用Google Geocoding API $url = 'https://maps.googleapis.com/maps/api/geocode/json'; $params = array( 'address' => urlencode($address), 'key' => $this->api_key, 'language' => 'zh-CN' ); $response = wp_remote_get($url . '?' . http_build_query($params)); if (is_wp_error($response)) { return array( 'success' => false, 'error' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if ($data['status'] !== 'OK') { return array( 'success' => false, 'error' => '地理编码失败:' . $data['status'] ); } $result = $data['results'][0]; $location = array( 'success' => true, 'latitude' => $result['geometry']['location']['lat'], 'longitude' => $result['geometry']['location']['lng'], 'formatted_address' => $result['formatted_address'], 'address_components' => $this->parse_address_components($result['address_components']) ); // 缓存结果 set_transient($cache_key, $location, $this->cache_time); return $location; } private function parse_address_components($components) { $parsed = array(); foreach ($components as $component) { foreach ($component['types'] as $type) { $parsed[$type] = $component['long_name']; } } return $parsed; } public function calculate_distance($lat1, $lon1, $lat2, $lon2, $unit = 'km') { $theta = $lon1 - $lon2; $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); $dist = acos($dist); $dist = rad2deg($dist); $miles = $dist * 60 * 1.1515; switch ($unit) { case 'km': return $miles * 1.609344; case 'm': return $miles * 1.609344 * 1000; default: return $miles; } } } 3.2 附近门店查询算法 实现高效的附近门店搜索算法: class NearbyStoresFinder { private $db; private $earth_radius = 6371; // 地球半径,单位公里 public function __construct() { global $wpdb; $this->db = $wpdb; } public function find_nearby_stores($latitude, $longitude, $radius_km = 10, $limit = 20, $category = null) { $table_name = $this->db->prefix . 'store_locations'; // 使用Haversine公式计算距离 $haversine = "( $this->earth_radius * acos( cos(radians(%f)) * cos(radians(latitude)) * cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)) ) )"; $query = "SELECT *, $haversine AS distance FROM $table_name WHERE active = 1"; $params = array($latitude, $longitude, $latitude); // 添加分类筛选 if ($category) { $query .= " AND FIND_IN_SET(%s, categories)"; $params[] = $category; } $query .= " HAVING distance <= %d ORDER BY distance ASC LIMIT %d"; $params[] = $radius_km; $params[] = $limit; $prepared_query = $this->db->prepare($query, $params); return $this->db->get_results($prepared_query); } public function find_stores_by_bounds($north, $south, $east, $west, $category = null) { $table_name = $this->db->prefix . 'store_locations'; $query = "SELECT * FROM $table_name WHERE active = 1 AND latitude BETWEEN %f AND %f AND longitude BETWEEN %f AND %f"; $params = array($south, $north, $west, $east); if ($category) { $query .= " AND FIND_IN_SET(%s, categories)"; $params[] = $category; } $query .= " ORDER BY featured DESC, store_name ASC LIMIT 100"; $prepared_query = $this->db->prepare($query, $params); return $this->db->get_results($prepared_query); } } 第四部分:前端界面与用户体验 4.1 创建搜索表单与结果展示 class StoreLocatorFrontend { private $api_key; public function __construct($api_key) { $this->api_key = $api_key; add_shortcode('store_locator', array($this, 'render_store_locator')); add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts')); add_action('wp_ajax_search_nearby_stores', array($this, 'ajax_search_nearby_stores')); add_action('wp_ajax_nopriv_search_nearby_stores', array($this, 'ajax_search_nearby_stores')); } public function enqueue_scripts() { global $post; if (has_shortcode($post->post_content, 'store_locator')) { // 加载Google Maps API wp_enqueue_script( 'google-maps-api', 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&libraries=places&language=zh-CN', array(), null, true ); // 加载自定义脚本 wp_enqueue_script( 'store-locator-frontend', plugin_dir_url(__FILE__) . 'js/store-locator-frontend.js', array('jquery', 'google-maps-api'), '1.0.0', true ); // 加载样式 wp_enqueue_style( 'store-locator-style', plugin_dir_url(__FILE__) . 'css/store-locator.css', array(), '1.0.0' ); // 传递数据到前端 wp_localize_script('store-locator-frontend', 'storeLocatorData', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('store_locator_nonce'), 'default_lat' => 39.9042, // 默认坐标(北京) 'default_lng' => 116.4074, 'default_radius' => 10, 4.2 构建响应式前端界面 public function render_store_locator($atts) { $atts = shortcode_atts(array( 'default_radius' => 10, 'max_results' => 50, 'show_categories' => true, 'show_filters' => true ), $atts); // 获取所有门店分类 $categories = $this->get_store_categories(); ob_start(); ?> <div class="store-locator-container"> <div class="store-locator-header"> <h2>附近门店查询</h2> <p>输入地址或使用当前位置查找附近的门店</p> </div> <div class="store-locator-main"> <!-- 搜索面板 --> <div class="search-panel"> <div class="search-form"> <div class="form-group"> <label for="store-search-input">搜索地址或位置:</label> <div class="search-input-wrapper"> <input type="text" id="store-search-input" class="search-input" placeholder="输入地址、城市或邮政编码"> <button type="button" id="use-current-location" class="location-btn"> <span class="dashicons dashicons-location"></span> 使用当前位置 </button> </div> </div> <?php if ($atts['show_categories'] && !empty($categories)): ?> <div class="form-group"> <label>服务分类:</label> <div class="categories-filter"> <select id="store-category" class="category-select"> <option value="">所有分类</option> <?php foreach ($categories as $category): ?> <option value="<?php echo esc_attr($category); ?>"> <?php echo esc_html($category); ?> </option> <?php endforeach; ?> </select> </div> </div> <?php endif; ?> <div class="form-group"> <label>搜索半径:</label> <div class="radius-slider"> <input type="range" id="search-radius" min="1" max="50" value="<?php echo esc_attr($atts['default_radius']); ?>" class="radius-range"> <span id="radius-value"><?php echo esc_attr($atts['default_radius']); ?> 公里</span> </div> </div> <div class="form-actions"> <button type="button" id="search-stores" class="search-btn"> 搜索附近门店 </button> <button type="button" id="reset-search" class="reset-btn"> 重置搜索 </button> </div> </div> <!-- 搜索结果列表 --> <div class="results-panel"> <div class="results-header"> <h3>搜索结果</h3> <div class="results-count"> 找到 <span id="results-count">0</span> 个门店 </div> </div> <div class="results-list" id="stores-results"> <div class="no-results"> <p>请输入位置开始搜索</p> </div> </div> <div class="results-pagination" id="results-pagination" style="display: none;"> <button id="prev-page" class="pagination-btn" disabled>上一页</button> <span id="page-info">第 1 页</span> <button id="next-page" class="pagination-btn" disabled>下一页</button> </div> </div> </div> <!-- 地图显示区域 --> <div class="map-panel"> <div id="store-locator-map"></div> <div class="map-controls"> <button id="zoom-in" class="map-control-btn">+</button> <button id="zoom-out" class="map-control-btn">-</button> <button id="reset-map" class="map-control-btn">重置视图</button> </div> </div> </div> <!-- 门店详情模态框 --> <div id="store-details-modal" class="store-modal"> <div class="modal-content"> <div class="modal-header"> <h3 id="modal-store-name"></h3> <button class="modal-close">×</button> </div> <div class="modal-body"> <div class="store-info-grid"> <div class="info-item"> <span class="dashicons dashicons-location"></span> <div> <strong>地址:</strong> <span id="modal-store-address"></span> </div> </div> <div class="info-item"> <span class="dashicons dashicons-phone"></span> <div> <strong>电话:</strong> <span id="modal-store-phone"></span> </div> </div> <div class="info-item"> <span class="dashicons dashicons-clock"></span> <div> <strong>营业时间:</strong> <div id="modal-store-hours"></div> </div> </div> <div class="info-item"> <span class="dashicons dashicons-admin-site"></span> <div> <strong>距离:</strong> <span id="modal-store-distance"></span> </div> </div> </div> <div class="store-description"> <h4>门店介绍</h4> <p id="modal-store-description"></p> </div> <div class="store-actions"> <a href="#" id="modal-get-directions" class="action-btn" target="_blank"> <span class="dashicons dashicons-car"></span> 获取路线 </a> <a href="#" id="modal-call-store" class="action-btn"> <span class="dashicons dashicons-phone"></span> 拨打电话 </a> <a href="#" id="modal-visit-website" class="action-btn" target="_blank"> <span class="dashicons dashicons-external"></span> 访问网站 </a> </div> </div> </div> </div> </div> <!-- 加载状态 --> <div id="loading-overlay" style="display: none;"> <div class="loading-spinner"></div> <p>正在搜索附近门店...</p> </div> <?php return ob_get_clean(); } private function get_store_categories() { global $wpdb; $table_name = $wpdb->prefix . 'store_locations'; $categories = $wpdb->get_col( "SELECT DISTINCT categories FROM $table_name WHERE active = 1 AND categories IS NOT NULL" ); $all_categories = array(); foreach ($categories as $category_string) { $cat_array = explode(',', $category_string); foreach ($cat_array as $category) { $category = trim($category); if ($category && !in_array($category, $all_categories)) { $all_categories[] = $category; } } } sort($all_categories); return $all_categories; } 4.3 前端JavaScript交互逻辑 // store-locator-frontend.js (function($) { 'use strict'; class StoreLocator { constructor() { this.map = null; this.markers = []; this.infoWindow = null; this.currentLocation = null; this.currentRadius = parseInt(storeLocatorData.default_radius); this.currentCategory = ''; this.currentPage = 1; this.storesPerPage = 10; this.allStores = []; this.init(); } init() { this.initMap(); this.bindEvents(); this.initAutocomplete(); } initMap() { const defaultLocation = { lat: parseFloat(storeLocatorData.default_lat), lng: parseFloat(storeLocatorData.default_lng) }; this.map = new google.maps.Map(document.getElementById('store-locator-map'), { zoom: 12, center: defaultLocation, mapTypeControl: true, streetViewControl: false, fullscreenControl: true, styles: this.getMapStyles() }); this.infoWindow = new google.maps.InfoWindow({ maxWidth: 300 }); // 添加初始标记 this.addInitialMarker(defaultLocation); } getMapStyles() { return [ { featureType: 'poi.business', stylers: [{ visibility: 'on' }] }, { featureType: 'transit', elementType: 'labels.icon', stylers: [{ visibility: 'off' }] } ]; } addInitialMarker(position) { const marker = new google.maps.Marker({ position: position, map: this.map, icon: { path: google.maps.SymbolPath.CIRCLE, scale: 8, fillColor: '#4285F4', fillOpacity: 1, strokeColor: '#FFFFFF', strokeWeight: 2 }, title: '中心位置' }); this.markers.push(marker); } bindEvents() { // 搜索按钮 $('#search-stores').on('click', () => this.searchStores()); // 使用当前位置 $('#use-current-location').on('click', () => this.getCurrentLocation()); // 半径滑块 $('#search-radius').on('input', (e) => { const radius = parseInt(e.target.value); $('#radius-value').text(radius + ' 公里'); this.currentRadius = radius; }); // 分类选择 $('#store-category').on('change', (e) => { this.currentCategory = e.target.value; }); // 重置搜索 $('#reset-search').on('click', () => this.resetSearch()); // 分页按钮 $('#prev-page').on('click', () => this.changePage(-1)); $('#next-page').on('click', () => this.changePage(1)); // 地图控制 $('#zoom-in').on('click', () => this.map.setZoom(this.map.getZoom() + 1)); $('#zoom-out').on('click', () => this.map.setZoom(this.map.getZoom() - 1)); $('#reset-map').on('click', () => this.resetMapView()); // 模态框关闭 $('.modal-close, #store-details-modal').on('click', (e) => { if (e.target === e.currentTarget) { this.closeModal(); } }); // 键盘事件 $(document).on('keyup', (e) => { if (e.key === 'Escape') this.closeModal(); }); } initAutocomplete() { const input = document.getElementById('store-search-input'); const autocomplete = new google.maps.places.Autocomplete(input, { types: ['geocode', 'establishment'], componentRestrictions: { country: 'cn' } }); autocomplete.addListener('place_changed', () => { const place = autocomplete.getPlace(); if (!place.geometry) { alert('未找到该位置,请尝试更详细的地址'); return; } this.currentLocation = { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }; this.updateMapCenter(this.currentLocation); this.searchStores(); }); } getCurrentLocation() { if (!navigator.geolocation) { alert('您的浏览器不支持地理位置功能'); return; } $('#use-current-location').prop('disabled', true).text('定位中...'); navigator.geolocation.getCurrentPosition( (position) => { this.currentLocation = { lat: position.coords.latitude, lng: position.coords.longitude }; // 反向地理编码获取地址 const geocoder = new google.maps.Geocoder(); geocoder.geocode({ location: this.currentLocation }, (results, status) => { if (status === 'OK' && results[0]) { $('#store-search-input').val(results[0].formatted_address); } }); this.updateMapCenter(this.currentLocation); this.searchStores(); $('#use-current-location').prop('disabled', false).html( '<span class="dashicons dashicons-location"></span> 使用当前位置' ); }, (error) => { let errorMessage = '无法获取您的位置:'; switch(error.code) { case error.PERMISSION_DENIED: errorMessage += '用户拒绝了位置请求'; break; case error.POSITION_UNAVAILABLE: errorMessage += '位置信息不可用'; break; case error.TIMEOUT: errorMessage += '获取位置超时'; break; default: errorMessage += '未知错误'; } alert(errorMessage); $('#use-current-location').prop('disabled', false).html( '<span class="dashicons dashicons-location"></span> 使用当前位置' ); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } updateMapCenter(location) { this.map.setCenter(location); // 更新中心标记 if (this.markers.length > 0) { this.markers[0].setPosition(location); } } async searchStores() { if (!this.currentLocation) { alert('请先选择或输入一个位置'); return; } this.showLoading(true); try { const response = await $.ajax({ url: storeLocatorData.ajax_url, method: 'POST', data: { action: 'search_nearby_stores', nonce: storeLocatorData.nonce, latitude: this.currentLocation.lat, longitude: this.currentLocation.lng, radius: this.currentRadius, category: this.currentCategory } }); if (response.success) { this.allStores = response.data.stores; this.currentPage = 1; this.displayResults(); this.displayMarkers(); this.updatePagination(); } else { throw new Error(response.data); } } catch (error) { console.error('搜索失败:', error); alert('搜索失败,请稍后重试'); } finally { this.showLoading(false); } } displayResults() { const $resultsContainer = $('#stores-results'); const startIndex = (this.currentPage - 1) * this.storesPerPage; const endIndex = startIndex + this.storesPerPage; const currentStores = this.allStores.slice(startIndex, endIndex); if (currentStores.length === 0) { $resultsContainer.html(` <div class="no-results"> <p>在指定范围内未找到门店</p> <p>尝试扩大搜索半径或选择其他位置</p> </div> `); return; } let html = ''; currentStores.forEach((store, index) => { html += this.getStoreCardHtml(store, startIndex + index + 1); }); $resultsContainer.html(html); $('#results-count').text(this.allStores.length); // 绑定卡片点击事件 $('.store-card').on('click', (e) => { const storeId = $(e.currentTarget).data('store-id'); const store = this.allStores.find(s => s.id == storeId); if (store) { this.showStoreDetails(store); } }); } getStoreCardHtml(store, index) { const distance = parseFloat(store.distance).toFixed(1); const featuredClass = store.featured ? 'featured' : ''; return ` <div class="store-card ${featuredClass}" data-store-id="${store.id}"> <div class="store-card-header"> <span class="store-index">${index}</span> <h4 class="store-name">${this.escapeHtml(store.store_name)}</h4> ${store.featured ? '<span class="featured-badge">推荐</span>' : ''} </div> <div class="store-card-body"> <div class="store-info"> <p class="store-address"> <span class="dashicons dashicons-location"></span> ${this.escapeHtml(store.address)} </p> ${store.phone ? ` <p class="store-phone"> <span class="dashicons dashicons-phone"></span> ${this.escapeHtml(store.phone)} </p> ` : ''} <p class="store-distance"> <span class="dashicons dashicons-admin-site"></span> 距离:${distance} 公里 </p> </div> <div class="store-actions"> <button class="btn-details" data-store-id="${store.id}"> 查看详情 </button> <button class="btn-directions" data-store-id="${store.id}"> 路线规划 </button> </div> </div> </div> `; } displayMarkers() { // 清除现有标记 this.clearMarkers(); // 添加新标记 this.allStores.forEach(store => { const marker = new google.maps.Marker({ position: { lat: parseFloat(store.latitude), lng: parseFloat(store.longitude) }, map: this.map, title: store.store_name, icon: this.getMarkerIcon(store.featured) });
发表评论手把手教学:为WordPress集成智能化的网站内容合规性检查工具 引言:内容合规性检查在数字时代的重要性 在当今数字化时代,网站内容合规性已成为网站运营者不可忽视的重要议题。随着全球各地对网络内容监管的加强,从数据隐私保护到版权管理,从敏感信息过滤到行业规范遵守,网站内容合规性检查已成为确保网站安全运营、避免法律风险的关键环节。 对于使用WordPress构建的网站而言,作为全球最流行的内容管理系统,它承载着互联网上超过40%的网站内容。然而,WordPress本身并未内置全面的内容合规性检查功能,这给网站管理员带来了不小的挑战。手动检查每一条发布的内容不仅耗时耗力,而且容易遗漏潜在问题。 本文将详细介绍如何通过WordPress代码二次开发,集成智能化的网站内容合规性检查工具,帮助网站管理员自动化完成内容审核,确保网站内容符合法律法规和行业标准。 第一部分:理解WordPress内容合规性检查的核心需求 1.1 内容合规性的多维度考量 网站内容合规性检查涉及多个维度,主要包括: 法律法规合规性:确保内容不违反国家法律法规,不包含违法信息 版权合规性:检查内容是否侵犯他人知识产权 敏感信息过滤:识别并处理个人隐私信息、商业秘密等敏感内容 行业特定规范:根据不同行业要求检查内容合规性(如医疗、金融等行业) 平台政策遵守:确保内容符合社交媒体平台、搜索引擎等的要求 1.2 WordPress内容发布流程分析 要有效集成合规性检查工具,首先需要理解WordPress的内容发布流程: 内容创建与编辑阶段 预览与保存草稿阶段 发布与更新阶段 已发布内容的定期检查阶段 理想的合规性检查工具应在这些关键节点介入,提供实时或批量的内容检查功能。 1.3 现有解决方案的局限性 目前市场上有一些WordPress合规性检查插件,但它们往往存在以下问题: 功能单一,无法满足多维度检查需求 缺乏智能化,误报率较高 定制化程度低,难以适应特定行业需求 性能影响较大,拖慢网站速度 数据隐私保护不足,可能将内容发送到第三方服务器 因此,通过代码二次开发实现定制化的合规性检查工具,成为许多专业网站的选择。 第二部分:构建WordPress合规性检查工具的技术基础 2.1 WordPress插件开发基础 在开始开发合规性检查工具之前,需要掌握以下WordPress开发基础知识: WordPress插件结构:了解插件文件组织方式、主文件编写规范 动作钩子(Action Hooks)和过滤器钩子(Filter Hooks):这是WordPress扩展功能的核心机制 自定义数据库表:用于存储检查结果和历史记录 WordPress REST API:为前后端交互提供接口 安全性最佳实践:包括数据验证、权限检查、非ce验证等 2.2 合规性检查的技术实现方案 合规性检查可以通过以下技术方案实现: 本地规则引擎:基于正则表达式和关键词库的规则匹配 机器学习模型:使用预训练的NLP模型识别敏感内容 第三方API集成:调用专业合规性检查服务的API 混合方案:结合本地规则和云端智能检查 考虑到性能和数据隐私,我们建议采用混合方案,将基础检查放在本地,复杂分析通过API完成。 2.3 开发环境搭建 开始开发前,需要准备以下环境: 本地WordPress开发环境(推荐使用Local by Flywheel或Docker) 代码编辑器(VS Code、PHPStorm等) 版本控制系统(Git) PHP 7.4+和MySQL 5.6+ 必要的调试工具(Query Monitor、Debug Bar等) 第三部分:手把手创建WordPress合规性检查插件 3.1 插件基础结构创建 首先,创建插件的基本文件结构: wp-content/plugins/content-compliance-checker/ ├── content-compliance-checker.php # 主插件文件 ├── includes/ │ ├── class-compliance-checker.php # 主功能类 │ ├── class-rule-engine.php # 规则引擎类 │ ├── class-api-handler.php # API处理器类 │ └── class-db-manager.php # 数据库管理类 ├── admin/ │ ├── css/ │ │ └── admin-style.css # 后台样式 │ ├── js/ │ │ └── admin-script.js # 后台脚本 │ └── partials/ │ └── admin-display.php # 后台界面 ├── public/ │ ├── css/ │ │ └── public-style.css # 前台样式 │ └── js/ │ └── public-script.js # 前台脚本 ├── assets/ │ └── images/ # 图片资源 └── languages/ # 国际化文件 3.2 主插件文件编写 创建主插件文件content-compliance-checker.php: <?php /** * Plugin Name: 内容合规性检查工具 * Plugin URI: https://yourwebsite.com/content-compliance-checker * Description: 智能化的WordPress网站内容合规性检查工具 * Version: 1.0.0 * Author: 你的名字 * License: GPL v2 or later * Text Domain: content-compliance-checker */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CCC_VERSION', '1.0.0'); define('CCC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CCC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('CCC_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'CCC_'; $base_dir = CCC_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class_name, $len) !== 0) { return; } $relative_class = substr($class_name, $len); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require_once $file; } }); // 初始化插件 function ccc_init_plugin() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo __('内容合规性检查工具需要WordPress 5.0或更高版本。', 'content-compliance-checker'); echo '</p></div>'; }); return; } // 实例化主类 $compliance_checker = new CCC_Compliance_Checker(); $compliance_checker->init(); } add_action('plugins_loaded', 'ccc_init_plugin'); // 激活和停用钩子 register_activation_hook(__FILE__, 'ccc_activate_plugin'); register_deactivation_hook(__FILE__, 'ccc_deactivate_plugin'); function ccc_activate_plugin() { require_once CCC_PLUGIN_DIR . 'includes/class-db-manager.php'; CCC_DB_Manager::create_tables(); // 设置默认选项 $default_options = array( 'enable_auto_check' => true, 'check_on_publish' => true, 'check_on_update' => true, 'check_on_draft' => false, 'block_on_violation' => true, 'notify_admin' => true, 'sensitivity_level' => 'medium', 'api_key' => '', 'api_service' => 'local', ); add_option('ccc_settings', $default_options); // 创建默认规则 ccc_create_default_rules(); } function ccc_deactivate_plugin() { // 清理临时数据 wp_clear_scheduled_hook('ccc_daily_compliance_check'); } function ccc_create_default_rules() { $default_rules = array( array( 'rule_name' => '敏感词检测', 'rule_type' => 'keyword', 'rule_pattern' => '赌博|毒品|色情|暴力|恐怖主义', 'rule_action' => 'block', 'rule_priority' => 10, 'is_active' => 1 ), array( 'rule_name' => '电话号码检测', 'rule_type' => 'regex', 'rule_pattern' => '/bd{3}[-.]?d{3}[-.]?d{4}b/', 'rule_action' => 'flag', 'rule_priority' => 5, 'is_active' => 1 ), array( 'rule_name' => '邮箱地址检测', 'rule_type' => 'regex', 'rule_pattern' => '/b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}b/', 'rule_action' => 'flag', 'rule_priority' => 5, 'is_active' => 1 ) ); $existing_rules = get_option('ccc_rules', array()); if (empty($existing_rules)) { update_option('ccc_rules', $default_rules); } } 3.3 主功能类实现 创建includes/class-compliance-checker.php: <?php class CCC_Compliance_Checker { private $rule_engine; private $api_handler; private $db_manager; public function __construct() { $this->rule_engine = new CCC_Rule_Engine(); $this->api_handler = new CCC_API_Handler(); $this->db_manager = new CCC_DB_Manager(); } public function init() { // 添加内容检查钩子 add_filter('wp_insert_post_data', array($this, 'check_post_content'), 99, 2); add_action('save_post', array($this, 'check_post_on_save'), 10, 3); add_action('publish_post', array($this, 'check_post_on_publish'), 10, 2); // 添加上传文件检查 add_filter('wp_handle_upload_prefilter', array($this, 'check_uploaded_file')); // 添加评论检查 add_filter('preprocess_comment', array($this, 'check_comment_content')); // 添加后台管理界面 if (is_admin()) { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('add_meta_boxes', array($this, 'add_compliance_meta_box')); } // 添加定期检查任务 add_action('ccc_daily_compliance_check', array($this, 'run_daily_compliance_check')); if (!wp_next_scheduled('ccc_daily_compliance_check')) { wp_schedule_event(time(), 'daily', 'ccc_daily_compliance_check'); } // 初始化REST API端点 add_action('rest_api_init', array($this, 'register_rest_routes')); } public function check_post_content($data, $postarr) { $settings = get_option('ccc_settings'); // 检查是否启用自动检查 if (!$settings['enable_auto_check']) { return $data; } // 检查草稿(如果设置允许) if ($data['post_status'] === 'draft' && !$settings['check_on_draft']) { return $data; } $post_id = isset($postarr['ID']) ? $postarr['ID'] : 0; $content_to_check = $data['post_content'] . ' ' . $data['post_title']; // 执行合规性检查 $result = $this->perform_compliance_check($content_to_check, $post_id); if ($result['has_violation']) { if ($settings['block_on_violation']) { // 阻止发布并显示错误 add_filter('redirect_post_location', function($location) use ($result) { return add_query_arg('ccc_error', urlencode($result['message']), $location); }); // 将文章状态改为草稿 $data['post_status'] = 'draft'; } // 记录违规 $this->db_manager->log_violation(array( 'post_id' => $post_id, 'violation_type' => $result['violation_type'], 'violation_details' => json_encode($result['violations']), 'check_date' => current_time('mysql') )); // 发送通知(如果启用) if ($settings['notify_admin']) { $this->send_notification($post_id, $result); } } return $data; } public function check_post_on_save($post_id, $post, $update) { // 跳过自动保存和修订 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id)) { return; } // 检查用户权限 if (!current_user_can('edit_post', $post_id)) { return; } $settings = get_option('ccc_settings'); // 检查更新(如果设置允许) if ($update && !$settings['check_on_update']) { return; } // 如果文章已发布,执行额外检查 if ($post->post_status === 'publish') { $this->perform_compliance_check($post->post_content . ' ' . $post->post_title, $post_id); } } public function check_post_on_publish($post_id, $post) { $settings = get_option('ccc_settings'); if (!$settings['check_on_publish']) { return; } $result = $this->perform_compliance_check($post->post_content . ' ' . $post->post_title, $post_id); if ($result['has_violation']) { // 记录发布时的违规 update_post_meta($post_id, '_ccc_publish_violation', $result); } } public function check_uploaded_file($file) { $settings = get_option('ccc_settings'); if (!$settings['enable_auto_check']) { return $file; } // 检查文件类型 $allowed_types = array('jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'); $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_types)) { $file['error'] = '不支持的文件类型。只允许上传:' . implode(', ', $allowed_types); return $file; } // 检查文件内容(如果是图片,可以检查EXIF数据等) if (in_array($file_ext, array('jpg', 'jpeg', 'png', 'gif'))) { // 这里可以添加图片内容检查逻辑 // 例如检查图片是否包含敏感内容 } return $file; } public function check_comment_content($commentdata) { $settings = get_option('ccc_settings'); if (!$settings['enable_auto_check']) { return $commentdata; } $content_to_check = $commentdata['comment_content']; $result = $this->perform_compliance_check($content_to_check, 0, 'comment'); if ($result['has_violation']) { if ($settings['block_on_violation']) { wp_die(__('您的评论包含不合规内容,无法提交。', 'content-compliance-checker')); } else { // 标记评论为待审核 $commentdata['comment_approved'] = 0; add_comment_meta($commentdata['comment_ID'], '_ccc_flagged', true, true); } } return $commentdata; } private function perform_compliance_check($content, $post_id = 0, $content_type = 'post') { $result = array( 'has_violation' => false, 'violation_type' => '', 'violations' => array(), 'message' => '' ); // 1. 使用本地规则引擎检查 $rule_violations = $this->rule_engine->check_content($content); if (!empty($rule_violations)) { $result['has_violation'] = true; $result['violation_type'] = 'rule_violation'; $result['violations'] = array_merge($result['violations'], $rule_violations); } // 2. 使用API检查(如果配置了API) $settings = get_option('ccc_settings'); if (!empty($settings['api_key']) && $settings['api_service'] !== 'local') { $api_result = $this->api_handler->check_content($content, $content_type); if ($api_result['has_violation']) { $result['has_violation'] = true; $result['violation_type'] = $api_result['violation_type']; $result['violations'] = array_merge($result['violations'], $api_result['violations']); } } // 3. 生成用户友好的消息 if ($result['has_violation']) { $violation_count = count($result['violations']); $result['message'] = sprintf( __('内容包含%d处不合规问题:', 'content-compliance-checker'), $violation_count ); foreach ($result['violations'] as $violation) { $result['message'] .= "n- " . $violation['description']; } } // 4. 记录检查结果 $this->db_manager->log_check_result(array( 'post_id' => $post_id, 'content_type' => $content_type, 'check_result' => json_encode($result), 'check_date' => current_time('mysql') )); return $result; } private function send_notification($post_id, $check_result) { $admin_email = get_option('admin_email'); $post_title = get_the_title($post_id); $post_edit_url = admin_url('post.php?post=' . $post_id . '&action=edit'); $subject = sprintf(__('[合规性警报] 文章 "%s" 包含不合规内容', 'content-compliance-checker'), $post_title); $message = sprintf(__('文章 "%s" 在发布时检测到不合规内容。', 'content-compliance-checker'), $post_title) . "nn"; $message .= __('检测到的问题:', 'content-compliance-checker') . "n"; foreach ($check_result['violations'] as $violation) { $message .= '- ' . $violation['description'] . "n"; } $message .= "n" . __('文章编辑链接:', 'content-compliance-checker') . "n"; $message .= $post_edit_url . "nn"; $message .= __('此邮件由内容合规性检查工具自动发送。', 'content-compliance-checker'); wp_mail($admin_email, $subject, $message); } public function add_admin_menu() { add_menu_page( __('内容合规性检查', 'content-compliance-checker'), __('合规性检查', 'content-compliance-checker'), 'manage_options', 'ccc-dashboard', array($this, 'display_dashboard_page'), 'dashicons-shield', 30 ); add_submenu_page( 'ccc-dashboard', __('合规性设置', 'content-compliance-checker'), __('设置', 'content-compliance-checker'), 'manage_options', 'ccc-settings', array($this, 'display_settings_page') ); add_submenu_page( 'ccc-dashboard', __('检查规则', 'content-compliance-checker'), __('规则管理', 'content-compliance-checker'), 'manage_options', 'ccc-rules', array($this, 'display_rules_page') ); add_submenu_page( 'ccc-dashboard', __('检查记录', 'content-compliance-checker'), __('检查记录', 'content-compliance-checker'), 'manage_options', 'ccc-logs', array($this, 'display_logs_page') ); add_submenu_page( 'ccc-dashboard', __('批量检查', 'content-compliance-checker'), __('批量检查', 'content-compliance-checker'), 'manage_options', 'ccc-bulk-check', array($this, 'display_bulk_check_page') ); } public function display_dashboard_page() { include CCC_PLUGIN_DIR . 'admin/partials/dashboard.php'; } public function display_settings_page() { include CCC_PLUGIN_DIR . 'admin/partials/settings.php'; } public function display_rules_page() { include CCC_PLUGIN_DIR . 'admin/partials/rules.php'; } public function display_logs_page() { include CCC_PLUGIN_DIR . 'admin/partials/logs.php'; } public function display_bulk_check_page() { include CCC_PLUGIN_DIR . 'admin/partials/bulk-check.php'; } public function enqueue_admin_scripts($hook) { if (strpos($hook, 'ccc-') === false) { return; } wp_enqueue_style( 'ccc-admin-style', CCC_PLUGIN_URL . 'admin/css/admin-style.css', array(), CCC_VERSION ); wp_enqueue_script( 'ccc-admin-script', CCC_PLUGIN_URL . 'admin/js/admin-script.js', array('jquery', 'wp-util'), CCC_VERSION, true ); wp_localize_script('ccc-admin-script', 'ccc_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ccc_ajax_nonce') )); } public function add_compliance_meta_box() { $post_types = get_post_types(array('public' => true), 'names'); foreach ($post_types as $post_type) { add_meta_box( 'ccc-compliance-meta-box', __('内容合规性检查', 'content-compliance-checker'), array($this, 'render_compliance_meta_box'), $post_type, 'side', 'high' ); } } public function render_compliance_meta_box($post) { $check_results = $this->db_manager->get_post_check_results($post->ID); echo '<div id="ccc-compliance-status">'; if (empty($check_results)) { echo '<p>' . __('尚未检查此内容。', 'content-compliance-checker') . '</p>'; echo '<button type="button" class="button button-secondary" id="ccc-check-now">' . __('立即检查', 'content-compliance-checker') . '</button>'; } else { $latest_result = end($check_results); $result_data = json_decode($latest_result->check_result, true); if ($result_data['has_violation']) { echo '<div class="ccc-alert ccc-alert-error">'; echo '<p><strong>' . __('检测到不合规内容', 'content-compliance-checker') . '</strong></p>'; echo '<ul>'; foreach ($result_data['violations'] as $violation) { echo '<li>' . esc_html($violation['description']) . '</li>'; } echo '</ul>'; echo '</div>'; } else { echo '<div class="ccc-alert ccc-alert-success">'; echo '<p><strong>' . __('内容合规', 'content-compliance-checker') . '</strong></p>'; echo '<p>' . sprintf(__('最后检查时间:%s', 'content-compliance-checker'), $latest_result->check_date) . '</p>'; echo '</div>'; } echo '<button type="button" class="button button-secondary" id="ccc-recheck-now">' . __('重新检查', 'content-compliance-checker') . '</button>'; } echo '</div>'; } public function run_daily_compliance_check() { // 获取所有已发布的文章 $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids' ); $post_ids = get_posts($args); foreach ($post_ids as $post_id) { $post = get_post($post_id); $this->perform_compliance_check($post->post_content . ' ' . $post->post_title, $post_id); } // 记录批量检查完成 $this->db_manager->log_bulk_check(array( 'check_type' => 'daily', 'items_checked' => count($post_ids), 'check_date' => current_time('mysql') )); } public function register_rest_routes() { register_rest_route('ccc/v1', '/check-content', array( 'methods' => 'POST', 'callback' => array($this, 'rest_check_content'), 'permission_callback' => function() { return current_user_can('edit_posts'); }, 'args' => array( 'content' => array( 'required' => true, 'validate_callback' => function($param) { return is_string($param) && !empty($param); } ), 'content_type' => array( 'required' => false, 'default' => 'post' ) ) )); register_rest_route('ccc/v1', '/get-stats', array( 'methods' => 'GET', 'callback' => array($this, 'rest_get_stats'), 'permission_callback' => function() { return current_user_can('manage_options'); } )); } public function rest_check_content($request) { $content = $request->get_param('content'); $content_type = $request->get_param('content_type'); $result = $this->perform_compliance_check($content, 0, $content_type); return rest_ensure_response($result); } public function rest_get_stats() { $stats = $this->db_manager->get_compliance_stats(); return rest_ensure_response($stats); } } ### 3.4 规则引擎类实现 创建`includes/class-rule-engine.php`: <?phpclass CCC_Rule_Engine { private $rules; public function __construct() { $this->load_rules(); } private function load_rules() { $this->rules = get_option('ccc_rules', array()); // 按优先级排序 usort($this->rules, function($a, $b) { return $b['rule_priority'] - $a['rule_priority']; }); } public function check_content($content) { $violations = array(); foreach ($this->rules as $rule) { if (!$rule['is_active']) { continue; } $matches = $this->apply_rule($rule, $content); if (!empty($matches)) { $violations[] = array( 'rule_id' => $rule['rule_id'] ?? uniqid(), 'rule_name' => $rule['rule_name'], 'rule_type' => $rule['rule_type'], 'matches' => $matches, 'description' => $this->generate_violation_description($rule, $matches) ); } } return $violations; } private function apply_rule($rule, $content) { $matches = array(); switch ($rule['rule_type']) { case 'keyword': $keywords = explode('|', $rule['rule_pattern']); foreach ($keywords as $keyword) { $keyword = trim($keyword); if (stripos($content, $keyword) !== false) { $matches[] = array( 'type' => 'keyword', 'value' => $keyword, 'position' => stripos($content, $keyword) ); } } break; case 'regex': if (@preg_match_all($rule['rule_pattern'], $content, $regex_matches, PREG_OFFSET_CAPTURE)) { foreach ($regex_matches[0] as $match) { $matches[] = array( 'type' => 'regex', 'value' => $match[0], 'position' => $match[1] ); } } break; case 'ml': // 机器学习规则 - 可以集成TensorFlow PHP或调用外部API // 这里是一个简单的实现示例 $sentiment = $this->analyze_sentiment($content); if ($sentiment['score'] < -0.5) { // 非常负面的情绪 $matches[] = array( 'type' => 'sentiment', 'value' => $sentiment['label'], 'score' => $sentiment['score'] ); } break; } return $matches; } private function analyze_sentiment($text) { // 简化的情感分析 $negative_words = array('糟糕', '差劲', '讨厌', '恶心', '垃圾', '骗子', '诈骗'); $positive_words = array('优秀', '很好', '喜欢', '满意', '推荐', '超值', '完美'); $negative_count = 0; $positive_count = 0; $total_words = str_word_count($text); if ($total_words === 0) { return array('label' => '中性', 'score' => 0); } foreach ($negative_words as $word) { $negative_count += substr_count($text, $word); } foreach ($positive_words as $word) { $positive_count += substr_count($text, $word); } $score = ($positive_count - $negative_count) / max(1, $total_words); if ($score > 0.1) { $label = '积极'; } elseif ($score < -0.1) { $label = '消极'; } else { $label = '中性'; } return array('label' => $label, 'score' => $score); } private function generate_violation_description($rule, $matches) { $description = sprintf(__('违反规则 "%s"', 'content-compliance-checker'), $rule['rule_name']); if (count($matches) <= 3) { $description .= ':'; $match_values = array(); foreach ($matches as $match) { $match_values[] = $match['value']; } $description .= implode('、', array_unique($match_values)); } else { $description .= sprintf(__('(发现%d处匹配)', 'content-compliance-checker'), count($matches)); } return $description; } public function add_rule($rule_data) { $rule_id = uniqid(); $rule_data['rule_id'] = $rule_id; $rule_data['is_active'] = $rule_data['is_active'] ?? 1; $this->rules[] = $rule_data; $this->save_rules(); return $rule_id; } public function update_rule($rule_id, $rule_data) { foreach ($this->rules as &$rule) { if ($rule['rule_id'] === $rule_id) { $rule = array_merge($rule, $rule_data); $this->save_rules(); return true; } } return false; } public function delete_rule($rule_id) { $this->rules = array_filter($this->rules, function($rule) use ($rule_id) { return $rule['rule_id'] !== $rule_id; }); $this->save_rules(); return true; } public function get_rules() { return $this->rules; } public function get_rule($rule_id) { foreach ($this->rules as $rule) { if ($rule['rule_id'] === $rule_id) { return $rule; } } return null; } private function save_rules() { update_option('ccc_rules', $this->rules); } } ### 3.5 API处理器类实现 创建`includes/class-api-handler.php`: <?phpclass CCC_API_Handler { private $api_services = array( 'google_safe_browsing' => array( 'name' => 'Google Safe Browsing', 'endpoint' => 'https://safebrowsing.googleapis.com/v4/threatMatches:find', 'requires_key' => true ), 'microsoft_presidio' => array( 'name' => 'Microsoft Presidio', 'endpoint' => 'https://presidio.azurewebsites.net/analyze', 'requires_key' => false ), 'openai_moderation' => array( 'name' => 'OpenAI Moderation', 'endpoint' => 'https://api.openai.com/v1/moderations', 'requires_key' => true ) ); public function check_content($content, $content_type = 'post') { $settings = get_option('ccc_settings'); $api_service = $settings['api_service']; $api_key = $settings['api_key']; if ($api_service === 'local' || empty($api_key)) { return array('has_violation' => false, 'violations' => array()); } if (!isset($this->api_services[$api_service])) { return array( 'has_violation' => false, 'violations' => array(), 'error' => '未知的API服务' ); } $service_config = $this->api_services[$api_service]; try { switch ($api_service) { case 'google_safe_browsing': return $this->check_google_safe_browsing($content, $api_key); case 'openai_moderation': return $this->check_openai_moderation($content, $api_key); case 'microsoft_presidio': return $this->check_microsoft_presidio($content); default: return array('has_violation' => false, 'violations' => array()); } } catch (Exception $e) { error_log('合规性检查API错误:' . $e->getMessage()); return array( 'has_violation' => false, 'violations' => array(), 'error' => 'API检查失败:' . $e->getMessage() ); } } private function check_google_safe_browsing($content, $api_key) { $urls = $this->extract_urls($content); if (empty($urls)) { return array('has_violation' => false, 'violations' => array()); } $client_id = 'wordpress-compliance-checker'; $client_version = '1.0'; $threat_entries = array(); foreach ($urls as $url) { $threat_entries[] = array('url' => $url); }
发表评论详细指南:开发网站内嵌的在线预约课程与虚拟教室系统 摘要 随着在线教育的蓬勃发展,越来越多的教育机构和个人教师需要在自己的网站上集成在线预约课程与虚拟教室功能。本指南将详细介绍如何通过WordPress程序的代码二次开发,实现一个功能完整的在线教育系统。我们将从系统设计、功能规划、技术实现到测试部署,全面解析开发过程,帮助您打造一个集课程预约、支付管理、虚拟教室和学员管理于一体的专业在线教育平台。 一、项目概述与需求分析 1.1 在线教育市场趋势 近年来,全球在线教育市场呈现爆发式增长。根据市场研究数据显示,到2025年,全球在线教育市场规模预计将达到3500亿美元。这一趋势推动了教育机构和个人教师对在线教学平台的需求,特别是能够与现有网站无缝集成的解决方案。 1.2 系统核心需求 我们的在线预约课程与虚拟教室系统需要满足以下核心需求: 课程展示与管理:教师可以创建、编辑和发布课程信息 在线预约系统:学员可以浏览课程并预约上课时间 支付集成:支持多种支付方式完成课程费用支付 虚拟教室:集成视频会议功能,支持实时互动教学 学员管理:教师可以管理学员信息、跟踪学习进度 通知系统:自动发送预约确认、上课提醒等通知 评价与反馈:学员可以对课程和教师进行评价 1.3 WordPress作为开发平台的优势 WordPress作为全球最流行的内容管理系统,具有以下优势: 庞大的插件生态系统和主题资源 成熟的用户管理和权限控制系统 强大的扩展性和自定义能力 活跃的开发者社区和丰富的文档资源 良好的SEO基础和移动端适配 二、系统架构设计 2.1 整体架构设计 我们的系统将采用分层架构设计: 前端展示层 (WordPress主题) ↓ 业务逻辑层 (自定义插件) ↓ 数据访问层 (WordPress数据库) ↓ 第三方服务层 (支付、视频会议API) 2.2 数据库设计 我们需要扩展WordPress的数据库结构,添加以下自定义表: 课程表 (wp_courses):存储课程基本信息 课程安排表 (wp_course_schedules):存储课程的具体时间安排 预约记录表 (wp_bookings):存储学员的预约记录 支付记录表 (wp_payments):存储支付相关信息 虚拟教室表 (wp_virtual_classes):存储虚拟教室的会议信息 学员进度表 (wp_student_progress):跟踪学员学习进度 2.3 技术栈选择 后端:PHP 7.4+,WordPress 5.8+ 前端:HTML5,CSS3,JavaScript (ES6+),jQuery 数据库:MySQL 5.7+ 或 MariaDB 10.3+ 视频会议:集成Zoom API或Jitsi Meet 支付网关:Stripe,PayPal或国内支付平台API 实时通信:WebSocket或Socket.io for实时通知 三、开发环境搭建与准备工作 3.1 本地开发环境配置 安装本地服务器环境: 使用XAMPP、MAMP或Local by Flywheel 确保PHP版本≥7.4,MySQL≥5.7 安装WordPress: # 下载最新版WordPress wget https://wordpress.org/latest.zip unzip latest.zip # 配置数据库连接信息 配置开发工具: 代码编辑器:VS Code或PHPStorm 版本控制:Git 调试工具:Query Monitor、Debug Bar插件 3.2 创建自定义插件结构 在wp-content/plugins/目录下创建插件文件夹结构: online-education-system/ ├── online-education-system.php # 主插件文件 ├── includes/ # 核心功能文件 │ ├── class-database.php # 数据库操作类 │ ├── class-course-manager.php # 课程管理类 │ ├── class-booking-system.php # 预约系统类 │ ├── class-payment-handler.php # 支付处理类 │ ├── class-virtual-classroom.php # 虚拟教室类 │ └── class-notification.php # 通知系统类 ├── admin/ # 后台管理文件 │ ├── css/ │ ├── js/ │ └── partials/ ├── public/ # 前端文件 │ ├── css/ │ ├── js/ │ └── templates/ ├── assets/ # 静态资源 └── vendor/ # 第三方库 3.3 插件主文件配置 创建主插件文件online-education-system.php: <?php /** * Plugin Name: 在线教育系统 * Plugin URI: https://yourwebsite.com/ * Description: 在线预约课程与虚拟教室系统 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: online-education */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('OES_VERSION', '1.0.0'); define('OES_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('OES_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { if (strpos($class_name, 'OES_') === 0) { $file = OES_PLUGIN_DIR . 'includes/class-' . strtolower(str_replace('_', '-', $class_name)) . '.php'; if (file_exists($file)) { require_once $file; } } }); // 初始化插件 function oes_init() { // 检查依赖 if (!function_exists('get_bloginfo')) { wp_die('此插件需要WordPress环境。'); } // 初始化核心类 if (class_exists('OES_Database')) { $database = new OES_Database(); $database->create_tables(); } // 加载文本域 load_plugin_textdomain('online-education', false, dirname(plugin_basename(__FILE__)) . '/languages'); } add_action('plugins_loaded', 'oes_init'); // 激活插件时执行的操作 register_activation_hook(__FILE__, function() { require_once OES_PLUGIN_DIR . 'includes/class-database.php'; $database = new OES_Database(); $database->create_tables(); // 创建必要的页面 oes_create_pages(); // 设置默认选项 update_option('oes_version', OES_VERSION); update_option('oes_currency', 'CNY'); update_option('oes_timezone', 'Asia/Shanghai'); }); // 停用插件时执行的操作 register_deactivation_hook(__FILE__, function() { // 清理临时数据 wp_clear_scheduled_hook('oes_daily_maintenance'); }); // 创建系统必要页面 function oes_create_pages() { $pages = [ 'course-list' => [ 'title' => '课程列表', 'content' => '[oes_course_list]' ], 'booking' => [ 'title' => '课程预约', 'content' => '[oes_booking_form]' ], 'student-dashboard' => [ 'title' => '学员中心', 'content' => '[oes_student_dashboard]' ], 'teacher-dashboard' => [ 'title' => '教师中心', 'content' => '[oes_teacher_dashboard]' ] ]; foreach ($pages as $slug => $page) { if (!get_page_by_path($slug)) { wp_insert_post([ 'post_title' => $page['title'], 'post_content' => $page['content'], 'post_status' => 'publish', 'post_type' => 'page', 'post_name' => $slug ]); } } } 四、核心功能模块开发 4.1 课程管理系统 4.1.1 数据库表结构 创建课程相关数据库表: // includes/class-database.php class OES_Database { public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 课程表 $courses_table = $wpdb->prefix . 'oes_courses'; $sql = "CREATE TABLE IF NOT EXISTS $courses_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, description text, teacher_id bigint(20) NOT NULL, category varchar(100), price decimal(10,2) DEFAULT 0.00, duration int DEFAULT 60, max_students int DEFAULT 10, thumbnail_url varchar(500), status varchar(20) DEFAULT 'draft', created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY teacher_id (teacher_id), KEY status (status) ) $charset_collate;"; // 课程安排表 $schedules_table = $wpdb->prefix . 'oes_course_schedules'; $sql .= "CREATE TABLE IF NOT EXISTS $schedules_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, course_id mediumint(9) NOT NULL, start_time datetime NOT NULL, end_time datetime NOT NULL, recurring_type varchar(20) DEFAULT 'once', recurring_days varchar(100), max_capacity int DEFAULT 10, booked_count int DEFAULT 0, status varchar(20) DEFAULT 'available', meeting_id varchar(200), meeting_password varchar(100), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY course_id (course_id), KEY start_time (start_time), KEY status (status) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } 4.1.2 课程管理后台界面 创建课程管理后台界面: // admin/partials/course-management.php class OES_Course_Manager { public function add_admin_menu() { add_menu_page( '课程管理', '在线教育', 'manage_options', 'oes-courses', [$this, 'render_course_list'], 'dashicons-welcome-learn-more', 30 ); add_submenu_page( 'oes-courses', '添加新课程', '添加课程', 'manage_options', 'oes-add-course', [$this, 'render_add_course'] ); add_submenu_page( 'oes-courses', '课程安排', '课程安排', 'manage_options', 'oes-schedules', [$this, 'render_schedule_management'] ); } public function render_course_list() { ?> <div class="wrap"> <h1 class="wp-heading-inline">课程管理</h1> <a href="<?php echo admin_url('admin.php?page=oes-add-course'); ?>" class="page-title-action">添加新课程</a> <div class="oes-course-list"> <?php global $wpdb; $courses_table = $wpdb->prefix . 'oes_courses'; $courses = $wpdb->get_results("SELECT * FROM $courses_table ORDER BY created_at DESC"); if ($courses) { echo '<table class="wp-list-table widefat fixed striped">'; echo '<thead><tr> <th>ID</th> <th>课程名称</th> <th>教师</th> <th>价格</th> <th>状态</th> <th>操作</th> </tr></thead>'; echo '<tbody>'; foreach ($courses as $course) { $teacher = get_userdata($course->teacher_id); echo '<tr> <td>' . $course->id . '</td> <td><strong>' . esc_html($course->title) . '</strong></td> <td>' . ($teacher ? $teacher->display_name : '未知') . '</td> <td>' . number_format($course->price, 2) . ' ' . get_option('oes_currency', 'CNY') . '</td> <td><span class="oes-status ' . $course->status . '">' . $this->get_status_text($course->status) . '</span></td> <td> <a href="' . admin_url('admin.php?page=oes-add-course&id=' . $course->id) . '">编辑</a> | <a href="#" class="oes-delete-course" data-id="' . $course->id . '">删除</a> </td> </tr>'; } echo '</tbody></table>'; } else { echo '<div class="notice notice-info"><p>暂无课程,请添加第一个课程。</p></div>'; } ?> </div> </div> <?php } public function render_add_course() { $course_id = isset($_GET['id']) ? intval($_GET['id']) : 0; $course = null; if ($course_id) { global $wpdb; $courses_table = $wpdb->prefix . 'oes_courses'; $course = $wpdb->get_row($wpdb->prepare("SELECT * FROM $courses_table WHERE id = %d", $course_id)); } ?> <div class="wrap"> <h1><?php echo $course_id ? '编辑课程' : '添加新课程'; ?></h1> <form method="post" action="" id="oes-course-form"> <?php wp_nonce_field('oes_save_course', 'oes_course_nonce'); ?> <input type="hidden" name="course_id" value="<?php echo $course_id; ?>"> <table class="form-table"> <tr> <th scope="row"><label for="course_title">课程标题</label></th> <td><input type="text" id="course_title" name="course_title" class="regular-text" value="<?php echo $course ? esc_attr($course->title) : ''; ?>" required></td> </tr> <tr> <th scope="row"><label for="course_description">课程描述</label></th> <td> <?php $description = $course ? $course->description : ''; wp_editor($description, 'course_description', [ 'textarea_name' => 'course_description', 'media_buttons' => true, 'textarea_rows' => 10 ]); ?> </td> </tr> <tr> <th scope="row"><label for="course_teacher">授课教师</label></th> <td> <select id="course_teacher" name="course_teacher" required> <option value="">选择教师</option> <?php $teachers = get_users(['role__in' => ['administrator', 'teacher']]); foreach ($teachers as $teacher) { $selected = $course && $course->teacher_id == $teacher->ID ? 'selected' : ''; echo '<option value="' . $teacher->ID . '" ' . $selected . '>' . $teacher->display_name . '</option>'; } ?> </select> </td> </tr> <tr> <th scope="row"><label for="course_price">课程价格</label></th> <td> <input type="number" id="course_price" name="course_price" step="0.01" min="0" value="<?php echo $course ? number_format($course->price, 2) : '0.00'; ?>" required> <span class="description"><?php echo get_option('oes_currency', 'CNY'); ?></span> </td> </tr> <tr> <th scope="row"><label for="course_duration">课程时长</label></th> <td> <input type="number" id="course_duration" name="course_duration" min="15" max="480" value="<?php echo $course ? $course->duration : 60; ?>" required> <span class="description">分钟</span> </td> </tr> <tr> <th scope="row"><label for="max_students">最大学员数</label></th> <td> <input type="number" id="max_students" name="max_students" min="1" max="100" value="<?php echo $course ? $course->max_students : 10; ?>" required> </td> </tr> <tr> <th scope="row"><label for="course_status">课程状态</label></th> <td> <select id="course_status" name="course_status"> <option value="draft" <?php echo $course && $course->status == 'draft' ? 'selected' : ''; ?>>草稿</option> <option value="published" <?php echo $course && $course->status == 'published' ? 'selected' : ''; ?>>已发布</option> <option value="archived" <?php echo $course && $course->status == 'archived' ? 'selected' : ''; ?>>已归档</option> </select> </td> </tr> </table> <p class="submit"> type="submit" name="submit_course" class="button button-primary" value="保存课程"> </p> </form> </div> <?php } private function get_status_text($status) { $statuses = [ 'draft' => '草稿', 'published' => '已发布', 'archived' => '已归档' ]; return isset($statuses[$status]) ? $statuses[$status] : $status; } } ### 4.2 在线预约系统 #### 4.2.1 预约数据库表设计 // 在class-database.php中继续添加class OES_Database { public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 预约记录表 $bookings_table = $wpdb->prefix . 'oes_bookings'; $sql = "CREATE TABLE IF NOT EXISTS $bookings_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, schedule_id mediumint(9) NOT NULL, student_id bigint(20) NOT NULL, booking_time datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', payment_status varchar(20) DEFAULT 'unpaid', payment_id varchar(100), notes text, attended tinyint(1) DEFAULT 0, rating int DEFAULT 0, review text, PRIMARY KEY (id), KEY schedule_id (schedule_id), KEY student_id (student_id), KEY status (status), KEY payment_status (payment_status) ) $charset_collate;"; // 支付记录表 $payments_table = $wpdb->prefix . 'oes_payments'; $sql .= "CREATE TABLE IF NOT EXISTS $payments_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, booking_id mediumint(9) NOT NULL, amount decimal(10,2) NOT NULL, currency varchar(10) DEFAULT 'CNY', payment_method varchar(50), transaction_id varchar(100), status varchar(20) DEFAULT 'pending', payment_time datetime, refund_amount decimal(10,2) DEFAULT 0.00, refund_time datetime, PRIMARY KEY (id), KEY booking_id (booking_id), KEY transaction_id (transaction_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } #### 4.2.2 前端预约界面 创建前端预约短代码和模板: // includes/class-booking-system.phpclass OES_Booking_System { public function register_shortcodes() { add_shortcode('oes_course_list', [$this, 'render_course_list']); add_shortcode('oes_booking_form', [$this, 'render_booking_form']); add_shortcode('oes_student_dashboard', [$this, 'render_student_dashboard']); } public function render_course_list($atts) { global $wpdb; $atts = shortcode_atts([ 'category' => '', 'teacher' => '', 'limit' => 10 ], $atts); $courses_table = $wpdb->prefix . 'oes_courses'; $where = "WHERE status = 'published'"; if (!empty($atts['category'])) { $where .= $wpdb->prepare(" AND category = %s", $atts['category']); } if (!empty($atts['teacher'])) { $where .= $wpdb->prepare(" AND teacher_id = %d", $atts['teacher']); } $courses = $wpdb->get_results("SELECT * FROM $courses_table $where ORDER BY created_at DESC LIMIT " . intval($atts['limit'])); ob_start(); ?> <div class="oes-course-list-container"> <div class="oes-course-filters"> <form method="get" class="oes-filter-form"> <input type="text" name="search" placeholder="搜索课程..." value="<?php echo isset($_GET['search']) ? esc_attr($_GET['search']) : ''; ?>"> <select name="category"> <option value="">所有分类</option> <?php $categories = $wpdb->get_col("SELECT DISTINCT category FROM $courses_table WHERE category IS NOT NULL AND category != ''"); foreach ($categories as $category) { $selected = isset($_GET['category']) && $_GET['category'] == $category ? 'selected' : ''; echo '<option value="' . esc_attr($category) . '" ' . $selected . '>' . esc_html($category) . '</option>'; } ?> </select> <button type="submit" class="oes-filter-button">筛选</button> </form> </div> <div class="oes-courses-grid"> <?php if ($courses): ?> <?php foreach ($courses as $course): ?> <?php $teacher = get_userdata($course->teacher_id); $schedules_table = $wpdb->prefix . 'oes_course_schedules'; $next_schedule = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $schedules_table WHERE course_id = %d AND start_time > NOW() AND status = 'available' ORDER BY start_time ASC LIMIT 1", $course->id )); ?> <div class="oes-course-card"> <div class="oes-course-thumbnail"> <?php if ($course->thumbnail_url): ?> <img src="<?php echo esc_url($course->thumbnail_url); ?>" alt="<?php echo esc_attr($course->title); ?>"> <?php else: ?> <div class="oes-course-thumbnail-placeholder"> <span class="dashicons dashicons-welcome-learn-more"></span> </div> <?php endif; ?> </div> <div class="oes-course-content"> <h3 class="oes-course-title"><?php echo esc_html($course->title); ?></h3> <div class="oes-course-meta"> <span class="oes-course-teacher"> <span class="dashicons dashicons-businessperson"></span> <?php echo $teacher ? esc_html($teacher->display_name) : '未知教师'; ?> </span> <span class="oes-course-duration"> <span class="dashicons dashicons-clock"></span> <?php echo intval($course->duration); ?>分钟 </span> <span class="oes-course-price"> <span class="dashicons dashicons-money-alt"></span> <?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?> </span> </div> <div class="oes-course-excerpt"> <?php echo wp_trim_words(strip_tags($course->description), 30); ?> </div> <div class="oes-course-actions"> <?php if ($next_schedule): ?> <a href="<?php echo add_query_arg(['course_id' => $course->id], get_permalink(get_page_by_path('booking'))); ?>" class="oes-book-button button"> 立即预约 </a> <?php else: ?> <button class="oes-book-button button" disabled>暂无排课</button> <?php endif; ?> <a href="<?php echo add_query_arg(['course_id' => $course->id], get_permalink()); ?>" class="oes-details-button button"> 查看详情 </a> </div> </div> </div> <?php endforeach; ?> <?php else: ?> <div class="oes-no-courses"> <p>暂无可用课程。</p> </div> <?php endif; ?> </div> </div> <?php return ob_get_clean(); } public function render_booking_form() { if (!is_user_logged_in()) { return '<div class="oes-login-required"><p>请先<a href="' . wp_login_url(get_permalink()) . '">登录</a>以预约课程。</p></div>'; } $course_id = isset($_GET['course_id']) ? intval($_GET['course_id']) : 0; if (!$course_id) { return '<div class="oes-booking-error"><p>请选择要预约的课程。</p></div>'; } global $wpdb; $courses_table = $wpdb->prefix . 'oes_courses'; $course = $wpdb->get_row($wpdb->prepare("SELECT * FROM $courses_table WHERE id = %d AND status = 'published'", $course_id)); if (!$course) { return '<div class="oes-booking-error"><p>课程不存在或不可用。</p></div>'; } $schedules_table = $wpdb->prefix . 'oes_course_schedules'; $schedules = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $schedules_table WHERE course_id = %d AND start_time > NOW() AND status = 'available' ORDER BY start_time ASC", $course_id )); ob_start(); ?> <div class="oes-booking-container"> <div class="oes-booking-header"> <h2>预约课程: <?php echo esc_html($course->title); ?></h2> <div class="oes-course-info"> <p><strong>教师:</strong> <?php echo get_userdata($course->teacher_id)->display_name; ?></p> <p><strong>价格:</strong> <?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></p> <p><strong>时长:</strong> <?php echo intval($course->duration); ?> 分钟</p> </div> </div> <?php if ($schedules): ?> <form id="oes-booking-form" method="post" action="<?php echo admin_url('admin-ajax.php'); ?>"> <?php wp_nonce_field('oes_process_booking', 'oes_booking_nonce'); ?> <input type="hidden" name="action" value="oes_process_booking"> <input type="hidden" name="course_id" value="<?php echo $course_id; ?>"> <div class="oes-schedule-selection"> <h3>选择上课时间</h3> <div class="oes-schedules-list"> <?php foreach ($schedules as $schedule): ?> <?php $available_seats = $schedule->max_capacity - $schedule->booked_count; $start_time = strtotime($schedule->start_time); $end_time = strtotime($schedule->end_time); ?> <div class="oes-schedule-item"> <input type="radio" id="schedule_<?php echo $schedule->id; ?>" name="schedule_id" value="<?php echo $schedule->id; ?>" <?php echo $available_seats <= 0 ? 'disabled' : ''; ?> required> <label for="schedule_<?php echo $schedule->id; ?>"> <div class="oes-schedule-date"> <?php echo date('Y年m月d日', $start_time); ?> </div> <div class="oes-schedule-time"> <?php echo date('H:i', $start_time); ?> - <?php echo date('H:i', $end_time); ?> </div> <div class="oes-schedule-availability"> <?php if ($available_seats > 0): ?> <span class="oes-available">剩余 <?php echo $available_seats; ?> 个名额</span> <?php else: ?> <span class="oes-full">已满额</span> <?php endif; ?> </div> </label> </div> <?php endforeach; ?> </div> </div> <div class="oes-booking-notes"> <h3>备注信息 (可选)</h3> <textarea name="booking_notes" rows="3" placeholder="如有特殊要求,请在此说明..."></textarea> </div> <div class="oes-payment-method"> <h3>选择支付方式</h3> <div class="oes-payment-options"> <label class="oes-payment-option"> <input type="radio" name="payment_method" value="alipay" checked> <span class="oes-payment-icon">支付宝</span> </label> <label class="oes-payment-option"> <input type="radio" name="payment_method" value="wechat"> <span class="oes-payment-icon">微信支付</span> </label> <label class="oes-payment-option"> <input type="radio" name="payment_method" value="paypal"> <span class="oes-payment-icon">PayPal</span> </label> </div> </div> <div class="oes-booking-summary"> <h3>订单摘要</h3> <table class="oes-summary-table"> <tr> <td>课程费用:</td> <td class="oes-price"><?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></td> </tr> <tr> <td>服务费:</td> <td class="oes-price">0.00 <?php echo get_option('oes_currency', 'CNY'); ?></td> </tr> <tr class="oes-total"> <td>总计:</td> <td class="oes-price"><?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></td> </tr> </table> </div> <div class="oes-booking-submit"> <button type="submit" class="oes-submit-button button button-primary"> 确认预约并支付 </button> <p class="oes-terms-notice"> 点击确认即表示您同意我们的<a href="<?php echo get_permalink(get_page_by_path('terms')); ?>">服务条款</a> </p> </div> </form> <div id="oes-booking-result" style="display:none;"></div> <?php else: ?> <div class="oes-no-schedules"> <p>该课程暂无可用时间安排。</p> <a href="<?php echo get_permalink(get_page_by_path('course-list')); ?>" class="button"> 返回课程列表 </a> </div> <?php endif; ?> </div> <?php return ob_get_clean(); } } ### 4.3 虚拟教室集成 #### 4.3.1 Zoom API集成 // includes/class-virtual-classroom.phpclass OES_Virtual_Classroom { private $zoom_api_key; private $zoom_api_secret; private $zoom_account_id; public function __construct() { $this->zoom_api_key = get_option('oes_zoom_api_key', ''); $this->zoom_api_secret = get_option('oes_zoom_api_secret', ''); $this->zoom_account_id = get_option('oes_zoom_account_id', ''); add_action('wp_ajax_oes_create_meeting', [$this, 'create_meeting']); add_action('wp_ajax_oes_get_meeting_info', [$this, 'get_meeting_info']); } public function create_meeting_for_schedule($schedule_id) { global $wpdb; $schedules_table = $wpdb->prefix . 'oes_course_schedules'; $courses_table = $wpdb->prefix . 'oes_courses'; $schedule = $wpdb->get_row($wpdb->prepare( "SELECT s.*, c.title as course_title, c.teacher_id FROM $schedules_table s LEFT JOIN $courses_table c ON s.course_id = c.id WHERE s.id = %d", $schedule_id )); if (!$schedule) { return false; } $teacher = get_userdata($schedule->teacher_id); $start_time = new DateTime($schedule->start_time, new DateTimeZone('UTC')); $meeting_data = [ 'topic' => $schedule->course_title . ' - ' . $start_time->format('Y-m-d H:i'), 'type' => 2, // 预定会议 'start_time' => $start_time->format('Y-m-dTH:i:sZ'), 'duration' => intval($schedule->end_time) - intval($schedule->start_time), 'timezone' => get_option('timezone_string', 'Asia/Shanghai'), 'password' => wp_generate_password(6, false), 'settings' => [ 'host_video' => true, 'participant_video' => true, 'join_before_host' => false, 'mute_upon_entry' => true, 'waiting_room' => true, 'auto_recording' => 'cloud' ] ]; $response = $this->call_zoom_api('users/me/meetings', 'POST', $meeting_data); if ($response && isset($response['id'])) { $wpdb->update( $schedules_table, [ 'meeting_id' => $response['id'], 'meeting_password' => $response['password'] ], ['id' => $schedule_id] ); return [ 'meeting_id' => $response['id'], 'join_url' => $response['join_url'], 'password' => $response['password'] ]; } return false; } private function call_zoom_api
发表评论一步步实现:为WordPress打造内部工单系统与客户支持门户 引言:为什么选择WordPress作为工单系统平台? 在当今数字化商业环境中,高效的客户支持和内部协作系统是企业成功的关键因素。许多企业每年花费大量资金购买第三方客户支持软件,如Zendesk、Freshdesk等,但这些解决方案往往价格昂贵且缺乏定制灵活性。 WordPress作为全球最流行的内容管理系统,占据了互联网上超过40%的网站份额。其强大的插件生态和开放源代码特性,使其成为构建定制化业务工具的绝佳平台。通过WordPress二次开发,我们可以以较低成本打造一个完全符合企业特定需求的内部工单系统与客户支持门户。 本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完整的工单系统,同时集成多种常用互联网小工具,提升客户支持效率和团队协作能力。 第一章:项目规划与需求分析 1.1 确定系统核心功能 在开始开发前,我们需要明确工单系统的基本功能需求: 用户端功能: 用户注册与登录 工单提交表单 工单状态跟踪 历史工单查看 文件上传功能 实时通知系统 管理端功能: 工单分配与转交 优先级管理 分类与标签系统 知识库集成 团队绩效统计 自动化规则设置 系统集成需求: 电子邮件通知 实时聊天小工具 常见问题(FAQ)模块 客户满意度调查 报表与分析工具 1.2 技术架构设计 基于WordPress的工单系统将采用以下技术架构: 前端界面:使用WordPress主题模板系统,结合Bootstrap框架确保响应式设计 数据存储:利用WordPress自定义文章类型(CPT)存储工单数据 用户管理:扩展WordPress原生用户系统,添加支持人员角色 通信机制:结合AJAX实现无刷新交互,使用WP Mail函数处理邮件通知 安全措施:实施WordPress非ce验证、数据消毒和权限检查 第二章:搭建基础开发环境 2.1 创建自定义插件 首先,我们需要创建一个独立的插件来容纳所有工单系统功能: <?php /** * Plugin Name: 企业工单与支持系统 * Plugin URI: https://yourcompany.com/ * Description: 基于WordPress的定制化工单与客户支持系统 * Version: 1.0.0 * Author: 您的公司 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('TICKET_SYSTEM_VERSION', '1.0.0'); define('TICKET_SYSTEM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('TICKET_SYSTEM_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once TICKET_SYSTEM_PLUGIN_DIR . 'includes/class-ticket-system.php'; function run_ticket_system() { $plugin = new Ticket_System(); $plugin->run(); } run_ticket_system(); 2.2 创建自定义文章类型存储工单 在插件中创建自定义文章类型来存储工单信息: class Ticket_System { public function __construct() { // 初始化钩子 add_action('init', array($this, 'register_ticket_post_type')); add_action('init', array($this, 'register_ticket_taxonomies')); } public function register_ticket_post_type() { $labels = array( 'name' => '工单', 'singular_name' => '工单', 'menu_name' => '工单系统', 'add_new' => '新建工单', 'add_new_item' => '添加新工单', 'edit_item' => '编辑工单', 'new_item' => '新工单', 'view_item' => '查看工单', 'search_items' => '搜索工单', 'not_found' => '未找到工单', 'not_found_in_trash' => '回收站中无工单' ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'ticket'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 5, 'menu_icon' => 'dashicons-tickets-alt', 'supports' => array('title', 'editor', 'author', 'comments'), 'show_in_rest' => true, // 启用Gutenberg编辑器支持 ); register_post_type('ticket', $args); } public function register_ticket_taxonomies() { // 工单状态分类 register_taxonomy( 'ticket_status', 'ticket', array( 'labels' => array( 'name' => '工单状态', 'singular_name' => '状态', 'search_items' => '搜索状态', 'all_items' => '所有状态', 'edit_item' => '编辑状态', 'update_item' => '更新状态', 'add_new_item' => '添加新状态', 'new_item_name' => '新状态名称', 'menu_name' => '状态' ), 'hierarchical' => true, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => array('slug' => 'ticket-status'), 'default_terms' => array('open', 'in-progress', 'resolved', 'closed') ) ); // 工单优先级分类 register_taxonomy( 'ticket_priority', 'ticket', array( 'labels' => array( 'name' => '优先级', 'singular_name' => '优先级', 'search_items' => '搜索优先级', 'all_items' => '所有优先级', 'edit_item' => '编辑优先级', 'update_item' => '更新优先级', 'add_new_item' => '添加新优先级', 'new_item_name' => '新优先级名称', 'menu_name' => '优先级' ), 'hierarchical' => true, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => array('slug' => 'ticket-priority'), 'default_terms' => array('low', 'medium', 'high', 'urgent') ) ); } } 第三章:构建用户前端界面 3.1 创建工单提交表单 在前端创建用户提交工单的表单: class Ticket_Frontend { public function __construct() { add_shortcode('ticket_submission_form', array($this, 'render_ticket_form')); add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); add_action('wp_ajax_submit_ticket', array($this, 'handle_ticket_submission')); add_action('wp_ajax_nopriv_submit_ticket', array($this, 'handle_ticket_submission')); } public function render_ticket_form() { // 检查用户是否登录 if (!is_user_logged_in()) { return '<div class="ticket-login-required">请先<a href="' . wp_login_url(get_permalink()) . '">登录</a>以提交工单</div>'; } ob_start(); ?> <div class="ticket-submission-container"> <h2>提交新工单</h2> <form id="ticket-submission-form" method="post" enctype="multipart/form-data"> <?php wp_nonce_field('submit_ticket_action', 'ticket_nonce'); ?> <div class="form-group"> <label for="ticket_title">问题标题 *</label> <input type="text" id="ticket_title" name="ticket_title" required class="form-control" placeholder="简要描述您的问题"> </div> <div class="form-group"> <label for="ticket_description">详细描述 *</label> <textarea id="ticket_description" name="ticket_description" rows="6" required class="form-control" placeholder="请详细描述您遇到的问题,包括步骤、期望结果和实际结果"></textarea> </div> <div class="form-row"> <div class="form-group col-md-6"> <label for="ticket_category">问题分类</label> <select id="ticket_category" name="ticket_category" class="form-control"> <option value="technical">技术问题</option> <option value="billing">账单问题</option> <option value="account">账户问题</option> <option value="feature">功能建议</option> <option value="other">其他</option> </select> </div> <div class="form-group col-md-6"> <label for="ticket_priority">优先级</label> <select id="ticket_priority" name="ticket_priority" class="form-control"> <option value="low">低 - 常规问题</option> <option value="medium" selected>中 - 需要尽快解决</option> <option value="high">高 - 严重影响使用</option> <option value="urgent">紧急 - 系统完全无法使用</option> </select> </div> </div> <div class="form-group"> <label for="ticket_attachments">附件 (可选)</label> <input type="file" id="ticket_attachments" name="ticket_attachments[]" multiple class="form-control-file"> <small class="form-text text-muted">支持图片、文档等格式,单个文件不超过5MB</small> </div> <div class="form-group"> <button type="submit" class="btn btn-primary btn-submit-ticket">提交工单</button> <div class="spinner-border text-primary d-none" role="status" id="ticket-submit-spinner"> <span class="sr-only">提交中...</span> </div> </div> <div id="ticket-submission-response" class="alert d-none"></div> </form> </div> <?php return ob_get_clean(); } public function handle_ticket_submission() { // 验证nonce if (!isset($_POST['ticket_nonce']) || !wp_verify_nonce($_POST['ticket_nonce'], 'submit_ticket_action')) { wp_die('安全验证失败'); } // 验证用户权限 if (!is_user_logged_in()) { wp_send_json_error(array('message' => '请先登录')); } $current_user = wp_get_current_user(); // 准备工单数据 $ticket_data = array( 'post_title' => sanitize_text_field($_POST['ticket_title']), 'post_content' => wp_kses_post($_POST['ticket_description']), 'post_status' => 'publish', 'post_type' => 'ticket', 'post_author' => $current_user->ID, 'meta_input' => array( '_ticket_category' => sanitize_text_field($_POST['ticket_category']), '_ticket_priority' => sanitize_text_field($_POST['ticket_priority']), '_ticket_status' => 'open' ) ); // 插入工单 $ticket_id = wp_insert_post($ticket_data); if (is_wp_error($ticket_id)) { wp_send_json_error(array('message' => '创建工单失败: ' . $ticket_id->get_error_message())); } // 处理附件上传 $this->handle_attachments($ticket_id); // 设置分类术语 wp_set_object_terms($ticket_id, sanitize_text_field($_POST['ticket_priority']), 'ticket_priority'); wp_set_object_terms($ticket_id, 'open', 'ticket_status'); // 发送通知邮件 $this->send_ticket_notification($ticket_id, $current_user); wp_send_json_success(array( 'message' => '工单提交成功!', 'ticket_id' => $ticket_id, 'redirect_url' => add_query_arg('ticket_id', $ticket_id, get_permalink(get_page_by_path('my-tickets'))) )); } } 3.2 创建用户工单管理面板 为用户创建查看和管理工单的面板: class Ticket_Dashboard { public function __construct() { add_shortcode('ticket_dashboard', array($this, 'render_ticket_dashboard')); } public function render_ticket_dashboard() { if (!is_user_logged_in()) { return '<p>请先登录查看您的工单。</p>'; } $current_user = wp_get_current_user(); $user_tickets = $this->get_user_tickets($current_user->ID); ob_start(); ?> <div class="ticket-dashboard-container"> <h2>我的工单</h2> <div class="dashboard-actions mb-4"> <a href="<?php echo get_permalink(get_page_by_path('submit-ticket')); ?>" class="btn btn-success"> <i class="fas fa-plus-circle"></i> 提交新工单 </a> <button class="btn btn-outline-secondary" id="refresh-tickets"> <i class="fas fa-sync-alt"></i> 刷新列表 </button> </div> <div class="ticket-filters mb-3"> <div class="btn-group" role="group"> <button type="button" class="btn btn-outline-primary active" data-filter="all">全部</button> <button type="button" class="btn btn-outline-warning" data-filter="open">进行中</button> <button type="button" class="btn btn-outline-success" data-filter="resolved">已解决</button> <button type="button" class="btn btn-outline-secondary" data-filter="closed">已关闭</button> </div> </div> <div class="table-responsive"> <table class="table table-hover ticket-table"> <thead> <tr> <th>工单ID</th> <th>标题</th> <th>状态</th> <th>优先级</th> <th>创建时间</th> <th>最后更新</th> <th>操作</th> </tr> </thead> <tbody> <?php if (empty($user_tickets)): ?> <tr> <td colspan="7" class="text-center">暂无工单记录</td> </tr> <?php else: ?> <?php foreach ($user_tickets as $ticket): ?> <?php $status = wp_get_post_terms($ticket->ID, 'ticket_status', array('fields' => 'names')); $priority = wp_get_post_terms($ticket->ID, 'ticket_priority', array('fields' => 'names')); $status_class = $this->get_status_class($status[0] ?? 'open'); $priority_class = $this->get_priority_class($priority[0] ?? 'medium'); ?> <tr class="ticket-row" data-status="<?php echo strtolower($status[0] ?? ''); ?>"> <td>#<?php echo $ticket->ID; ?></td> <td> <a href="<?php echo get_permalink($ticket->ID); ?>" class="ticket-title"> <?php echo esc_html($ticket->post_title); ?> </a> </td> <td> <span class="badge badge-<?php echo $status_class; ?>"> <?php echo $status[0] ?? 'Open'; ?> </span> </td> <td> <span class="badge badge-<?php echo $priority_class; ?>"> <?php echo $priority[0] ?? 'Medium'; ?> </span> </td> <td><?php echo get_the_date('Y-m-d H:i', $ticket->ID); ?></td> <td><?php echo get_the_modified_date('Y-m-d H:i', $ticket->ID); ?></td> <td> <a href="<?php echo get_permalink($ticket->ID); ?>" class="btn btn-sm btn-outline-primary"> <i class="fas fa-eye"></i> 查看 </a> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> <div class="ticket-statistics mt-4 p-3 bg-light rounded"> <h5>工单统计</h5> <div class="row"> <div class="col-md-3"> <div class="stat-box text-center"> <div class="stat-number"><?php echo count($user_tickets); ?></div> <div class="stat-label">总工单数</div> </div> </div> <div class="col-md-3"> <div class="stat-box text-center"> <div class="stat-number"><?php echo $this->count_tickets_by_status($user_tickets, 'open'); ?></div> <div class="stat-label">进行中</div> </div> </div> <div class="col-md-3"> <div class="stat-box text-center"> <div class="stat-number"><?php echo $this->count_tickets_by_status($user_tickets, 'resolved'); ?></div> <div class="stat-label">已解决</div> </div> </div> <div class="col-md-3"> <div class="stat-box text-center"> <div class="stat-number"><?php echo $this->count_tickets_by_status($user_tickets, 'closed'); ?></div> <div class="stat-label">已关闭</div> </div> </div> </div> </div> </div> <script> jQuery(document).ready(function($) { // 工单筛选功能 $('.ticket-filters button').on('click', function() { var filter = $(this).data('filter'); // 更新按钮状态 $('.ticket-filters button').removeClass('active'); $(this).addClass('active'); // 筛选工单行 if (filter === 'all') { $('.ticket-row').show(); } else { $('.ticket-row').hide(); $('.ticket-row[data-status="' + filter + '"]').show(); } }); // 刷新工单列表 $('#refresh-tickets').on('click', function() { location.reload(); }); }); </script> <?php return ob_get_clean(); } private function get_user_tickets($user_id) { $args = array( 'post_type' => 'ticket', 'author' => $user_id, 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC' ); return get_posts($args); } private function count_tickets_by_status($tickets, $status) { $count = 0; foreach ($tickets as $ticket) { $ticket_status = wp_get_post_terms($ticket->ID, 'ticket_status', array('fields' => 'names')); if (!empty($ticket_status) && strtolower($ticket_status[0]) === $status) { $count++; } } return $count; } private function get_status_class($status) { $status = strtolower($status); switch ($status) { case 'open': return 'warning'; case 'in-progress': return 'info'; case 'resolved': return 'success'; case 'closed': return 'secondary'; default: return 'light'; } } private function get_priority_class($priority) { $priority = strtolower($priority); switch ($priority) { case 'low': return 'info'; case 'medium': return 'primary'; case 'high': return 'warning'; case 'urgent': return 'danger'; default: return 'light'; } } } ## 第四章:构建管理后台功能 ### 4.1 创建工单管理界面 为支持团队创建专门的管理界面: class Ticket_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_filter('manage_ticket_posts_columns', array($this, 'add_ticket_columns')); add_action('manage_ticket_posts_custom_column', array($this, 'manage_ticket_columns'), 10, 2); add_action('add_meta_boxes', array($this, 'add_ticket_meta_boxes')); add_action('save_post_ticket', array($this, 'save_ticket_meta')); } public function add_admin_menu() { add_menu_page( '工单系统', '工单系统', 'manage_options', 'ticket-system', array($this, 'render_admin_dashboard'), 'dashicons-tickets-alt', 30 ); add_submenu_page( 'ticket-system', '工单统计', '统计报表', 'manage_options', 'ticket-statistics', array($this, 'render_statistics_page') ); add_submenu_page( 'ticket-system', '系统设置', '设置', 'manage_options', 'ticket-settings', array($this, 'render_settings_page') ); } public function render_admin_dashboard() { global $wpdb; // 获取统计数据 $total_tickets = wp_count_posts('ticket'); $open_tickets = $total_tickets->publish ?? 0; // 获取最近工单 $recent_tickets = get_posts(array( 'post_type' => 'ticket', 'posts_per_page' => 10, 'orderby' => 'date', 'order' => 'DESC' )); // 获取支持人员绩效 $support_performance = $this->get_support_performance(); ob_start(); ?> <div class="wrap ticket-admin-dashboard"> <h1 class="wp-heading-inline">工单系统仪表板</h1> <div class="dashboard-widgets"> <div class="row"> <div class="col-md-3"> <div class="card text-white bg-primary mb-3"> <div class="card-body"> <h5 class="card-title">总工单数</h5> <p class="card-text display-4"><?php echo array_sum((array)$total_tickets); ?></p> </div> </div> </div> <div class="col-md-3"> <div class="card text-white bg-warning mb-3"> <div class="card-body"> <h5 class="card-title">进行中</h5> <p class="card-text display-4"><?php echo $open_tickets; ?></p> </div> </div> </div> <div class="col-md-3"> <div class="card text-white bg-success mb-3"> <div class="card-body"> <h5 class="card-title">已解决</h5> <p class="card-text display-4"><?php echo $total_tickets->resolved ?? 0; ?></p> </div> </div> </div> <div class="col-md-3"> <div class="card text-white bg-info mb-3"> <div class="card-body"> <h5 class="card-title">平均响应时间</h5> <p class="card-text display-4"><?php echo $this->get_average_response_time(); ?>h</p> </div> </div> </div> </div> <div class="row"> <div class="col-md-8"> <div class="card mb-3"> <div class="card-header"> <h5>最近工单</h5> </div> <div class="card-body"> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>标题</th> <th>客户</th> <th>状态</th> <th>分配至</th> <th>创建时间</th> </tr> </thead> <tbody> <?php foreach ($recent_tickets as $ticket): ?> <?php $author = get_userdata($ticket->post_author); $assigned_to = get_post_meta($ticket->ID, '_ticket_assigned_to', true); $assigned_user = $assigned_to ? get_userdata($assigned_to) : null; $status = wp_get_post_terms($ticket->ID, 'ticket_status', array('fields' => 'names')); ?> <tr> <td>#<?php echo $ticket->ID; ?></td> <td> <a href="<?php echo get_edit_post_link($ticket->ID); ?>"> <?php echo esc_html($ticket->post_title); ?> </a> </td> <td><?php echo $author ? $author->display_name : '未知用户'; ?></td> <td> <span class="badge badge-<?php echo $this->get_status_class($status[0] ?? 'open'); ?>"> <?php echo $status[0] ?? 'Open'; ?> </span> </td> <td> <?php if ($assigned_user): ?> <?php echo $assigned_user->display_name; ?> <?php else: ?> <span class="text-muted">未分配</span> <?php endif; ?> </td> <td><?php echo get_the_date('m-d H:i', $ticket->ID); ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> <div class="col-md-4"> <div class="card mb-3"> <div class="card-header"> <h5>支持人员绩效</h5> </div> <div class="card-body"> <table class="table table-sm"> <thead> <tr> <th>姓名</th> <th>处理数</th> <th>解决率</th> </tr> </thead> <tbody> <?php foreach ($support_performance as $performance): ?> <tr> <td><?php echo $performance['name']; ?></td> <td><?php echo $performance['ticket_count']; ?></td> <td> <div class="progress"> <div class="progress-bar" role="progressbar" style="width: <?php echo $performance['resolution_rate']; ?>%"> <?php echo number_format($performance['resolution_rate'], 1); ?>% </div> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <div class="card"> <div class="card-header"> <h5>快速操作</h5> </div> <div class="card-body"> <a href="<?php echo admin_url('post-new.php?post_type=ticket'); ?>" class="btn btn-primary btn-block mb-2"> 创建新工单 </a> <a href="<?php echo admin_url('edit.php?post_type=ticket'); ?>" class="btn btn-secondary btn-block mb-2"> 查看所有工单 </a> <a href="<?php echo admin_url('admin.php?page=ticket-statistics'); ?>" class="btn btn-info btn-block"> 查看详细统计 </a> </div> </div> </div> </div> </div> </div> <?php echo ob_get_clean(); } private function get_support_performance() { // 获取所有支持人员 $support_users = get_users(array( 'role__in' => array('administrator', 'editor', 'support_agent'), 'fields' => array('ID', 'display_name') )); $performance_data = array(); foreach ($support_users as $user) { // 获取分配给该用户的工单 $assigned_tickets = get_posts(array( 'post_type' => 'ticket', 'meta_key' => '_ticket_assigned_to', 'meta_value' => $user->ID, 'posts_per_page' => -1 )); $resolved_count = 0; foreach ($assigned_tickets as $ticket) { $status = wp_get_post_terms($ticket->ID, 'ticket_status', array('fields' => 'names')); if (!empty($status) && in_array(strtolower($status[0]), array('resolved', 'closed'))) { $resolved_count++; } } $total_count = count($assigned_tickets); $resolution_rate = $total_count > 0 ? ($resolved_count / $total_count) * 100 : 0; $performance_data[] = array( 'name' => $user->display_name, 'ticket_count' => $total_count, 'resolved_count' => $resolved_count, 'resolution_rate' => $resolution_rate ); } return $performance_data; } } ### 4.2 添加工单分配与跟踪功能 class Ticket_Assignment { public function __construct() { add_action('add_meta_boxes_ticket', array($this, 'add_assignment_meta_box')); add_action('wp_ajax_assign_ticket', array($this, 'handle_ticket_assignment')); add_action('wp_ajax_get_available_agents', array($this, 'get_available_agents')); } public function add_assignment_meta_box() { add_meta_box( 'ticket_assignment', '工单分配', array($this, 'render_assignment_meta_box'), 'ticket', 'side', 'high' ); } public function render_assignment_meta_box($post) { $assigned_to = get_post_meta($post->ID, '_ticket_assigned_to', true); $current_assignee = $assigned_to ? get_userdata($assigned_to) : null; // 获取可分配的支持人员 $available_agents = get_users(array( 'role__in' => array('administrator', 'editor', 'support_agent'), 'fields' => array('ID', 'display_name', 'user_email') )); wp_nonce_field('assign_ticket_action', 'assignment_nonce'); ?> <div class="ticket-assignment-container"> <div class="current-assignee mb-3"> <strong>当前负责人:</strong> <?php if ($current_assignee): ?> <div class="assignee-info mt-1 p-2 bg-light rounded"> <div class="d-flex align-items-center"> <div class="assignee-avatar mr-2"> <?php echo get_avatar($current_assignee->ID, 32); ?> </div> <div> <div class="assignee-name"><?php echo $current_assignee->display_name; ?></div> <div class="assignee-email text-muted small"><?php echo $current_assignee->user_email; ?></div> </div> </div> </div> <?php else: ?> <div class="text-muted mt-1">未分配</div> <?php endif; ?> </div> <div class="assignment-form"> <label for="assign_to"><strong>分配给:</strong></label> <select id="assign_to" name="assign_to" class="form-control"> <option value="">选择支持人员...</option> <?php foreach ($available_agents as $agent): ?> <option value="<?php echo $agent->ID; ?>" <?php selected($assigned_to, $agent->ID); ?>> <?php echo $agent->display_name; ?> (<?php echo $agent->user_email; ?>) </option> <?php endforeach; ?> </select> <div class="assignment-actions mt-3"> <button type="button" class="button button-primary" id="assign-ticket-btn"> 分配工单 </button> <?php if ($assigned_to): ?> <button type="button" class="button button-link" id="unassign-ticket-btn"> 取消分配 </button> <?php endif; ?> </div> <div class="assignment-history mt-3"> <strong>分配历史:</strong> <?php $this->display_assignment_history($post->ID); ?> </div> </div> <div id="assignment-response" class="mt-2"></div> </div> <script> jQuery(document).ready(function($) { $('#assign-ticket-btn').on('click', function() { var ticketId = <?php echo $post->ID; ?>; var assignTo = $('#assign_to').val(); var nonce = $('#assignment_nonce').val(); if (!assignTo) { alert('请选择要分配的支持人员'); return; } $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'assign_ticket', ticket_id: ticketId, assign_to: assignTo, nonce: nonce }, beforeSend: function() { $('#assign-ticket-btn').prop('disabled', true).text('分配中...'); }, success: function(response) { if (response.success) { $('#assignment-response').html('<div class="notice notice-success"><p>' + response.data.message + '</p></div>'); location.reload(); } else { $('#assignment-response').html('<div class="notice notice-error"><p>' + response.data.message + '</p></div>'); } }, complete: function() { $('#assign-ticket-btn').prop('disabled', false).text('分配工单'); } }); }); }); </script> <?php } public function handle_ticket_assignment() { // 验证nonce和权限 if (!check_ajax_referer('assign_ticket_action', 'nonce', false)) { wp_send_json_error(array('
发表评论WordPress开发教程:集成网站自动化数据采集与信息聚合工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:WordPress的无限可能 在当今数字化时代,网站已不仅仅是信息展示的窗口,更是功能集成与服务的平台。WordPress作为全球最受欢迎的内容管理系统,占据了互联网上超过43%的网站份额。然而,许多用户仅将其视为简单的博客或内容发布工具,未能充分挖掘其作为强大开发平台的潜力。 实际上,通过代码二次开发,WordPress可以转型为功能丰富的应用平台,集成自动化数据采集、信息聚合以及各种实用小工具。本教程将深入探讨如何通过WordPress开发,实现网站自动化数据采集与信息聚合,并集成常用互联网小工具功能,帮助您将普通网站升级为智能化的多功能平台。 第一部分:WordPress开发环境搭建与基础知识 1.1 开发环境配置 在开始WordPress二次开发之前,首先需要搭建合适的开发环境。推荐使用本地开发环境如XAMPP、MAMP或Local by Flywheel,这些工具提供了完整的PHP、MySQL和Web服务器环境。 对于代码编辑器,Visual Studio Code是目前最受欢迎的选择,配合以下扩展插件可极大提升开发效率: PHP Intelephense(PHP代码智能提示) WordPress Snippet(WordPress代码片段) GitLens(Git版本控制集成) 此外,建议安装调试工具如Query Monitor和Debug Bar,这些插件能帮助您在开发过程中实时监控数据库查询、PHP错误和性能数据。 1.2 WordPress主题与插件架构理解 要有效进行WordPress二次开发,必须深入理解其核心架构: 主题系统:WordPress主题控制网站的外观和显示方式。子主题开发是自定义功能而不影响父主题更新的最佳实践。创建子主题只需在wp-content/themes目录下建立新文件夹,包含style.css和functions.php文件。 插件系统:插件用于扩展WordPress功能,独立于主题。良好的插件应遵循单一职责原则,专注于特定功能的实现。 钩子机制:动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)是WordPress扩展性的核心。动作钩子允许在特定点执行自定义代码,而过滤器钩子允许修改数据。 // 动作钩子示例 add_action('wp_head', 'custom_head_code'); function custom_head_code() { echo '<meta name="custom-tag" content="value">'; } // 过滤器钩子示例 add_filter('the_title', 'custom_title_format'); function custom_title_format($title) { return '📌 ' . $title; } 1.3 自定义文章类型与字段 对于数据采集和聚合,自定义文章类型(CPT)和字段是基础。CPT允许您创建不同于标准文章和页面的内容类型,如“新闻”、“产品”或“数据条目”。 // 注册自定义文章类型 function register_data_collection_cpt() { $labels = array( 'name' => '采集数据', 'singular_name' => '数据条目' ); $args = array( 'labels' => $labels, 'public' => true, 'has_archive' => true, 'supports' => array('title', 'editor', 'custom-fields'), 'show_in_rest' => true, // 启用Gutenberg编辑器支持 ); register_post_type('collected_data', $args); } add_action('init', 'register_data_collection_cpt'); 对于更复杂的字段需求,推荐使用Advanced Custom Fields(ACF)插件或Meta Box框架,它们提供了直观的字段管理界面。 第二部分:自动化数据采集系统开发 2.1 数据采集策略与规划 在开发数据采集功能前,需要明确采集目标、数据源和更新频率。常见的数据源包括: RSS/Atom订阅源 公开API接口 网页抓取(需遵守robots.txt和法律法规) 社交媒体平台 公开数据库 设计数据采集系统时,应考虑以下因素: 数据源稳定性与可用性 采集频率与服务器负载平衡 数据去重与更新机制 错误处理与日志记录 版权与法律合规性 2.2 WordPress定时任务系统 WordPress提供了内置的定时任务系统WP-Cron,可用于定期执行数据采集任务。但需要注意的是,WP-Cron基于页面访问触发,对于精确的定时任务可能不够可靠。对于高要求的采集任务,建议使用系统级的Cron任务。 // 注册定时采集任务 function register_data_collection_cron() { // 确保事件未已安排 if (!wp_next_scheduled('hourly_data_collection')) { // 安排每小时执行一次 wp_schedule_event(time(), 'hourly', 'hourly_data_collection'); } } add_action('wp', 'register_data_collection_cron'); // 定义采集函数 function perform_data_collection() { // 数据采集逻辑 $data_sources = get_option('data_collection_sources', array()); foreach ($data_sources as $source) { $collected_data = fetch_data_from_source($source); process_and_store_data($collected_data); } // 记录采集日志 update_option('last_collection_time', current_time('mysql')); } add_action('hourly_data_collection', 'perform_data_collection'); // 添加自定义时间间隔 function add_custom_cron_intervals($schedules) { $schedules['every_10_minutes'] = array( 'interval' => 600, 'display' => __('每10分钟') ); return $schedules; } add_filter('cron_schedules', 'add_custom_cron_intervals'); 2.3 数据采集方法实现 RSS/Atom订阅采集: function fetch_rss_feed($feed_url) { include_once(ABSPATH . WPINC . '/feed.php'); // 获取RSS订阅 $rss = fetch_feed($feed_url); if (is_wp_error($rss)) { error_log('RSS采集错误: ' . $rss->get_error_message()); return false; } $max_items = $rss->get_item_quantity(10); // 获取最新10条 $rss_items = $rss->get_items(0, $max_items); $collected_data = array(); foreach ($rss_items as $item) { $data_entry = array( 'title' => $item->get_title(), 'content' => $item->get_content(), 'excerpt' => $item->get_description(), 'source_url' => $item->get_permalink(), 'publish_date' => $item->get_date('Y-m-d H:i:s'), 'author' => $item->get_author() ? $item->get_author()->get_name() : '', 'categories' => array() ); // 获取分类 $categories = $item->get_categories(); if ($categories) { foreach ($categories as $category) { $data_entry['categories'][] = $category->get_label(); } } $collected_data[] = $data_entry; } return $collected_data; } API数据采集: function fetch_api_data($api_url, $api_key = '') { $args = array( 'timeout' => 30, 'headers' => $api_key ? array('Authorization' => 'Bearer ' . $api_key) : array() ); $response = wp_remote_get($api_url, $args); if (is_wp_error($response)) { error_log('API请求错误: ' . $response->get_error_message()); return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log('JSON解析错误: ' . json_last_error_msg()); return false; } return $data; } 网页内容抓取:对于网页抓取,建议使用WordPress内置的HTTP API配合DOM解析: function scrape_website_content($url, $selector) { // 获取网页内容 $response = wp_remote_get($url, array('timeout' => 30)); if (is_wp_error($response)) { return false; } $html = wp_remote_retrieve_body($response); // 使用DOMDocument解析HTML $dom = new DOMDocument(); @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); $xpath = new DOMXPath($dom); $elements = $xpath->query($selector); $results = array(); foreach ($elements as $element) { $results[] = $dom->saveHTML($element); } return $results; } 注意:网页抓取应遵守robots.txt规则,尊重版权,并控制请求频率以避免对目标服务器造成负担。 2.4 数据存储与处理 采集到的数据需要有效存储和处理。WordPress提供了多种存储选项: 自定义数据库表:对于大量结构化数据,创建自定义数据库表可能更高效: function create_data_collection_table() { global $wpdb; $table_name = $wpdb->prefix . 'collected_data'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, source_id varchar(100) NOT NULL, title text NOT NULL, content longtext, source_url varchar(500), collected_date datetime DEFAULT CURRENT_TIMESTAMP, processed tinyint(1) DEFAULT 0, PRIMARY KEY (id), KEY source_id (source_id), KEY processed (processed) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } register_activation_hook(__FILE__, 'create_data_collection_table'); 使用自定义文章类型存储:对于大多数情况,使用自定义文章类型配合自定义字段是更简单的方法: function store_collected_data_as_post($data) { // 检查是否已存在相同内容(避免重复) $existing_post = get_posts(array( 'post_type' => 'collected_data', 'meta_query' => array( array( 'key' => 'source_url', 'value' => $data['source_url'] ) ), 'posts_per_page' => 1 )); if (!empty($existing_post)) { // 更新现有文章 $post_id = $existing_post[0]->ID; $post_data = array( 'ID' => $post_id, 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_excerpt' => $data['excerpt'], 'post_status' => 'publish' ); wp_update_post($post_data); } else { // 创建新文章 $post_data = array( 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_excerpt' => $data['excerpt'], 'post_status' => 'publish', 'post_type' => 'collected_data', 'post_date' => $data['publish_date'] ?: current_time('mysql') ); $post_id = wp_insert_post($post_data); } // 保存元数据 if ($post_id && !is_wp_error($post_id)) { update_post_meta($post_id, 'source_url', $data['source_url']); update_post_meta($post_id, 'original_author', $data['author']); update_post_meta($post_id, 'collected_date', current_time('mysql')); // 保存分类 if (!empty($data['categories'])) { $category_ids = array(); foreach ($data['categories'] as $category_name) { $term = term_exists($category_name, 'category'); if (!$term) { $term = wp_insert_term($category_name, 'category'); } if (!is_wp_error($term)) { $category_ids[] = (int)$term['term_id']; } } wp_set_post_terms($post_id, $category_ids, 'category'); } } return $post_id; } 2.5 数据清洗与去重 采集的数据通常需要清洗和去重处理: function clean_and_deduplicate_data($data_array) { $unique_data = array(); $content_hashes = array(); foreach ($data_array as $data) { // 内容清洗 $data['title'] = sanitize_text_field($data['title']); $data['content'] = wp_kses_post($data['content']); // 过滤允许的HTML // 去除HTML标签获取纯文本用于去重 $content_text = wp_strip_all_tags($data['content']); $content_hash = md5($content_text); // 检查是否重复 if (!in_array($content_hash, $content_hashes)) { $content_hashes[] = $content_hash; $unique_data[] = $data; } } return $unique_data; } 第三部分:信息聚合与展示系统 3.1 数据聚合策略 信息聚合不仅仅是收集数据,更是将多源数据整合为有价值的信息流。常见的聚合策略包括: 时间线聚合:按时间顺序展示多源数据 主题聚合:按主题或分类组织相关内容 来源聚合:按数据源分类展示 混合聚合:结合多种维度展示数据 3.2 创建聚合页面模板 在WordPress主题中创建专门的聚合页面模板: <?php /* Template Name: 数据聚合页面 */ get_header(); // 获取聚合配置 $sources = get_field('aggregation_sources'); // 假设使用ACF字段 $layout = get_field('aggregation_layout', 'grid'); // 网格或列表布局 $items_per_page = get_field('items_per_page', 20); $current_page = max(1, get_query_var('paged')); ?> <div class="aggregation-container"> <header class="aggregation-header"> <h1><?php the_title(); ?></h1> <div class="aggregation-filters"> <select id="source-filter"> <option value="all">所有来源</option> <?php foreach ($sources as $source): ?> <option value="<?php echo esc_attr($source['value']); ?>"> <?php echo esc_html($source['label']); ?> </option> <?php endforeach; ?> </select> <select id="date-filter"> <option value="all">全部时间</option> <option value="today">今天</option> <option value="week">本周</option> <option value="month">本月</option> </select> </div> </header> <div class="aggregation-content" id="aggregation-results"> <?php // 查询聚合数据 $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $items_per_page, 'paged' => $current_page, 'orderby' => 'date', 'order' => 'DESC' ); // 添加源过滤 if (isset($_GET['source']) && $_GET['source'] !== 'all') { $args['meta_query'] = array( array( 'key' => 'data_source', 'value' => sanitize_text_field($_GET['source']), 'compare' => '=' ) ); } // 添加日期过滤 if (isset($_GET['date_filter'])) { $date_filter = sanitize_text_field($_GET['date_filter']); $date_query = array(); switch ($date_filter) { case 'today': $date_query = array( 'after' => 'today midnight', 'inclusive' => true ); break; case 'week': $date_query = array( 'after' => '1 week ago' ); break; case 'month': $date_query = array( 'after' => '1 month ago' ); break; } if (!empty($date_query)) { $args['date_query'] = $date_query; } } $aggregation_query = new WP_Query($args); if ($aggregation_query->have_posts()): echo $layout === 'grid' ? '<div class="aggregation-grid">' : '<div class="aggregation-list">'; while ($aggregation_query->have_posts()): $aggregation_query->the_post(); include(locate_template('template-parts/aggregation-item.php')); endwhile; echo '</div>'; // 分页 echo '<div class="aggregation-pagination">'; echo paginate_links(array( 'total' => $aggregation_query->max_num_pages, 'current' => $current_page, 'prev_text' => '« 上一页', 'next_text' => '下一页 »' )); echo '</div>'; wp_reset_postdata(); else: echo '<p class="no-results">暂无聚合数据</p>'; endif; ?> </div> </div> <script> // AJAX过滤功能 jQuery(document).ready(function($) { $('#source-filter, #date-filter').on('change', function() { var source = $('#source-filter').val(); var dateFilter = $('#date-filter').val(); $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { data', source: source, date_filter: dateFilter, page: 1 }, beforeSend: function() { $('#aggregation-results').html('<div class="loading">加载中...</div>'); }, success: function(response) { $('#aggregation-results').html(response); } }); }); });</script> <?phpget_footer(); #### 3.3 实时数据聚合与更新 对于需要实时展示的数据,可以结合AJAX和WebSocket技术: // 实时数据推送端点function realtime_aggregation_endpoint() { register_rest_route('aggregation/v1', '/realtime', array( 'methods' => 'GET', 'callback' => 'get_realtime_aggregation_data', 'permission_callback' => '__return_true' )); }add_action('rest_api_init', 'realtime_aggregation_endpoint'); function get_realtime_aggregation_data($request) { $last_id = $request->get_param('last_id'); $category = $request->get_param('category'); $args = array( 'post_type' => 'collected_data', 'posts_per_page' => 10, 'orderby' => 'date', 'order' => 'DESC' ); if ($last_id) { $args['date_query'] = array( 'after' => get_the_date('Y-m-d H:i:s', $last_id) ); } if ($category && $category !== 'all') { $args['tax_query'] = array( array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => $category ) ); } $query = new WP_Query($args); $data = array(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $data[] = array( 'id' => get_the_ID(), 'title' => get_the_title(), 'excerpt' => get_the_excerpt(), 'date' => get_the_date('Y-m-d H:i:s'), 'source' => get_post_meta(get_the_ID(), 'data_source', true), 'url' => get_permalink() ); } wp_reset_postdata(); } return rest_ensure_response(array( 'success' => true, 'data' => $data, 'timestamp' => current_time('timestamp') )); } 前端实时更新实现: // 前端实时数据监听class RealtimeAggregation { constructor(options) { this.options = Object.assign({ endpoint: '/wp-json/aggregation/v1/realtime', interval: 30000, // 30秒 container: '#realtime-feed', lastId: 0 }, options); this.init(); } init() { this.container = document.querySelector(this.options.container); if (!this.container) return; this.loadInitialData(); this.startPolling(); } async loadInitialData() { try { const response = await fetch(`${this.options.endpoint}?last_id=0`); const data = await response.json(); if (data.success && data.data.length > 0) { this.renderData(data.data); this.options.lastId = data.data[0].id; } } catch (error) { console.error('加载数据失败:', error); } } startPolling() { setInterval(() => { this.checkForUpdates(); }, this.options.interval); } async checkForUpdates() { try { const response = await fetch( `${this.options.endpoint}?last_id=${this.options.lastId}` ); const data = await response.json(); if (data.success && data.data.length > 0) { this.prependData(data.data); this.options.lastId = data.data[0].id; // 显示新数据通知 this.showNewItemsNotification(data.data.length); } } catch (error) { console.error('检查更新失败:', error); } } renderData(items) { items.forEach(item => { const itemElement = this.createItemElement(item); this.container.appendChild(itemElement); }); } prependData(items) { items.reverse().forEach(item => { const itemElement = this.createItemElement(item); this.container.insertBefore(itemElement, this.container.firstChild); }); } createItemElement(item) { const div = document.createElement('div'); div.className = 'realtime-item'; div.innerHTML = ` <div class="item-header"> <span class="source-badge">${this.escapeHtml(item.source)}</span> <span class="item-time">${this.formatTime(item.date)}</span> </div> <h3 class="item-title"> <a href="${this.escapeHtml(item.url)}">${this.escapeHtml(item.title)}</a> </h3> <p class="item-excerpt">${this.escapeHtml(item.excerpt)}</p> `; return div; } showNewItemsNotification(count) { // 实现新数据通知逻辑 const notification = document.createElement('div'); notification.className = 'new-items-notification'; notification.innerHTML = ` 有${count}条新内容,<a href="#" class="show-new">点击查看</a> `; notification.querySelector('.show-new').addEventListener('click', (e) => { e.preventDefault(); notification.remove(); }); document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatTime(dateString) { const date = new Date(dateString); const now = new Date(); const diff = Math.floor((now - date) / 1000); // 秒 if (diff < 60) return '刚刚'; if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`; if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`; return date.toLocaleDateString(); } } // 初始化实时聚合document.addEventListener('DOMContentLoaded', () => { new RealtimeAggregation({ container: '#realtime-feed', interval: 15000 // 15秒 }); }); #### 3.4 智能推荐与个性化聚合 基于用户行为实现个性化内容推荐: class PersonalizedAggregation { private $user_id; private $preferences; public function __construct($user_id = null) { $this->user_id = $user_id ?: get_current_user_id(); $this->load_user_preferences(); } private function load_user_preferences() { if ($this->user_id) { $this->preferences = get_user_meta($this->user_id, 'aggregation_preferences', true); } if (empty($this->preferences)) { $this->preferences = array( 'preferred_categories' => array(), 'preferred_sources' => array(), 'reading_history' => array(), 'click_pattern' => array() ); } } public function track_user_interaction($post_id, $interaction_type = 'view') { if (!$this->user_id) return; $post_categories = wp_get_post_categories($post_id); $post_source = get_post_meta($post_id, 'data_source', true); // 更新阅读历史 $history = $this->preferences['reading_history']; array_unshift($history, array( 'post_id' => $post_id, 'timestamp' => current_time('timestamp'), 'type' => $interaction_type )); // 保持最近100条记录 $this->preferences['reading_history'] = array_slice($history, 0, 100); // 更新分类偏好 foreach ($post_categories as $cat_id) { if (!isset($this->preferences['preferred_categories'][$cat_id])) { $this->preferences['preferred_categories'][$cat_id] = 0; } $this->preferences['preferred_categories'][$cat_id] += 1; } // 更新来源偏好 if ($post_source) { if (!isset($this->preferences['preferred_sources'][$post_source])) { $this->preferences['preferred_sources'][$post_source] = 0; } $this->preferences['preferred_sources'][$post_source] += 1; } $this->save_preferences(); } public function get_personalized_feed($limit = 20) { $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $limit, 'orderby' => 'relevance', 'meta_query' => array() ); // 基于用户偏好调整查询 if (!empty($this->preferences['preferred_categories'])) { arsort($this->preferences['preferred_categories']); $top_categories = array_slice( array_keys($this->preferences['preferred_categories']), 0, 3 ); $args['category__in'] = $top_categories; } if (!empty($this->preferences['preferred_sources'])) { arsort($this->preferences['preferred_sources']); $top_sources = array_slice( array_keys($this->preferences['preferred_sources']), 0, 2 ); $args['meta_query'][] = array( 'key' => 'data_source', 'value' => $top_sources, 'compare' => 'IN' ); } // 排除已读内容 if (!empty($this->preferences['reading_history'])) { $read_posts = array_column($this->preferences['reading_history'], 'post_id'); $args['post__not_in'] = array_unique($read_posts); } // 添加相关性评分 add_filter('posts_where', array($this, 'add_relevance_scoring')); $query = new WP_Query($args); remove_filter('posts_where', array($this, 'add_relevance_scoring')); return $query; } public function add_relevance_scoring($where) { // 基于用户偏好计算相关性得分的复杂逻辑 // 这里简化实现,实际应用中可能需要更复杂的算法 global $wpdb; if (!empty($this->preferences['preferred_categories'])) { // 为偏好的分类添加权重 $category_weights = array(); foreach ($this->preferences['preferred_categories'] as $cat_id => $count) { $weight = min(10, $count / 10); // 计算权重 $category_weights[$cat_id] = $weight; } // 这里可以添加更复杂的SQL逻辑来计算相关性 } return $where; } private function save_preferences() { if ($this->user_id) { update_user_meta( $this->user_id, 'aggregation_preferences', $this->preferences ); } } public function get_recommendations_based_on_similarity($post_id, $limit = 5) { $post_categories = wp_get_post_categories($post_id); $post_tags = wp_get_post_tags($post_id, array('fields' => 'ids')); $post_source = get_post_meta($post_id, 'data_source', true); $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $limit, 'post__not_in' => array($post_id), 'orderby' => 'relevance' ); // 基于内容相似性查找相关文章 $tax_query = array('relation' => 'OR'); if (!empty($post_categories)) { $tax_query[] = array( 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => $post_categories ); } if (!empty($post_tags)) { $tax_query[] = array( 'taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => $post_tags ); } if (!empty($tax_query)) { $args['tax_query'] = $tax_query; } if ($post_source) { $args['meta_query'] = array( array( 'key' => 'data_source', 'value' => $post_source, 'compare' => '=' ) ); } return new WP_Query($args); } } // 使用示例add_action('wp', function() { if (is_singular('collected_data')) { $personalizer = new PersonalizedAggregation(); $personalizer->track_user_interaction(get_the_ID()); } }); ### 第四部分:常用互联网小工具集成 #### 4.1 工具类插件架构设计 创建可扩展的小工具系统: // 小工具管理器类class ToolManager { private static $instance = null; private $tools = array(); private function __construct() { $this->load_tools(); add_action('init', array($this, 'register_tools')); } public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function load_tools() { // 加载内置工具 $this->register_tool('unit_converter', array( 'name' => '单位换算器', 'description' => '常用单位换算工具', 'callback' => array($this, 'render_unit_converter'), 'icon' => 'dashicons-calculator', 'category' => 'utility' )); $this->register_tool('color_picker', array( 'name' => '颜色选择器', 'description' => 'RGB/HEX颜色选择与转换', 'callback' => array($this, 'render_color_picker'), 'icon' => 'dashicons-art', 'category' => 'design' )); $this->register_tool('qrcode_generator', array( 'name' => '二维码生成器', 'description' => '生成自定义二维码', 'callback' => array($this, 'render_qrcode_generator'), 'icon' => 'dashicons-format-image', 'category' => 'generator' )); // 允许其他插件注册工具 do_action('tool_manager_register_tools', $this); } public function register_tool($slug, $args) { $defaults = array( 'name' => '', 'description' => '', 'callback' => null, 'icon' => 'dashicons-admin-tools', 'category' => 'general', 'settings' => array() ); $this->tools[$slug] = wp_parse_args($args, $defaults); } public function register_tools() { // 注册短代码 foreach ($this->tools as $slug => $tool) { add_shortcode('tool_' . $slug, $tool['callback']); } // 注册Gutenberg块 if (function_exists('register_block_type')) { $this->register_tool_blocks(); } } private function register_tool_blocks() { wp_register_script( 'tool-blocks', plugins_url('js/tool-blocks.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components'), '1.0.0', true ); register_block_type('tool-manager/tool', array( 'editor_script' => 'tool-blocks', 'render_callback' => array($this, 'render_tool_block') )); } public function render_tool_block($attributes) { $slug = $attributes['toolSlug'] ?? ''; if (isset($this->tools[$slug]) && is_callable($this->tools[$slug]['callback'])) { ob_start(); call_user_func($this->tools[$slug]['callback'], $attributes); return ob_get_clean(); } return '<p>工具未找到</p>'; } public function get_tools_by_category($category = '') { if (empty($category)) { return $this->tools; } return array_filter($this->tools, function($tool) use ($category) { return $tool['category'] === $category; }); } public function render_tool_selector() { $categories = array(); foreach ($this->tools as $tool) { if (!isset($categories[$tool['category']])) { $categories[$tool['category']] = array(); } $categories[$tool['category']][] = $tool; } ob_start(); ?> <div class="tool-selector"> <?php foreach ($categories as $category_name => $category_tools): ?> <div class="tool-category"> <h3><?php echo esc_html($this->get_category_label($category_name)); ?></h3> <div class="tool-grid"> <?php foreach ($category_tools as $slug => $tool): ?> <div class="tool-item" data-tool="<?php echo esc_attr($slug); ?>"> <div class="tool-icon"> <span class="dashicons <?php echo esc_attr($tool['icon']); ?>
发表评论实战教学:为WordPress网站添加在线虚拟试衣间与产品3D展示功能 引言:拥抱Web 3.0时代的交互式购物体验 在电子商务竞争日益激烈的今天,静态图片和文字描述已难以满足消费者的购物需求。据统计,提供3D产品展示的电商网站转化率平均提升40%,退货率降低25%。对于时尚电商而言,虚拟试衣间功能更是能将用户参与度提升300%以上。 本教程将深入讲解如何通过WordPress代码二次开发,为您的网站添加在线虚拟试衣间和产品3D展示功能。无论您是WordPress开发者、电商店主还是技术爱好者,都能通过本文学会如何将这些前沿技术整合到您的网站中。 第一部分:技术选型与环境准备 1.1 核心技术与工具评估 在开始开发前,我们需要评估几种主流技术方案: 3D展示技术选项: Three.js:最流行的WebGL库,功能强大,社区活跃 Babylon.js:微软开发的3D引擎,对商业应用友好 Model-Viewer:Google推出的Web组件,简单易用 A-Frame:基于Three.js的VR框架,适合沉浸式体验 虚拟试衣间技术方案: 2D图像融合:使用Canvas API处理服装与用户图像的合成 3D人体建模:基于参数化人体模型调整服装 AR试穿:通过摄像头实现增强现实试穿效果 开发环境要求: WordPress 5.0+版本 PHP 7.4+环境 支持HTML5和WebGL的现代浏览器 基本的JavaScript和PHP知识 1.2 创建开发环境 首先,我们需要设置一个安全的开发环境: // 创建专用插件目录结构 /* - virtual-fitting-room/ - virtual-fitting-room.php (主插件文件) - includes/ - class-3d-viewer.php - class-virtual-fitting.php - class-asset-manager.php - assets/ - js/ - three.min.js - fitting-room.js - css/ - fitting-room.css - models/ (3D模型文件) - templates/ (前端模板) - admin/ (后台管理界面) */ 在主插件文件中添加基础结构: <?php /** * Plugin Name: 虚拟试衣间与3D展示 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress产品添加虚拟试衣间和3D展示功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VFR_VERSION', '1.0.0'); define('VFR_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('VFR_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class) { $prefix = 'VFR_'; $base_dir = VFR_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } $relative_class = substr($class, $len); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function vfr_init() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>虚拟试衣间插件需要WordPress 5.0或更高版本。</p></div>'; }); return; } // 初始化核心类 $vfr_3d_viewer = new VFR_3D_Viewer(); $vfr_virtual_fitting = new VFR_Virtual_Fitting(); $vfr_asset_manager = new VFR_Asset_Manager(); } add_action('plugins_loaded', 'vfr_init'); 第二部分:实现3D产品展示功能 2.1 集成Three.js 3D引擎 首先,我们需要将Three.js集成到WordPress中: // includes/class-asset-manager.php class VFR_Asset_Manager { public function __construct() { add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } public function enqueue_frontend_assets() { // 加载Three.js库 wp_enqueue_script( 'three-js', VFR_PLUGIN_URL . 'assets/js/three.min.js', array(), 'r128', true ); // 加载OrbitControls(相机控制) wp_enqueue_script( 'three-orbit-controls', VFR_PLUGIN_URL . 'assets/js/OrbitControls.js', array('three-js'), '1.0', true ); // 加载GLTFLoader(3D模型加载器) wp_enqueue_script( 'three-gltf-loader', VFR_PLUGIN_URL . 'assets/js/GLTFLoader.js', array('three-js'), '1.0', true ); // 加载自定义3D查看器脚本 wp_enqueue_script( 'vfr-3d-viewer', VFR_PLUGIN_URL . 'assets/js/3d-viewer.js', array('three-js', 'three-orbit-controls', 'three-gltf-loader'), VFR_VERSION, true ); // 加载样式 wp_enqueue_style( 'vfr-frontend-style', VFR_PLUGIN_URL . 'assets/css/frontend.css', array(), VFR_VERSION ); // 传递数据到JavaScript wp_localize_script('vfr-3d-viewer', 'vfr_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('vfr_nonce') )); } } 2.2 创建3D查看器核心功能 // assets/js/3d-viewer.js class Product3DViewer { constructor(containerId, modelPath, options = {}) { this.container = document.getElementById(containerId); this.modelPath = modelPath; this.options = Object.assign({ backgroundColor: 0xf0f0f0, showControls: true, autoRotate: true, enableZoom: true }, options); this.scene = null; this.camera = null; this.renderer = null; this.controls = null; this.model = null; this.init(); } init() { // 创建场景 this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(this.options.backgroundColor); // 创建相机 this.camera = new THREE.PerspectiveCamera( 45, this.container.clientWidth / this.container.clientHeight, 0.1, 1000 ); this.camera.position.set(5, 5, 5); // 创建渲染器 this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); this.renderer.setPixelRatio(window.devicePixelRatio); this.container.appendChild(this.renderer.domElement); // 添加光源 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 5); this.scene.add(directionalLight); // 添加轨道控制 if (this.options.showControls) { this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.enableZoom = this.options.enableZoom; this.controls.autoRotate = this.options.autoRotate; } // 加载3D模型 this.loadModel(); // 开始动画循环 this.animate(); // 处理窗口大小变化 window.addEventListener('resize', () => this.onWindowResize()); } loadModel() { const loader = new THREE.GLTFLoader(); loader.load( this.modelPath, (gltf) => { this.model = gltf.scene; this.scene.add(this.model); // 调整模型位置和大小 const box = new THREE.Box3().setFromObject(this.model); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); // 居中模型 this.model.position.x += (this.model.position.x - center.x); this.model.position.y += (this.model.position.y - center.y); this.model.position.z += (this.model.position.z - center.z); // 缩放模型到合适大小 const maxDim = Math.max(size.x, size.y, size.z); const scale = 5 / maxDim; this.model.scale.multiplyScalar(scale); console.log('3D模型加载成功'); }, (progress) => { // 加载进度回调 const percent = (progress.loaded / progress.total * 100).toFixed(2); console.log(`模型加载进度: ${percent}%`); }, (error) => { console.error('3D模型加载失败:', error); } ); } animate() { requestAnimationFrame(() => this.animate()); if (this.controls && this.options.autoRotate) { this.controls.update(); } this.renderer.render(this.scene, this.camera); } onWindowResize() { this.camera.aspect = this.container.clientWidth / this.container.clientHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); } // 公共方法:更换模型 changeModel(newModelPath) { if (this.model) { this.scene.remove(this.model); } this.modelPath = newModelPath; this.loadModel(); } // 公共方法:更改背景颜色 setBackgroundColor(color) { this.scene.background = new THREE.Color(color); } } // WordPress集成 document.addEventListener('DOMContentLoaded', function() { // 查找所有3D查看器容器 const viewers = document.querySelectorAll('.vfr-3d-container'); viewers.forEach(container => { const modelPath = container.getAttribute('data-model'); const options = { backgroundColor: container.getAttribute('data-bg-color') || 0xf0f0f0, autoRotate: container.getAttribute('data-auto-rotate') !== 'false', enableZoom: container.getAttribute('data-enable-zoom') !== 'false' }; new Product3DViewer(container.id, modelPath, options); }); }); 2.3 创建WordPress短代码和Gutenberg块 // includes/class-3d-viewer.php class VFR_3D_Viewer { public function __construct() { // 注册短代码 add_shortcode('3d_product_viewer', array($this, 'render_3d_viewer_shortcode')); // 注册Gutenberg块 add_action('init', array($this, 'register_gutenberg_block')); // 添加产品编辑页面元框 add_action('add_meta_boxes', array($this, 'add_3d_model_meta_box')); add_action('save_post_product', array($this, 'save_3d_model_meta')); } // 短代码渲染 public function render_3d_viewer_shortcode($atts) { $atts = shortcode_atts(array( 'model' => '', 'width' => '100%', 'height' => '500px', 'bg_color' => '#f0f0f0', 'auto_rotate' => 'true', 'enable_zoom' => 'true' ), $atts, '3d_product_viewer'); // 生成唯一ID $viewer_id = 'vfr-3d-viewer-' . uniqid(); // 构建HTML $output = '<div class="vfr-3d-container" id="' . esc_attr($viewer_id) . '" '; $output .= 'data-model="' . esc_url($atts['model']) . '" '; $output .= 'data-bg-color="' . esc_attr($atts['bg_color']) . '" '; $output .= 'data-auto-rotate="' . esc_attr($atts['auto_rotate']) . '" '; $output .= 'data-enable-zoom="' . esc_attr($atts['enable_zoom']) . '" '; $output .= 'style="width:' . esc_attr($atts['width']) . ';height:' . esc_attr($atts['height']) . ';"></div>'; return $output; } // 注册Gutenberg块 public function register_gutenberg_block() { if (!function_exists('register_block_type')) { return; } register_block_type('vfr/3d-viewer', array( 'editor_script' => 'vfr-gutenberg-editor', 'render_callback' => array($this, 'render_gutenberg_3d_viewer'), 'attributes' => array( 'modelUrl' => array('type' => 'string'), 'width' => array('type' => 'string', 'default' => '100%'), 'height' => array('type' => 'string', 'default' => '500px'), 'backgroundColor' => array('type' => 'string', 'default' => '#f0f0f0'), 'autoRotate' => array('type' => 'boolean', 'default' => true), 'enableZoom' => array('type' => 'boolean', 'default' => true) ) )); } // 渲染Gutenberg块 public function render_gutenberg_3d_viewer($attributes) { return $this->render_3d_viewer_shortcode(array( 'model' => $attributes['modelUrl'], 'width' => $attributes['width'], 'height' => $attributes['height'], 'bg_color' => $attributes['backgroundColor'], 'auto_rotate' => $attributes['autoRotate'] ? 'true' : 'false', 'enable_zoom' => $attributes['enableZoom'] ? 'true' : 'false' )); } // 添加3D模型元框 public function add_3d_model_meta_box() { add_meta_box( 'vfr_3d_model', '3D模型设置', array($this, 'render_3d_model_meta_box'), 'product', 'side', 'default' ); } // 渲染元框内容 public function render_3d_model_meta_box($post) { wp_nonce_field('vfr_3d_model_nonce', 'vfr_3d_model_nonce_field'); $model_url = get_post_meta($post->ID, '_vfr_3d_model_url', true); ?> <div class="vfr-meta-box"> <p> <label for="vfr_3d_model_url">3D模型URL (GLTF/GLB格式):</label> <input type="url" id="vfr_3d_model_url" name="vfr_3d_model_url" value="<?php echo esc_url($model_url); ?>" style="width:100%; margin-top:5px;"> </p> <p class="description"> 上传GLTF或GLB格式的3D模型文件,然后在此处粘贴URL。 </p> <button type="button" class="button vfr-upload-model" style="width:100%;"> 上传/选择3D模型 </button> </div> <script> jQuery(document).ready(function($) { $('.vfr-upload-model').click(function(e) { e.preventDefault(); var frame = wp.media({ title: '选择3D模型文件', button: { text: '使用此文件' }, multiple: false, library: { type: ['application/octet-stream', 'model/gltf-binary'] } }); frame.on('select', function() { var attachment = frame.state().get('selection').first().toJSON(); $('#vfr_3d_model_url').val(attachment.url); }); frame.open(); }); }); </script> <?php } // 保存元数据 public function save_3d_model_meta($post_id) { // 安全检查 if (!isset($_POST['vfr_3d_model_nonce_field']) || !wp_verify_nonce($_POST['vfr_3d_model_nonce_field'], 'vfr_3d_model_nonce')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!current_user_can('edit_post', $post_id)) { return; } // 保存3D模型URL if (isset($_POST['vfr_3d_model_url'])) { vfr_3d_model_url'])); } } } ## 第三部分:实现虚拟试衣间功能 ### 3.1 虚拟试衣间核心架构设计 虚拟试衣间需要处理用户图像与服装图像的智能合成。我们将采用以下技术路径: 1. **用户身体测量**:通过上传照片或输入尺寸获取用户体型数据 2. **服装变形算法**:根据用户体型调整服装图像 3. **图像合成**:将调整后的服装与用户图像自然融合 // includes/class-virtual-fitting.phpclass VFR_Virtual_Fitting { private $upload_dir; public function __construct() { $this->upload_dir = wp_upload_dir(); // 注册AJAX处理 add_action('wp_ajax_vfr_process_fitting', array($this, 'process_fitting')); add_action('wp_ajax_nopriv_vfr_process_fitting', array($this, 'process_fitting')); // 注册短代码 add_shortcode('virtual_fitting_room', array($this, 'render_fitting_room')); // 添加服装尺寸管理 add_action('init', array($this, 'register_clothing_size_taxonomy')); } // 注册服装尺寸分类法 public function register_clothing_size_taxonomy() { $labels = array( 'name' => '服装尺寸', 'singular_name' => '尺寸', 'search_items' => '搜索尺寸', 'all_items' => '所有尺寸', 'edit_item' => '编辑尺寸', 'update_item' => '更新尺寸', 'add_new_item' => '添加新尺寸', 'new_item_name' => '新尺寸名称', 'menu_name' => '尺寸' ); register_taxonomy('clothing_size', 'product', array( 'hierarchical' => true, 'labels' => $labels, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => array('slug' => 'size'), )); } // 渲染虚拟试衣间界面 public function render_fitting_room($atts) { $atts = shortcode_atts(array( 'product_id' => 0, 'width' => '800px', 'height' => '600px' ), $atts, 'virtual_fitting_room'); // 获取当前用户ID $user_id = get_current_user_id(); // 获取用户保存的身体尺寸 $user_measurements = $user_id ? get_user_meta($user_id, 'vfr_body_measurements', true) : array(); // 获取产品信息 $product = $atts['product_id'] ? wc_get_product($atts['product_id']) : null; ob_start(); ?> <div class="vfr-fitting-room-container" style="width:<?php echo esc_attr($atts['width']); ?>; max-width:100%;"> <!-- 用户控制面板 --> <div class="vfr-control-panel"> <div class="vfr-tabs"> <button class="vfr-tab active" data-tab="upload">上传照片</button> <button class="vfr-tab" data-tab="measurements">身体尺寸</button> <button class="vfr-tab" data-tab="clothing">选择服装</button> <button class="vfr-tab" data-tab="result">试穿效果</button> </div> <!-- 上传照片标签页 --> <div class="vfr-tab-content active" id="tab-upload"> <div class="vfr-upload-area" id="vfr-upload-area"> <div class="vfr-upload-instructions"> <i class="dashicons dashicons-format-image" style="font-size:48px;color:#ccc;"></i> <p>点击或拖拽上传全身照片</p> <p class="vfr-upload-hint">建议:正面站立,背景简单,光线充足</p> </div> <input type="file" id="vfr-user-photo" accept="image/*" style="display:none;"> </div> <div class="vfr-preview-container" id="vfr-user-preview" style="display:none;"> <img id="vfr-user-image" src="" alt="用户照片"> <button type="button" class="vfr-remove-image">重新上传</button> </div> </div> <!-- 身体尺寸标签页 --> <div class="vfr-tab-content" id="tab-measurements"> <form id="vfr-measurements-form"> <div class="vfr-measurement-row"> <label>身高 (cm):</label> <input type="number" name="height" value="<?php echo esc_attr($user_measurements['height'] ?? ''); ?>" min="100" max="250"> </div> <div class="vfr-measurement-row"> <label>胸围 (cm):</label> <input type="number" name="chest" value="<?php echo esc_attr($user_measurements['chest'] ?? ''); ?>" min="50" max="150"> </div> <div class="vfr-measurement-row"> <label>腰围 (cm):</label> <input type="number" name="waist" value="<?php echo esc_attr($user_measurements['waist'] ?? ''); ?>" min="40" max="150"> </div> <div class="vfr-measurement-row"> <label>臀围 (cm):</label> <input type="number" name="hips" value="<?php echo esc_attr($user_measurements['hips'] ?? ''); ?>" min="50" max="150"> </div> <div class="vfr-measurement-row"> <label>肩宽 (cm):</label> <input type="number" name="shoulders" value="<?php echo esc_attr($user_measurements['shoulders'] ?? ''); ?>" min="30" max="80"> </div> <button type="button" id="vfr-save-measurements" class="button button-primary">保存尺寸</button> <button type="button" id="vfr-auto-detect" class="button">自动检测</button> </form> </div> <!-- 选择服装标签页 --> <div class="vfr-tab-content" id="tab-clothing"> <?php if ($product): ?> <div class="vfr-product-selection"> <h4>当前产品: <?php echo esc_html($product->get_name()); ?></h4> <div class="vfr-clothing-options"> <div class="vfr-color-options"> <label>颜色:</label> <?php if ($product->is_type('variable')) { $colors = $product->get_available_variations(); foreach ($colors as $color) { echo '<button type="button" class="vfr-color-option" data-color="' . esc_attr($color['attributes']['attribute_color']) . '">' . esc_html($color['attributes']['attribute_color']) . '</button>'; } } ?> </div> <div class="vfr-size-options"> <label>尺寸:</label> <?php $sizes = get_terms(array('taxonomy' => 'clothing_size', 'hide_empty' => false)); foreach ($sizes as $size) { echo '<button type="button" class="vfr-size-option" data-size="' . esc_attr($size->slug) . '">' . esc_html($size->name) . '</button>'; } ?> </div> </div> </div> <?php else: ?> <p>请选择要试穿的产品</p> <?php endif; ?> </div> <!-- 试穿效果标签页 --> <div class="vfr-tab-content" id="tab-result"> <div class="vfr-result-container"> <canvas id="vfr-fitting-canvas" width="400" height="600"></canvas> <div class="vfr-result-controls"> <button type="button" id="vfr-download-result" class="button">下载图片</button> <button type="button" id="vfr-share-result" class="button">分享</button> <button type="button" id="vfr-try-another" class="button">试穿其他</button> </div> </div> </div> </div> <!-- 试衣预览区域 --> <div class="vfr-preview-area"> <div class="vfr-preview-wrapper"> <div class="vfr-original-preview"> <h4>原始照片</h4> <div id="vfr-original-container"></div> </div> <div class="vfr-fitted-preview"> <h4>试穿效果</h4> <div id="vfr-fitted-container"></div> </div> </div> <div class="vfr-loading" id="vfr-loading" style="display:none;"> <div class="vfr-spinner"></div> <p>正在处理中...</p> </div> </div> </div> <script> // 虚拟试衣间JavaScript逻辑将在下面实现 </script> <?php return ob_get_clean(); } // 处理试衣请求 public function process_fitting() { // 验证nonce if (!check_ajax_referer('vfr_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } // 获取POST数据 $user_image = $_POST['user_image'] ?? ''; $product_id = intval($_POST['product_id'] ?? 0); $measurements = $_POST['measurements'] ?? array(); $color = sanitize_text_field($_POST['color'] ?? ''); $size = sanitize_text_field($_POST['size'] ?? ''); // 验证数据 if (empty($user_image) || $product_id <= 0) { wp_send_json_error('缺少必要数据'); } // 解码base64图像 $user_image = str_replace('data:image/png;base64,', '', $user_image); $user_image = str_replace(' ', '+', $user_image); $user_image_data = base64_decode($user_image); // 保存临时文件 $temp_dir = $this->upload_dir['basedir'] . '/vfr_temp/'; if (!file_exists($temp_dir)) { wp_mkdir_p($temp_dir); } $user_image_path = $temp_dir . uniqid('user_') . '.png'; file_put_contents($user_image_path, $user_image_data); // 获取产品图像 $product = wc_get_product($product_id); $product_image_id = $product->get_image_id(); $product_image_path = get_attached_file($product_image_id); // 调用图像处理函数 $result = $this->process_clothing_fitting($user_image_path, $product_image_path, $measurements); if ($result['success']) { // 将结果图像转换为base64 $result_image = base64_encode(file_get_contents($result['path'])); // 清理临时文件 unlink($user_image_path); unlink($result['path']); // 保存用户尺寸(如果用户已登录) if (is_user_logged_in() && !empty($measurements)) { update_user_meta(get_current_user_id(), 'vfr_body_measurements', $measurements); } wp_send_json_success(array( 'image' => 'data:image/png;base64,' . $result_image, 'message' => '试穿处理完成' )); } else { wp_send_json_error($result['message']); } } // 服装试穿处理核心算法 private function process_clothing_fitting($user_image_path, $clothing_image_path, $measurements) { // 这里实现图像处理算法 // 实际项目中可能需要使用OpenCV、ImageMagick或深度学习模型 // 简化版实现:使用PHP GD库进行基本图像处理 $user_image = imagecreatefrompng($user_image_path); $clothing_image = imagecreatefrompng($clothing_image_path); if (!$user_image || !$clothing_image) { return array('success' => false, 'message' => '图像加载失败'); } // 获取图像尺寸 $user_width = imagesx($user_image); $user_height = imagesy($user_image); // 根据用户尺寸调整服装图像 $scale_factor = $this->calculate_scale_factor($measurements, $user_height); $clothing_width = imagesx($clothing_image) * $scale_factor; $clothing_height = imagesy($clothing_image) * $scale_factor; // 创建新图像 $result_image = imagecreatetruecolor($user_width, $user_height); // 复制用户图像 imagecopy($result_image, $user_image, 0, 0, 0, 0, $user_width, $user_height); // 调整服装图像大小 $scaled_clothing = imagescale($clothing_image, $clothing_width, $clothing_height); // 计算服装位置(简化版:居中放置) $x_position = ($user_width - $clothing_width) / 2; $y_position = $user_height * 0.3; // 假设服装从30%高度开始 // 合并图像(使用alpha混合) $this->image_alpha_merge($result_image, $scaled_clothing, $x_position, $y_position, 0, 0, $clothing_width, $clothing_height, 100); // 保存结果 $result_path = $this->upload_dir['basedir'] . '/vfr_temp/result_' . uniqid() . '.png'; imagepng($result_image, $result_path); // 释放内存 imagedestroy($user_image); imagedestroy($clothing_image); imagedestroy($result_image); imagedestroy($scaled_clothing); return array('success' => true, 'path' => $result_path); } // 计算缩放因子 private function calculate_scale_factor($measurements, $user_height) { // 简化算法:根据身高比例缩放 $standard_height = 170; // 标准身高170cm $height = $measurements['height'] ?? $user_height; return $height / $standard_height; } // Alpha通道图像合并 private function image_alpha_merge($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) { // 创建临时图像 $tmp_im = imagecreatetruecolor($src_w, $src_h); // 复制源图像到临时图像 imagecopy($tmp_im, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); imagecopy($tmp_im, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); imagecopymerge($dst_im, $tmp_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); // 释放临时图像 imagedestroy($tmp_im); } } ### 3.2 虚拟试衣间前端交互实现 // assets/js/fitting-room.jsclass VirtualFittingRoom { constructor() { this.userImage = null; this.measurements = {}; this.selectedProduct = null; this.selectedColor = null; this.selectedSize = null; this.init(); } init() { this.setupEventListeners(); this.setupDragAndDrop(); this.loadSavedMeasurements(); } setupEventListeners() { // 标签页切换 document.querySelectorAll('.vfr-tab').forEach(tab => { tab.addEventListener('click', (e) => { this.switchTab(e.target.dataset.tab); }); }); // 上传区域点击 document.getElementById('vfr-upload-area').addEventListener('click', () => { document.getElementById('vfr-user-photo').click(); }); // 文件选择 document.getElementById('vfr-user-photo').addEventListener('change', (e) => { this.handleImageUpload(e.target.files[0]); }); // 保存尺寸 document.getElementById('vfr-save-measurements').addEventListener('click', () => { this.saveMeasurements(); }); // 自动检测 document.getElementById('vfr-auto-detect').addEventListener('click', () => { this.autoDetectMeasurements(); }); // 颜色选择 document.querySelectorAll('.vfr-color-option').forEach(option => { option.addEventListener('click', (e) => { this.selectColor(e.target.dataset.color); }); }); // 尺寸选择 document.querySelectorAll('.vfr-size-option').forEach(option => { option.addEventListener('click', (e) => { this.selectSize(e.target.dataset.size); }); }); // 处理试衣 document.getElementById('vfr-process-fitting').addEventListener('click', () => { this.processFitting(); }); } setupDragAndDrop() { const uploadArea = document.getElementById
发表评论手把手教程:在WordPress中集成多平台社交媒体内容聚合与展示 引言:为什么需要社交媒体内容聚合? 在当今数字营销时代,社交媒体已成为品牌建设、用户互动和内容传播的核心渠道。然而,随着品牌在多平台(如微博、微信公众号、Twitter、Facebook、Instagram、LinkedIn等)的布局,内容分散管理的问题日益凸显。WordPress作为全球最流行的内容管理系统,通过代码二次开发实现社交媒体内容聚合与展示,能够帮助网站管理员: 提升内容展示效率:自动聚合多平台内容,减少手动更新工作量 增强用户参与度:集中展示社交媒体动态,鼓励用户跨平台互动 强化品牌一致性:统一设计和展示风格,提升品牌专业形象 优化SEO效果:新鲜、多样化的内容有助于提升搜索引擎排名 本教程将详细指导您通过WordPress代码二次开发,实现一个功能完善的多平台社交媒体内容聚合系统。 第一部分:准备工作与环境配置 1.1 开发环境搭建 在开始开发前,确保您已具备以下环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel WordPress安装:最新稳定版本(建议5.8以上) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:用于调试前端代码 1.2 创建自定义插件框架 为避免主题更新导致代码丢失,我们将创建一个独立插件: <?php /** * Plugin Name: 多平台社交媒体聚合器 * Plugin URI: https://yourwebsite.com/ * Description: 聚合并展示多平台社交媒体内容 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SMA_VERSION', '1.0.0'); define('SMA_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SMA_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 add_action('plugins_loaded', 'sma_init'); function sma_init() { // 检查必要扩展 if (!extension_loaded('curl')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>社交媒体聚合器需要cURL扩展支持,请联系主机提供商启用。</p></div>'; }); return; } // 加载核心类 require_once SMA_PLUGIN_DIR . 'includes/class-social-aggregator.php'; require_once SMA_PLUGIN_DIR . 'includes/class-api-manager.php'; require_once SMA_PLUGIN_DIR . 'includes/class-display-engine.php'; // 初始化主类 $social_aggregator = new Social_Media_Aggregator(); $social_aggregator->init(); } 1.3 数据库表设计 我们需要创建数据表来存储聚合的内容: // 在插件激活时创建表 register_activation_hook(__FILE__, 'sma_create_tables'); function sma_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'social_media_posts'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, platform varchar(50) NOT NULL, post_id varchar(255) NOT NULL, author_name varchar(255), author_avatar varchar(500), content text, media_urls text, post_url varchar(500), likes_count int(11) DEFAULT 0, shares_count int(11) DEFAULT 0, comments_count int(11) DEFAULT 0, post_time datetime NOT NULL, fetched_time datetime NOT NULL, is_active tinyint(1) DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY platform_post (platform, post_id), KEY post_time (post_time), KEY platform (platform) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建缓存表 $cache_table = $wpdb->prefix . 'social_media_cache'; $cache_sql = "CREATE TABLE IF NOT EXISTS $cache_table ( cache_key varchar(255) NOT NULL, cache_value longtext, expiration datetime NOT NULL, PRIMARY KEY (cache_key) ) $charset_collate;"; dbDelta($cache_sql); } 第二部分:多平台API集成 2.1 API管理器设计 创建一个统一的API管理器类,处理不同平台的认证和请求: <?php // includes/class-api-manager.php class Social_Media_API_Manager { private $platforms = []; private $cache_time = 3600; // 缓存1小时 public function __construct() { $this->platforms = [ 'weibo' => [ 'name' => '微博', 'api_base' => 'https://api.weibo.com/2/', 'auth_type' => 'oauth2' ], 'wechat' => [ 'name' => '微信公众号', 'api_base' => 'https://api.weixin.qq.com/cgi-bin/', 'auth_type' => 'oauth2' ], 'twitter' => [ 'name' => 'Twitter', 'api_base' => 'https://api.twitter.com/2/', 'auth_type' => 'oauth2' ], 'facebook' => [ 'name' => 'Facebook', 'api_base' => 'https://graph.facebook.com/v12.0/', 'auth_type' => 'oauth2' ], 'instagram' => [ 'name' => 'Instagram', 'api_base' => 'https://graph.facebook.com/v12.0/', 'auth_type' => 'oauth2' ] ]; } /** * 获取平台数据 */ public function fetch_posts($platform, $params = []) { // 检查缓存 $cache_key = 'sma_' . $platform . '_' . md5(serialize($params)); $cached = $this->get_cache($cache_key); if ($cached !== false) { return $cached; } // 根据平台调用不同的API方法 $method_name = 'fetch_' . $platform . '_posts'; if (method_exists($this, $method_name)) { $posts = $this->$method_name($params); // 缓存结果 $this->set_cache($cache_key, $posts); return $posts; } return new WP_Error('unsupported_platform', '不支持的社交媒体平台'); } /** * 获取微博内容 */ private function fetch_weibo_posts($params) { $defaults = [ 'count' => 20, 'screen_name' => get_option('sma_weibo_username'), 'access_token' => get_option('sma_weibo_access_token') ]; $params = wp_parse_args($params, $defaults); $url = add_query_arg([ 'screen_name' => $params['screen_name'], 'count' => $params['count'], 'access_token' => $params['access_token'] ], 'https://api.weibo.com/2/statuses/user_timeline.json'); $response = wp_remote_get($url, [ 'timeout' => 15, 'sslverify' => false ]); if (is_wp_error($response)) { return $response; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['error'])) { return new WP_Error('weibo_api_error', $data['error']); } return $this->parse_weibo_posts($data); } /** * 解析微博数据 */ private function parse_weibo_posts($data) { $posts = []; if (!isset($data['statuses']) || empty($data['statuses'])) { return $posts; } foreach ($data['statuses'] as $status) { $post = [ 'platform' => 'weibo', 'post_id' => $status['id'], 'author_name' => $status['user']['screen_name'], 'author_avatar' => $status['user']['profile_image_url'], 'content' => $this->format_weibo_content($status['text']), 'media_urls' => [], 'post_url' => 'https://weibo.com/' . $status['user']['id'] . '/' . $status['bid'], 'likes_count' => $status['attitudes_count'], 'shares_count' => $status['reposts_count'], 'comments_count' => $status['comments_count'], 'post_time' => date('Y-m-d H:i:s', strtotime($status['created_at'])) ]; // 处理图片 if (isset($status['pic_urls']) && !empty($status['pic_urls'])) { foreach ($status['pic_urls'] as $pic) { $post['media_urls'][] = $pic['thumbnail_pic']; } } // 处理视频 if (isset($status['page_info']) && $status['page_info']['type'] == 'video') { $post['media_urls'][] = $status['page_info']['page_url']; } $posts[] = $post; } return $posts; } /** * 格式化微博内容(处理链接、话题、@用户) */ private function format_weibo_content($text) { // 处理链接 $text = preg_replace( '/(https?://[^s]+)/', '<a href="$1" target="_blank" rel="nofollow">$1</a>', $text ); // 处理话题 $text = preg_replace( '/#([^#]+)#/', '<a href="https://s.weibo.com/weibo?q=$1" target="_blank">#$1#</a>', $text ); // 处理@用户 $text = preg_replace( '/@([wx{4e00}-x{9fa5}-]+)/u', '<a href="https://weibo.com/n/$1" target="_blank">@$1</a>', $text ); return $text; } /** * 获取微信公众号文章 */ private function fetch_wechat_posts($params) { // 微信公众号API实现 // 注意:微信公众号API需要服务号并认证,这里仅展示框架 $access_token = $this->get_wechat_access_token(); if (is_wp_error($access_token)) { return $access_token; } // 获取素材列表 $url = add_query_arg([ 'access_token' => $access_token, 'type' => 'news', 'offset' => 0, 'count' => 20 ], 'https://api.weixin.qq.com/cgi-bin/material/batchget_material'); $response = wp_remote_post($url, [ 'timeout' => 15, 'sslverify' => false ]); // 解析响应... return []; } /** * 缓存管理方法 */ private function get_cache($key) { global $wpdb; $table = $wpdb->prefix . 'social_media_cache'; $result = $wpdb->get_row($wpdb->prepare( "SELECT cache_value FROM $table WHERE cache_key = %s AND expiration > %s", $key, current_time('mysql') )); if ($result) { return maybe_unserialize($result->cache_value); } return false; } private function set_cache($key, $value) { global $wpdb; $table = $wpdb->prefix . 'social_media_cache'; $expiration = date('Y-m-d H:i:s', time() + $this->cache_time); $wpdb->replace($table, [ 'cache_key' => $key, 'cache_value' => maybe_serialize($value), 'expiration' => $expiration ]); } } 2.2 OAuth认证集成 为安全地存储API密钥,创建设置页面: // includes/class-settings.php class Social_Media_Aggregator_Settings { public function __construct() { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); } public function add_admin_menu() { add_options_page( '社交媒体聚合设置', '社交聚合', 'manage_options', 'social-media-aggregator', [$this, 'render_settings_page'] ); } public function register_settings() { // 注册平台设置 $platforms = ['weibo', 'wechat', 'twitter', 'facebook', 'instagram']; foreach ($platforms as $platform) { register_setting('sma_settings', 'sma_' . $platform . '_enabled'); register_setting('sma_settings', 'sma_' . $platform . '_username'); register_setting('sma_settings', 'sma_' . $platform . '_access_token'); register_setting('sma_settings', 'sma_' . $platform . '_app_id'); register_setting('sma_settings', 'sma_' . $platform . '_app_secret'); } // 显示设置 register_setting('sma_settings', 'sma_display_limit'); register_setting('sma_settings', 'sma_update_interval'); register_setting('sma_settings', 'sma_display_style'); } public function render_settings_page() { ?> <div class="wrap"> <h1>社交媒体聚合设置</h1> <form method="post" action="options.php"> <?php settings_fields('sma_settings'); ?> <h2 class="title">平台配置</h2> <table class="form-table"> <tr> <th scope="row">微博配置</th> <td> <label> <input type="checkbox" name="sma_weibo_enabled" value="1" <?php checked(1, get_option('sma_weibo_enabled')); ?>> 启用微博聚合 </label> <p class="description">需要微博开放平台Access Token</p> <p> <label>用户名:<br> <input type="text" name="sma_weibo_username" value="<?php echo esc_attr(get_option('sma_weibo_username')); ?>" class="regular-text"> </label> </p> <p> <label>Access Token:<br> <input type="password" name="sma_weibo_access_token" value="<?php echo esc_attr(get_option('sma_weibo_access_token')); ?>" class="regular-text"> </label> </p> </td> </tr> <!-- 其他平台配置类似 --> </table> <h2 class="title">显示设置</h2> <table class="form-table"> <tr> <th scope="row">显示数量</th> <td> <input type="number" name="sma_display_limit" value="<?php echo esc_attr(get_option('sma_display_limit', 10)); ?>" min="1" max="50"> <p class="description">每页显示的内容数量</p> </td> </tr> <tr> <th scope="row">更新频率</th> <td> <select name="sma_update_interval"> <option value="1800" <?php selected(get_option('sma_update_interval'), 1800); ?>>30分钟</option> <option value="3600" <?php selected(get_option('sma_update_interval'), 3600); ?>>1小时</option> <option value="7200" <?php selected(get_option('sma_update_interval'), 7200); ?>>2小时</option> <option value="21600" <?php selected(get_option('sma_update_interval'), 21600); ?>>6小时</option> </select> </td> </tr> </table> <?php submit_button(); ?> </form> <div class="card"> <h3>API配置指南</h3> <ol> <li>微博:访问 <a href="https://open.weibo.com/" target="_blank">微博开放平台</a> 创建应用获取Access Token</li> <li>微信公众号:需要认证服务号,在 <a href="https://mp.weixin.qq.com/" target="_blank">微信公众平台</a> 获取凭证</li> <li>Twitter:访问 <a href="https://developer.twitter.com/" target="_blank">Twitter开发者平台</a></li> </ol> </div> </div> <?php } } 第三部分:内容展示引擎开发 3.1 短代码系统实现 创建短代码以便在文章和页面中插入社交媒体内容: // includes/class-display-engine.php class Social_Media_Display_Engine { private $api_manager; public function __construct($api_manager) { $this->api_manager = $api_manager; $this->init_hooks(); } private function init_hooks() { // 注册短代码 add_shortcode('social_media_feed', [$this, 'render_feed_shortcode']); // 注册小工具 add_action('widgets_init', function() { register_widget('Social_Media_Feed_Widget'); }); // 注册Gutenberg块 add_action('init', [$this, 'register_gutenberg_block']); // AJAX加载更多 add_action('wp_ajax_sma_load_more', [$this, 'ajax_load_more']); add_action('wp_ajax_nopriv_sma_load_more', [$this, 'ajax_load_more']); } /** * 短代码渲染 */ public function render_feed_shortcode($atts) { $atts = shortcode_atts([ 'platform' => 'all', // all, weibo, wechat, twitter等 'limit' => get_option('sma_display_limit', 10), 'layout' => 'grid', // grid, list, masonry 'show_avatars' => 'yes', 'show_metrics' => 'yes', 'auto_refresh' => 'no', 'filter' => '' // 过滤关键词 ], $atts, 'social_media_feed'); // 获取数据 $posts = $this->get_posts_for_display($atts); if (empty($posts)) { return '<div class="sma-no-posts">暂无社交媒体内容</div>'; } // 渲染输出 ob_start(); ?> <div class="social-media-aggregator" data-platform="<?php echo esc_attr($atts['platform']); ?>" data-limit="<?php echo esc_attr($atts['limit']); ?>" data-layout="<?php echo esc_attr($atts['layout']); ?>" data-page="1"> <?php if ($atts['filter']): ?> <div class="sma-feed-filter"> <input type="text" class="sma-filter-input" placeholder="搜索内容..." data-filter="<?php echo esc_attr($atts['filter']); ?>"> <button class="sma-filter-btn">搜索</button> </div> <?php endif; ?> <div class="sma-feed-container sma-layout-<?php echo esc_attr($atts['layout']); ?>"> <?php foreach ($posts as $post): ?> <?php $this->render_post_card($post, $atts); ?> <?php endforeach; ?> </div> <?php if (count($posts) >= $atts['limit']): ?> <div class="sma-load-more-container"> <button class="sma-load-more-btn">加载更多</button> <div class="sma-loading" style="display:none;"> <div class="sma-spinner"></div> 加载中... </div> </div> <?php endif; ?> </div> <?php if ($atts['auto_refresh'] === 'yes'): ?> <script> jQuery(document).ready(function($) { // 每5分钟自动刷新 setInterval(function() { var container = $('.social-media-aggregator'); var platform = container.data('platform'); var limit = container.data('limit'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'sma_refresh_feed', platform: platform, limit: limit, nonce: '<?php echo wp_create_nonce('sma_refresh'); ?>' }, success: function(response) { if (response.success) { container.find('.sma-feed-container').html(response.data.html); } } }); }, 300000); // 5分钟 }); </script> <?php endif; return ob_get_clean(); } /** * 渲染单个内容卡片 */ private function render_post_card($post, $atts) { $platform_class = 'sma-platform-' . $post['platform']; $media_html = ''; // 处理媒体内容 if (!empty($post['media_urls'])) { $media_html = $this->render_media_content($post['media_urls'], $post['platform']); } // 格式化时间 $time_diff = human_time_diff(strtotime($post['post_time']), current_time('timestamp')); ?> <div class="sma-post-card <?php echo esc_attr($platform_class); ?>" data-post-id="<?php echo esc_attr($post['post_id']); ?>" data-platform="<?php echo esc_attr($post['platform']); ?>"> <div class="sma-post-header"> <?php if ($atts['show_avatars'] === 'yes' && !empty($post['author_avatar'])): ?> <div class="sma-author-avatar"> <img src="<?php echo esc_url($post['author_avatar']); ?>" alt="<?php echo esc_attr($post['author_name']); ?>" onerror="this.src='<?php echo SMA_PLUGIN_URL; ?>assets/default-avatar.png'"> </div> <?php endif; ?> <div class="sma-author-info"> <div class="sma-author-name"> <?php echo esc_html($post['author_name']); ?> <span class="sma-platform-badge"><?php echo $this->get_platform_name($post['platform']); ?></span> </div> <div class="sma-post-time" title="<?php echo esc_attr($post['post_time']); ?>"> <?php echo $time_diff; ?>前 </div> </div> <div class="sma-platform-icon"> <?php echo $this->get_platform_icon($post['platform']); ?> </div> </div> <div class="sma-post-content"> <?php echo wp_kses_post($post['content']); ?> </div> <?php if ($media_html): ?> <div class="sma-post-media"> <?php echo $media_html; ?> </div> <?php endif; ?> <?php if ($atts['show_metrics'] === 'yes'): ?> <div class="sma-post-metrics"> <span class="sma-metric sma-likes" title="点赞"> <i class="sma-icon-heart"></i> <?php echo $this->format_number($post['likes_count']); ?> </span> <span class="sma-metric sma-shares" title="分享"> <i class="sma-icon-share"></i> <?php echo $this->format_number($post['shares_count']); ?> </span> <span class="sma-metric sma-comments" title="评论"> <i class="sma-icon-comment"></i> <?php echo $this->format_number($post['comments_count']); ?> </span> <a href="<?php echo esc_url($post['post_url']); ?>" target="_blank" rel="nofollow noopener" class="sma-original-link"> 查看原文 </a> </div> <?php endif; ?> <div class="sma-post-actions"> <button class="sma-action-btn sma-like-btn" data-post-id="<?php echo esc_attr($post['post_id']); ?>"> <i class="sma-icon-heart"></i> 点赞 </button> <button class="sma-action-btn sma-share-btn" data-post-url="<?php echo esc_url($post['post_url']); ?>"> <i class="sma-icon-share"></i> 分享 </button> <button class="sma-action-btn sma-embed-btn" data-embed-code="<?php echo esc_attr($this->generate_embed_code($post)); ?>"> <i class="sma-icon-embed"></i> 嵌入 </button> </div> </div> <?php } /** * 渲染媒体内容 */ private function render_media_content($media_urls, $platform) { if (empty($media_urls)) return ''; ob_start(); // 判断媒体类型 $first_media = $media_urls[0]; $is_video = preg_match('/(.mp4|.mov|.avi|.webm|youtube.com|vimeo.com)/i', $first_media); if ($is_video) { // 视频内容 ?> <div class="sma-video-container"> <video controls preload="metadata" poster="<?php echo SMA_PLUGIN_URL; ?>assets/video-poster.jpg"> <source src="<?php echo esc_url($first_media); ?>" type="video/mp4"> 您的浏览器不支持视频播放 </video> </div> <?php } else { // 图片内容 $total_images = count($media_urls); ?> <div class="sma-image-gallery" data-total="<?php echo $total_images; ?>"> <?php foreach ($media_urls as $index => $image_url): ?> <?php if ($index < 4): // 最多显示4张 ?> <div class="sma-image-item <?php echo $total_images > 1 ? 'sma-multiple' : 'sma-single'; ?>" style="background-image: url('<?php echo esc_url($image_url); ?>')" data-index="<?php echo $index; ?>"> <?php if ($index === 3 && $total_images > 4): ?> <div class="sma-image-more">+<?php echo $total_images - 4; ?></div> <?php endif; ?> <img src="<?php echo esc_url($image_url); ?>" alt="社交媒体图片" loading="lazy"> </div> <?php endif; ?> <?php endforeach; ?> </div> <?php if ($total_images > 1): ?> <div class="sma-gallery-lightbox" style="display:none;"> <div class="sma-lightbox-content"> <button class="sma-lightbox-close">×</button> <div class="sma-lightbox-slider"></div> <div class="sma-lightbox-caption"></div> <button class="sma-lightbox-prev">‹</button> <button class="sma-lightbox-next">›</button> </div> </div> <?php endif; } return ob_get_clean(); } /** * AJAX加载更多 */ public function ajax_load_more() { check_ajax_referer('sma_ajax', 'nonce'); $page = intval($_POST['page']); $platform = sanitize_text_field($_POST['platform']); $limit = intval($_POST['limit']); $layout = sanitize_text_field($_POST['layout']); $offset = ($page - 1) * $limit; // 获取更多数据 $posts = $this->get_posts_for_display([ 'platform' => $platform, 'limit' => $limit, 'offset' => $offset ]); if (empty($posts)) { wp_send_json_error(['message' => '没有更多内容']); } // 渲染HTML ob_start(); foreach ($posts as $post) { $this->render_post_card($post, [ 'show_avatars' => 'yes', 'show_metrics' => 'yes', 'layout' => $layout ]); } $html = ob_get_clean(); wp_send_json_success([ 'html' => $html, 'has_more' => count($posts) >= $limit ]); } /** * 获取展示用的数据 */ private function get_posts_for_display($args) { global $wpdb; $table_name = $wpdb->prefix . 'social_media_posts'; $defaults = [ 'platform' => 'all', 'limit' => 10, 'offset' => 0, 'filter' => '' ]; $args = wp_parse_args($args, $defaults); // 构建查询 $where = ['is_active = 1']; $params = []; if ($args['platform'] !== 'all') { $where[] = 'platform = %s'; $params[] = $args['platform']; } if (!empty($args['filter'])) { $where[] = '(content LIKE %s OR author_name LIKE %s)'; $search_term = '%' . $wpdb->esc_like($args['filter']) . '%'; $params[] = $search_term; $params[] = $search_term; } $where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; $query = $wpdb->prepare( "SELECT * FROM $table_name $where_sql ORDER BY post_time DESC LIMIT %d OFFSET %d", array_merge($params, [$args['limit'], $args['offset']]) ); return $wpdb->get_results($query, ARRAY_A); } /** * 获取平台名称 */ private function get_platform_name($platform) { $names = [ 'weibo' => '微博', 'wechat' => '微信', 'twitter' => 'Twitter', 'facebook' => 'Facebook', 'instagram' => 'Instagram' ]; return $names[$platform] ?? $platform; } /** * 获取平台图标 */ private function get_platform_icon($platform) { $icons = [ 'weibo' => '<svg viewBox="0 0 24 24"><path d="M20.194 3.46c-1.802.81-3.73 1.36-5.758 1.61.022.16.033.322.033.486 0 2.33-.94 4.43-2.46 5.94-1.52 1.51-3.61 2.45-5.94 2.45-.164 0-.326-.01-.486-.03C4.9 15.444 4.35 17.372 3.54 19.174c1.8-.81 3.73-1.36 5.76-1.61-.02-.16-.03-.322-.03-.486 0-2.33.94-4.43 2.46-5.94 1.52-1.51 3.61-2.45 5.94-2.45.164 0 .326.01.486.03.25-1.986.8-3.914 1.61-5.716z"/></svg>',
发表评论详细教程:为网站打造会员专属社区与论坛互动系统,通过WordPress代码二次开发实现常用互联网小工具功能 引言:为什么需要会员专属社区与论坛系统? 在当今互联网环境中,用户参与度和社区粘性已成为网站成功的关键因素。一个功能完善的会员专属社区与论坛系统不仅能提升用户体验,还能有效增加用户留存时间、促进内容生成和增强品牌忠诚度。对于内容创作者、在线教育平台、企业官网或任何希望建立稳定用户群体的网站来说,会员社区都是不可或缺的组成部分。 WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和灵活性。通过代码二次开发,我们可以在WordPress平台上构建出功能丰富、高度定制化的会员社区系统,同时集成各种实用的互联网小工具,为用户提供全方位的互动体验。 本教程将详细指导您如何从零开始,通过WordPress代码二次开发,打造一个功能完整的会员专属社区与论坛互动系统,并集成常用互联网小工具功能。 第一部分:前期准备与环境搭建 1.1 选择合适的WordPress主题 在开始开发之前,选择一个适合社区建设的WordPress主题至关重要。建议选择以下类型的主题: 响应式设计:确保社区在所有设备上都能良好显示 轻量级框架:避免过多不必要的功能影响性能 良好的扩展性:支持自定义开发和无冲突的代码结构 社区友好型:内置或兼容论坛、用户资料等社区功能 推荐主题:Astra、GeneratePress、OceanWP等轻量级多功能主题。 1.2 必备插件安装与配置 虽然我们将主要通过代码开发实现功能,但一些基础插件可以节省开发时间: BuddyPress:提供基础的社交网络功能框架 bbPress:轻量级论坛系统,与WordPress完美集成 MemberPress或WooCommerce Memberships:会员管理系统 Advanced Custom Fields:自定义字段管理,便于扩展用户资料 User Role Editor:用户角色和权限管理 1.3 开发环境配置 为了安全地进行代码开发,建议配置本地开发环境: 安装Local by Flywheel或XAMPP作为本地服务器环境 设置调试模式:在wp-config.php中添加以下代码: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 安装代码编辑器:VS Code、Sublime Text或PHPStorm 配置版本控制系统(Git)以便代码管理 第二部分:会员系统核心功能开发 2.1 自定义用户角色与权限系统 WordPress默认的用户角色可能无法满足社区需求,我们需要创建更精细的角色体系: // 在主题的functions.php或自定义插件中添加 function create_community_user_roles() { // 添加青铜会员角色 add_role('bronze_member', '青铜会员', array( 'read' => true, 'edit_posts' => true, 'delete_posts' => false, 'publish_posts' => false, 'upload_files' => true, 'participate_in_forum' => true, 'access_basic_content' => true )); // 添加白银会员角色 add_role('silver_member', '白银会员', array( 'read' => true, 'edit_posts' => true, 'delete_posts' => true, 'publish_posts' => true, 'upload_files' => true, 'participate_in_forum' => true, 'access_premium_content' => true, 'create_topics' => true )); // 添加黄金会员角色 add_role('gold_member', '黄金会员', array( 'read' => true, 'edit_posts' => true, 'edit_others_posts' => true, 'delete_posts' => true, 'delete_others_posts' => false, 'publish_posts' => true, 'upload_files' => true, 'participate_in_forum' => true, 'access_all_content' => true, 'create_topics' => true, 'moderate_comments' => true, 'access_exclusive_areas' => true )); } add_action('init', 'create_community_user_roles'); 2.2 扩展用户资料字段 使用Advanced Custom Fields插件或自定义代码扩展用户资料: // 自定义用户资料字段 function add_custom_user_profile_fields($user) { if(!current_user_can('edit_user', $user->ID)) { return false; } ?> <h3>社区资料</h3> <table class="form-table"> <tr> <th><label for="user_location">所在地</label></th> <td> <input type="text" name="user_location" id="user_location" value="<?php echo esc_attr(get_the_author_meta('user_location', $user->ID)); ?>" class="regular-text" /> </td> </tr> <tr> <th><label for="user_occupation">职业</label></th> <td> <input type="text" name="user_occupation" id="user_occupation" value="<?php echo esc_attr(get_the_author_meta('user_occupation', $user->ID)); ?>" class="regular-text" /> </td> </tr> <tr> <th><label for="user_interests">兴趣标签</label></th> <td> <input type="text" name="user_interests" id="user_interests" value="<?php echo esc_attr(get_the_author_meta('user_interests', $user->ID)); ?>" class="regular-text" /> <p class="description">用逗号分隔多个兴趣标签</p> </td> </tr> <tr> <th><label for="user_signature">个人签名</label></th> <td> <textarea name="user_signature" id="user_signature" rows="3" cols="30"><?php echo esc_textarea(get_the_author_meta('user_signature', $user->ID)); ?></textarea> </td> </tr> </table> <?php } add_action('show_user_profile', 'add_custom_user_profile_fields'); add_action('edit_user_profile', 'add_custom_user_profile_fields'); // 保存自定义用户字段 function save_custom_user_profile_fields($user_id) { if(!current_user_can('edit_user', $user_id)) { return false; } update_user_meta($user_id, 'user_location', sanitize_text_field($_POST['user_location'])); update_user_meta($user_id, 'user_occupation', sanitize_text_field($_POST['user_occupation'])); update_user_meta($user_id, 'user_interests', sanitize_text_field($_POST['user_interests'])); update_user_meta($user_id, 'user_signature', sanitize_textarea_field($_POST['user_signature'])); } add_action('personal_options_update', 'save_custom_user_profile_fields'); add_action('edit_user_profile_update', 'save_custom_user_profile_fields'); 2.3 会员等级与权限控制 实现基于会员等级的权限控制系统: // 检查用户是否有特定权限 function community_user_can($user_id, $capability) { $user = get_user_by('id', $user_id); if(!$user) { return false; } // 获取用户角色 $user_roles = $user->roles; // 定义各角色权限映射 $role_capabilities = array( 'bronze_member' => array( 'access_basic_content', 'participate_in_forum', 'view_profiles' ), 'silver_member' => array( 'access_basic_content', 'access_premium_content', 'participate_in_forum', 'create_topics', 'view_profiles', 'send_private_messages' ), 'gold_member' => array( 'access_basic_content', 'access_premium_content', 'access_all_content', 'participate_in_forum', 'create_topics', 'moderate_comments', 'view_profiles', 'send_private_messages', 'access_exclusive_areas', 'download_resources' ), 'administrator' => array('all') // 管理员拥有所有权限 ); // 检查用户是否有权限 foreach($user_roles as $role) { if(array_key_exists($role, $role_capabilities)) { if(in_array('all', $role_capabilities[$role]) || in_array($capability, $role_capabilities[$role])) { return true; } } } return false; } // 使用示例:在模板中检查权限 function display_premium_content() { $user_id = get_current_user_id(); if(community_user_can($user_id, 'access_premium_content')) { // 显示高级内容 echo '<div class="premium-content">这里是高级会员专属内容</div>'; } else { // 显示升级提示 echo '<div class="upgrade-prompt">升级为高级会员以查看此内容</div>'; } } 第三部分:论坛系统开发与集成 3.1 基于bbPress的论坛定制 bbPress是一个轻量级论坛插件,我们可以通过代码进行深度定制: // 自定义论坛主题模板 function custom_bbpress_templates() { // 重写bbPress模板路径 return get_stylesheet_directory() . '/bbpress/'; } add_filter('bbp_get_template_stack', 'custom_bbpress_templates', 10, 1); // 创建自定义论坛分类 function create_custom_forum_structure() { // 技术讨论区 $tech_forum_id = wp_insert_post(array( 'post_title' => '技术讨论区', 'post_content' => '关于网站开发、编程技术的讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_author' => 1 )); // 在技术讨论区下创建子论坛 if($tech_forum_id) { wp_insert_post(array( 'post_title' => '前端开发', 'post_content' => 'HTML、CSS、JavaScript等前端技术讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_parent' => $tech_forum_id, 'post_author' => 1 )); wp_insert_post(array( 'post_title' => '后端开发', 'post_content' => 'PHP、Python、数据库等后端技术讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_parent' => $tech_forum_id, 'post_author' => 1 )); } // 创建其他论坛分类... } // 注释掉这行,除非您确实需要创建论坛结构 // add_action('init', 'create_custom_forum_structure'); 3.2 论坛权限与访问控制 // 限制特定论坛的访问权限 function restrict_forum_access($capabilities, $forum_id) { // 获取当前用户 $user_id = get_current_user_id(); // 获取论坛元数据 $forum_access_level = get_post_meta($forum_id, '_forum_access_level', true); // 如果没有设置访问级别,使用默认权限 if(empty($forum_access_level)) { return $capabilities; } // 检查用户是否有访问权限 if(!community_user_can($user_id, 'access_' . $forum_access_level . '_content')) { // 如果没有权限,移除阅读能力 $capabilities['read_others_topics'] = false; $capabilities['view_forum'] = false; } return $capabilities; } add_filter('bbp_get_forum_caps', 'restrict_forum_access', 10, 2); // 在论坛编辑页面添加访问级别选择 function add_forum_access_level_metabox() { add_meta_box( 'forum_access_level', '论坛访问级别', 'render_forum_access_level_metabox', 'forum', 'side', 'high' ); } add_action('add_meta_boxes', 'add_forum_access_level_metabox'); function render_forum_access_level_metabox($post) { wp_nonce_field('save_forum_access_level', 'forum_access_level_nonce'); $current_value = get_post_meta($post->ID, '_forum_access_level', true); $options = array( 'basic' => '基础会员', 'premium' => '高级会员', 'all' => '所有会员' ); echo '<select name="forum_access_level" id="forum_access_level">'; foreach($options as $value => $label) { echo '<option value="' . esc_attr($value) . '" ' . selected($current_value, $value, false) . '>' . esc_html($label) . '</option>'; } echo '</select>'; echo '<p class="description">设置可以访问此论坛的最低会员级别</p>'; } function save_forum_access_level($post_id) { if(!isset($_POST['forum_access_level_nonce']) || !wp_verify_nonce($_POST['forum_access_level_nonce'], 'save_forum_access_level')) { return; } if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if(!current_user_can('edit_post', $post_id)) { return; } if(isset($_POST['forum_access_level'])) { update_post_meta($post_id, '_forum_access_level', sanitize_text_field($_POST['forum_access_level'])); } } add_action('save_post_forum', 'save_forum_access_level'); 3.3 论坛功能增强 // 添加点赞功能 function add_topic_like_system() { // 在前端添加点赞按钮 add_action('bbp_theme_after_topic_content', 'display_like_button'); // 处理AJAX点赞请求 add_action('wp_ajax_topic_like', 'handle_topic_like'); add_action('wp_ajax_nopriv_topic_like', 'require_login_for_like'); } add_action('init', 'add_topic_like_system'); function display_like_button() { if(!is_singular('topic')) { return; } $topic_id = get_the_ID(); $user_id = get_current_user_id(); $like_count = get_post_meta($topic_id, '_topic_likes', true) ?: 0; $user_liked = $user_id ? get_user_meta($user_id, '_liked_topic_' . $topic_id, true) : false; ?> <div class="topic-like-system"> <button class="like-button <?php echo $user_liked ? 'liked' : ''; ?>" data-topic-id="<?php echo $topic_id; ?>"> <span class="like-icon">👍</span> <span class="like-count"><?php echo $like_count; ?></span> </button> </div> <script> jQuery(document).ready(function($) { $('.like-button').on('click', function() { var button = $(this); var topicId = button.data('topic-id'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'topic_like', topic_id: topicId, nonce: '<?php echo wp_create_nonce('topic_like_nonce'); ?>' }, success: function(response) { if(response.success) { button.toggleClass('liked'); button.find('.like-count').text(response.data.like_count); } else { alert(response.data.message); } } }); }); }); </script> <?php } function handle_topic_like() { // 验证nonce if(!wp_verify_nonce($_POST['nonce'], 'topic_like_nonce')) { wp_die('安全验证失败'); } $topic_id = intval($_POST['topic_id']); $user_id = get_current_user_id(); if(!$user_id) { wp_send_json_error(array('message' => '请先登录')); } $user_liked = get_user_meta($user_id, '_liked_topic_' . $topic_id, true); $like_count = get_post_meta($topic_id, '_topic_likes', true) ?: 0; if($user_liked) { // 取消点赞 delete_user_meta($user_id, '_liked_topic_' . $topic_id); $like_count = max(0, $like_count - 1); update_post_meta($topic_id, '_topic_likes', $like_count); } else { // 点赞 update_user_meta($user_id, '_liked_topic_' . $topic_id, true); $like_count++; update_post_meta($topic_id, '_topic_likes', $like_count); } wp_send_json_success(array('like_count' => $like_count)); } function require_login_for_like() { wp_send_json_error(array('message' => '请先登录')); } 第四部分:集成常用互联网小工具 4.1 实时通知系统 // 创建通知系统 class Community_Notification_System { private static $instance = null; public static function get_instance() { if(null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { 4.1 实时通知系统(续) add_action('wp_ajax_get_notifications', array($this, 'ajax_get_notifications')); add_action('wp_ajax_mark_notification_read', array($this, 'ajax_mark_notification_read')); add_action('wp_enqueue_scripts', array($this, 'enqueue_notification_scripts')); add_action('admin_bar_menu', array($this, 'add_notification_to_admin_bar'), 100); } // 创建新通知 public static function create_notification($user_id, $type, $message, $link = '', $sender_id = 0) { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { $notifications = array(); } $notification = array( 'id' => uniqid('notif_'), 'type' => $type, 'message' => $message, 'link' => $link, 'sender_id' => $sender_id, 'timestamp' => current_time('timestamp'), 'read' => false ); array_unshift($notifications, $notification); // 只保留最近的50条通知 if(count($notifications) > 50) { $notifications = array_slice($notifications, 0, 50); } update_user_meta($user_id, '_community_notifications', $notifications); // 更新未读计数 self::update_unread_count($user_id); return $notification['id']; } // 更新未读计数 private static function update_unread_count($user_id) { $notifications = get_user_meta($user_id, '_community_notifications', true); $unread_count = 0; if(is_array($notifications)) { foreach($notifications as $notification) { if(!$notification['read']) { $unread_count++; } } } update_user_meta($user_id, '_community_unread_notifications', $unread_count); return $unread_count; } // 获取用户通知 public static function get_user_notifications($user_id, $limit = 20) { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { return array(); } if($limit > 0) { $notifications = array_slice($notifications, 0, $limit); } return $notifications; } // 标记通知为已读 public static function mark_as_read($user_id, $notification_id = 'all') { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { return false; } $updated = false; foreach($notifications as &$notification) { if($notification_id === 'all' || $notification['id'] === $notification_id) { if(!$notification['read']) { $notification['read'] = true; $updated = true; } } } if($updated) { update_user_meta($user_id, '_community_notifications', $notifications); self::update_unread_count($user_id); } return $updated; } // AJAX获取通知 public function ajax_get_notifications() { if(!is_user_logged_in()) { wp_die('请先登录'); } $user_id = get_current_user_id(); $notifications = self::get_user_notifications($user_id, 10); wp_send_json_success(array( 'notifications' => $notifications, 'unread_count' => get_user_meta($user_id, '_community_unread_notifications', true) ?: 0 )); } // AJAX标记通知为已读 public function ajax_mark_notification_read() { if(!is_user_logged_in()) { wp_die('请先登录'); } $user_id = get_current_user_id(); $notification_id = isset($_POST['notification_id']) ? sanitize_text_field($_POST['notification_id']) : 'all'; self::mark_as_read($user_id, $notification_id); wp_send_json_success(); } // 加载通知脚本 public function enqueue_notification_scripts() { if(!is_user_logged_in()) { return; } wp_enqueue_script('community-notifications', get_template_directory_uri() . '/js/notifications.js', array('jquery'), '1.0', true); wp_localize_script('community-notifications', 'communityNotifications', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('notification_nonce'), 'poll_interval' => 30000 // 30秒轮询一次 )); } // 在管理栏添加通知 public function add_notification_to_admin_bar($admin_bar) { if(!is_user_logged_in()) { return; } $user_id = get_current_user_id(); $unread_count = get_user_meta($user_id, '_community_unread_notifications', true) ?: 0; $admin_bar->add_node(array( 'id' => 'community-notifications', 'title' => '通知' . ($unread_count > 0 ? ' <span class="notification-count">' . $unread_count . '</span>' : ''), 'href' => '#', 'meta' => array( 'class' => 'community-notifications-menu', 'html' => $this->get_notification_dropdown_html() ) )); } // 获取通知下拉菜单HTML private function get_notification_dropdown_html() { if(!is_user_logged_in()) { return ''; } $user_id = get_current_user_id(); $notifications = self::get_user_notifications($user_id, 5); ob_start(); ?> <div class="notification-dropdown" style="display: none;"> <div class="notification-header"> <h3>通知</h3> <?php if(!empty($notifications)): ?> <a href="#" class="mark-all-read">全部标记为已读</a> <?php endif; ?> </div> <div class="notification-list"> <?php if(empty($notifications)): ?> <div class="notification-item no-notifications"> 暂无新通知 </div> <?php else: ?> <?php foreach($notifications as $notification): ?> <div class="notification-item <?php echo $notification['read'] ? 'read' : 'unread'; ?>" data-notification-id="<?php echo esc_attr($notification['id']); ?>"> <div class="notification-content"> <p><?php echo esc_html($notification['message']); ?></p> <span class="notification-time"><?php echo human_time_diff($notification['timestamp'], current_time('timestamp')) . '前'; ?></span> </div> <?php if(!empty($notification['link'])): ?> <a href="<?php echo esc_url($notification['link']); ?>" class="notification-link"></a> <?php endif; ?> </div> <?php endforeach; ?> <?php endif; ?> </div> <div class="notification-footer"> <a href="<?php echo esc_url(home_url('/notifications/')); ?>">查看所有通知</a> </div> </div> <?php return ob_get_clean(); } } // 初始化通知系统 Community_Notification_System::get_instance(); // 创建通知触发器 function trigger_community_notifications() { // 当用户收到私信时 add_action('bp_messages_message_sent', function($message) { $recipients = $message->get_recipients(); foreach($recipients as $recipient) { if($recipient->user_id != $message->sender_id) { $sender = get_userdata($message->sender_id); Community_Notification_System::create_notification( $recipient->user_id, 'message', $sender->display_name . ' 给您发送了一条私信', bp_core_get_user_domain($message->sender_id) . 'messages/view/' . $message->thread_id . '/', $message->sender_id ); } } }); // 当用户的话题收到回复时 add_action('bbp_new_reply', function($reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author) { $topic_author = get_post_field('post_author', $topic_id); if($topic_author != $reply_author) { $reply_author_name = get_the_author_meta('display_name', $reply_author); Community_Notification_System::create_notification( $topic_author, 'reply', $reply_author_name . ' 回复了您的话题', get_permalink($reply_id), $reply_author ); } }, 10, 5); } add_action('init', 'trigger_community_notifications'); 4.2 私信系统 // 增强私信功能 class Enhanced_Private_Messaging { public function __construct() { // 检查BuddyPress是否存在 if(!function_exists('bp_is_active')) { return; } // 添加消息类型 add_filter('bp_messages_allowed_tags', array($this, 'allow_additional_message_tags')); // 添加消息附件功能 add_action('bp_after_messages_compose_content', array($this, 'add_attachment_field')); add_action('messages_message_sent', array($this, 'handle_message_attachment'), 10, 1); // 添加消息搜索 add_action('bp_messages_directory_search_form', array($this, 'add_message_search')); // 添加消息分类标签 add_action('bp_before_message_compose_input', array($this, 'add_message_labels')); } // 允许更多HTML标签 public function allow_additional_message_tags($allowed_tags) { $allowed_tags['img'] = array( 'src' => true, 'alt' => true, 'width' => true, 'height' => true, 'class' => true ); $allowed_tags['a']['target'] = true; $allowed_tags['code'] = array(); $allowed_tags['pre'] = array(); return $allowed_tags; } // 添加附件字段 public function add_attachment_field() { if(!community_user_can(get_current_user_id(), 'send_private_messages')) { return; } ?> <div class="message-attachment-field"> <label for="message-attachment">添加附件</label> <input type="file" name="message_attachment" id="message-attachment" accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.txt"> <p class="description">支持图片、文档文件,最大2MB</p> </div> <script> jQuery(document).ready(function($) { $('#send_message_form').on('submit', function(e) { var fileInput = $('#message-attachment')[0]; if(fileInput.files.length > 0) { var file = fileInput.files[0]; var maxSize = 2 * 1024 * 1024; // 2MB if(file.size > maxSize) { alert('文件大小不能超过2MB'); e.preventDefault(); return false; } // 创建FormData对象 var formData = new FormData(this); formData.append('message_attachment', file); // 使用AJAX提交表单 e.preventDefault(); $.ajax({ url: $(this).attr('action'), type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { // 处理成功响应 window.location.reload(); } }); } }); }); </script> <?php } // 处理消息附件 public function handle_message_attachment($message) { if(!empty($_FILES['message_attachment']['name'])) { require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload = wp_handle_upload($_FILES['message_attachment'], array( 'test_form' => false, 'action' => 'bp_message_attachment' )); if(!isset($upload['error'])) { // 保存附件信息到消息元数据 bp_messages_update_meta($message->id, 'attachment_url', $upload['url']); bp_messages_update_meta($message->id, 'attachment_type', $_FILES['message_attachment']['type']); bp_messages_update_meta($message->id, 'attachment_name', $_FILES['message_attachment']['name']); } } } // 显示消息附件 public static function display_message_attachment($message_id) { $attachment_url = bp_messages_get_meta($message_id, 'attachment_url', true); if($attachment_url) { $attachment_type = bp_messages_get_meta($message_id, 'attachment_type', true); $attachment_name = bp_messages_get_meta($message_id, 'attachment_name', true); if(strpos($attachment_type, 'image/') === 0) { echo '<div class="message-attachment image-attachment">'; echo '<a href="' . esc_url($attachment_url) . '" target="_blank">'; echo '<img src="' . esc_url($attachment_url) . '" alt="' . esc_attr($attachment_name) . '" style="max-width: 300px;">'; echo '</a>'; echo '</div>'; } else { echo '<div class="message-attachment file-attachment">'; echo '<a href="' . esc_url($attachment_url) . '" target="_blank" class="attachment-link">'; echo '<span class="attachment-icon">📎</span>'; echo '<span class="attachment-name">' . esc_html($attachment_name) . '</span>'; echo '</a>'; echo '</div>'; } } } // 添加消息搜索 public function add_message_search() { ?> <div class="message-search-form"> <form action="<?php echo bp_get_messages_directory_permalink(); ?>" method="get"> <input type="text" name="s" placeholder="搜索消息..." value="<?php echo isset($_GET['s']) ? esc_attr($_GET['s']) : ''; ?>"> <button type="submit">搜索</button> </form> </div> <?php } // 添加消息标签 public function add_message_labels() { ?> <div class="message-labels"> <label>消息标签:</label> <select name="message_label" id="message-label"> <option value="">无标签</option> <option value="important">重要</option> <option value="work">工作</option> <option value="personal">个人</option> <option value="question">问题</option> </select> </div> <?php } } // 初始化增强私信系统 new Enhanced_Private_Messaging(); // 在消息显示时添加附件 add_action('bp_after_message_content', function() { global $messages_template; if(isset($messages_template->message->id)) { Enhanced_Private_Messaging::display_message_attachment($messages_template->message->id); } }); 4.3 积分与成就系统 // 积分与成就系统 class Points_and_Achievements_System { private $achievements = array(); public function __construct() { $this->define_achievements(); add_action('init', array($this, 'register_points_post_type')); add_action('user_register', array($this, 'award_registration_points')); add_action('bbp_new_topic', array($this, 'award_topic_creation_points'), 10, 4); add_action('bbp_new_reply', array($this, 'award_reply_points'), 10, 5); add_action('bp_activity_add', array($this, 'check_activity_achievements')); add_action('wp_ajax_get_user_points', array($this, 'ajax_get_user_points')); add_action('wp_enqueue_scripts', array($this, 'enqueue_points_scripts')); // 添加积分显示到用户资料 add_action('bp_profile_header_meta', array($this, 'display_user_points_in_profile')); } // 定义成就 private function define_achievements() { $this->achievements = array( 'first_post' => array( 'name' => '初来乍到', 'description' => '发表第一个帖子', 'points' => 100, 'badge' => '🥇' ), 'active_member' => array( 'name' => '活跃会员', 'description' => '连续7天登录', 'points' => 200, 'badge' => '🔥' ), 'helpful_member' => array( 'name' => '热心助人', 'description' => '获得10个感谢', 'points' => 300, 'badge' => '❤️' ), 'expert' => array( 'name' => '社区专家', 'description' => '发表50个高质量回复', 'points' => 500, 'badge' => '👑' ), 'veteran' => array(
发表评论WordPress高级教程:开发集成AI的智能内容创作与辅助写作工具 引言:WordPress在AI时代的新机遇 WordPress作为全球最流行的内容管理系统,占据了互联网超过43%的网站份额。随着人工智能技术的飞速发展,将AI能力集成到WordPress平台已成为提升内容创作效率和质量的重要趋势。本教程将深入探讨如何通过WordPress代码二次开发,构建集成AI的智能内容创作与辅助写作工具,同时实现常用互联网小工具功能,为网站管理员和内容创作者提供全方位的智能解决方案。 在当今信息爆炸的时代,内容创作者面临着日益增长的创作压力和质量要求。AI技术的引入不仅可以自动化部分创作流程,还能提供智能建议、优化内容结构、增强可读性,甚至生成个性化内容。通过将AI能力与WordPress的强大扩展性相结合,我们可以打造出真正智能化的内容创作生态系统。 第一部分:WordPress开发环境搭建与AI技术选型 1.1 WordPress开发环境配置 要开始WordPress高级开发,首先需要搭建专业的开发环境。推荐使用以下配置: 本地开发环境:使用Local by Flywheel、XAMPP或Docker容器化环境 代码编辑器:VS Code配合PHP Intelephense、WordPress代码片段等扩展 调试工具:安装Query Monitor、Debug Bar等WordPress调试插件 版本控制:Git配合GitHub或GitLab进行代码管理 对于AI集成开发,还需要确保服务器环境满足以下要求: PHP 7.4或更高版本 至少256MB内存限制(建议512MB以上) 支持cURL扩展 可选的GPU加速支持(用于本地AI模型运行) 1.2 AI技术选型与集成策略 选择合适的AI技术是项目成功的关键。根据功能需求和资源情况,可以考虑以下方案: 云端AI服务集成: OpenAI GPT系列API:用于内容生成、改写、摘要 Google Cloud Natural Language:用于情感分析、实体识别 IBM Watson:用于语气分析、内容优化 国内可选:百度文心、阿里通义、腾讯混元等 本地AI模型部署: 使用Transformers库部署轻量级模型 利用ONNX Runtime优化推理性能 考虑使用GPT-2、BERT等开源模型 混合方案: 核心功能使用云端API保证质量 辅助功能使用本地模型降低成本 敏感数据处理使用本地模型确保隐私 第二部分:WordPress插件架构设计与AI集成 2.1 智能内容创作插件架构设计 创建一个名为"AI Content Creator Pro"的WordPress插件,采用模块化设计: /* Plugin Name: AI Content Creator Pro Plugin URI: https://yourwebsite.com/ai-content-creator Description: 集成AI的智能内容创作与辅助写作工具 Version: 1.0.0 Author: Your Name License: GPL v2 or later */ // 主插件类 class AI_Content_Creator_Pro { private static $instance = null; private $ai_services = []; private $tools_manager; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->define_constants(); $this->init_hooks(); $this->load_dependencies(); } private function define_constants() { define('AICC_VERSION', '1.0.0'); define('AICC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('AICC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('AICC_AI_CACHE_EXPIRY', 24 * HOUR_IN_SECONDS); } private function init_hooks() { add_action('init', [$this, 'init_plugin']); add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_action('add_meta_boxes', [$this, 'add_content_ai_metabox']); } private function load_dependencies() { // 加载核心模块 require_once AICC_PLUGIN_DIR . 'includes/class-ai-service-manager.php'; require_once AICC_PLUGIN_DIR . 'includes/class-content-generator.php'; require_once AICC_PLUGIN_DIR . 'includes/class-writing-assistant.php'; require_once AICC_PLUGIN_DIR . 'includes/class-tools-manager.php'; require_once AICC_PLUGIN_DIR . 'includes/class-utilities.php'; // 加载AI服务适配器 require_once AICC_PLUGIN_DIR . 'includes/ai-services/class-openai-adapter.php'; require_once AICC_PLUGIN_DIR . 'includes/ai-services/class-local-ai-adapter.php'; // 加载工具模块 require_once AICC_PLUGIN_DIR . 'includes/tools/class-seo-analyzer.php'; require_once AICC_PLUGIN_DIR . 'includes/tools/class-readability-checker.php'; require_once AICC_PLUGIN_DIR . 'includes/tools/class-plagiarism-checker.php'; } public function init_plugin() { $this->ai_services = AI_Service_Manager::get_available_services(); $this->tools_manager = new Tools_Manager(); // 初始化短代码 $this->init_shortcodes(); } // ... 其他方法 } 2.2 AI服务管理器实现 AI服务管理器负责统一接口调用不同的AI服务: class AI_Service_Manager { private static $services = []; public static function register_service($service_id, $class_name, $config = []) { self::$services[$service_id] = [ 'class' => $class_name, 'config' => $config, 'instance' => null ]; } public static function get_service($service_id = 'default') { if (!isset(self::$services[$service_id])) { return false; } if (null === self::$services[$service_id]['instance']) { $class_name = self::$services[$service_id]['class']; self::$services[$service_id]['instance'] = new $class_name( self::$services[$service_id]['config'] ); } return self::$services[$service_id]['instance']; } public static function get_available_services() { $services = []; // 检查OpenAI配置 $openai_key = get_option('aicc_openai_api_key', ''); if (!empty($openai_key)) { self::register_service('openai', 'OpenAI_Adapter', [ 'api_key' => $openai_key, 'model' => get_option('aicc_openai_model', 'gpt-3.5-turbo'), 'max_tokens' => get_option('aicc_openai_max_tokens', 1000) ]); $services['openai'] = 'OpenAI GPT'; } // 检查本地AI服务 if (self::is_local_ai_available()) { self::register_service('local', 'Local_AI_Adapter', [ 'model_path' => get_option('aicc_local_model_path', ''), 'use_gpu' => get_option('aicc_use_gpu', false) ]); $services['local'] = '本地AI模型'; } return $services; } private static function is_local_ai_available() { // 检查本地AI环境是否可用 $model_path = get_option('aicc_local_model_path', ''); return !empty($model_path) && file_exists($model_path); } public static function generate_content($prompt, $options = [], $service_id = 'default') { $service = self::get_service($service_id); if (!$service) { return new WP_Error('service_unavailable', 'AI服务不可用'); } // 检查缓存 $cache_key = 'aicc_content_' . md5($prompt . serialize($options) . $service_id); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // 调用AI服务 $result = $service->generate($prompt, $options); // 缓存结果 if (!is_wp_error($result)) { set_transient($cache_key, $result, AICC_AI_CACHE_EXPIRY); } return $result; } } 第三部分:核心功能实现 - 智能内容创作 3.1 AI内容生成器实现 内容生成器是插件的核心功能,支持多种内容类型生成: class Content_Generator { private $ai_service; public function __construct($service_id = 'default') { $this->ai_service = AI_Service_Manager::get_service($service_id); } public function generate_blog_post($topic, $options = []) { $default_options = [ 'tone' => 'informative', 'length' => 'medium', 'target_audience' => 'general', 'include_seo_keywords' => true, 'structure' => 'introduction-body-conclusion' ]; $options = wp_parse_args($options, $default_options); // 构建AI提示词 $prompt = $this->build_blog_post_prompt($topic, $options); // 调用AI生成内容 $content = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => $this->get_token_length($options['length']), 'temperature' => 0.7, 'top_p' => 0.9 ]); if (is_wp_error($content)) { return $content; } // 后处理内容 $processed_content = $this->post_process_content($content, $options); return [ 'title' => $this->extract_title($processed_content), 'content' => $processed_content, 'excerpt' => $this->generate_excerpt($processed_content), 'meta_description' => $this->generate_meta_description($processed_content), 'tags' => $this->extract_tags($processed_content, $topic) ]; } private function build_blog_post_prompt($topic, $options) { $prompt_templates = [ 'informative' => "请以专业、 informative 的语气撰写一篇关于 {$topic} 的博客文章。", 'conversational' => "请以对话式、友好的语气撰写一篇关于 {$topic} 的博客文章。", 'persuasive' => "请以有说服力的语气撰写一篇关于 {$topic} 的博客文章,旨在说服读者采取行动。" ]; $tone = isset($prompt_templates[$options['tone']]) ? $prompt_templates[$options['tone']] : $prompt_templates['informative']; $length_instructions = [ 'short' => '文章长度约500字左右,内容简洁明了。', 'medium' => '文章长度约1000-1500字,内容详实。', 'long' => '文章长度约2000字以上,内容全面深入。' ]; $length = isset($length_instructions[$options['length']]) ? $length_instructions[$options['length']] : $length_instructions['medium']; $structure_guide = "文章结构应包括:引言、主体内容(分几个小节阐述)和结论。"; $seo_instruction = $options['include_seo_keywords'] ? "请自然地包含相关关键词,但不要堆砌。" : ""; $audience_instruction = "目标读者是{$options['target_audience']},请使用他们容易理解的语言。"; return "{$tone} {$length} {$structure_guide} {$seo_instruction} {$audience_instruction}"; } private function get_token_length($length) { $lengths = [ 'short' => 800, 'medium' => 1500, 'long' => 3000 ]; return isset($lengths[$length]) ? $lengths[$length] : $lengths['medium']; } public function generate_content_ideas($seed_topic, $count = 5) { $prompt = "基于主题'{$seed_topic}',生成{$count}个相关的博客文章创意。每个创意包括标题和简要描述。"; $ideas = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 800, 'temperature' => 0.8 ]); if (is_wp_error($ideas)) { return $ideas; } return $this->parse_content_ideas($ideas); } public function rewrite_content($original_content, $options = []) { $default_options = [ 'purpose' => 'improve_clarity', 'tone' => null, 'simplify' => false ]; $options = wp_parse_args($options, $default_options); $purpose_instructions = [ 'improve_clarity' => '提高内容清晰度和可读性', 'seo_optimization' => '优化SEO,自然地加入关键词', 'change_tone' => '改变文章语气为' . ($options['tone'] ?: '专业'), 'summarize' => '总结内容,保留核心信息' ]; $instruction = isset($purpose_instructions[$options['purpose']]) ? $purpose_instructions[$options['purpose']] : '改进内容'; $prompt = "请重新改写以下内容,要求:{$instruction}。" . ($options['simplify'] ? "请简化语言,使其更易理解。" : "") . "nn原文:n{$original_content}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => strlen($original_content) * 1.5, 'temperature' => 0.6 ]); } // ... 其他内容生成方法 } 3.2 智能写作助手实现 写作助手在编辑器中提供实时AI辅助: class Writing_Assistant { public function __construct() { add_action('admin_init', [$this, 'init_writing_assistant']); } public function init_writing_assistant() { // 添加编辑器按钮 add_filter('mce_buttons', [$this, 'add_tinymce_buttons']); add_filter('mce_external_plugins', [$this, 'add_tinymce_plugins']); // 添加Gutenberg块 if (function_exists('register_block_type')) { add_action('init', [$this, 'register_gutenberg_blocks']); } // 添加AJAX处理 add_action('wp_ajax_aicc_writing_assist', [$this, 'handle_ajax_request']); } public function add_tinymce_buttons($buttons) { array_push($buttons, 'aicc_assistant'); return $buttons; } public function add_tinymce_plugins($plugins) { $plugins['aicc_assistant'] = AICC_PLUGIN_URL . 'assets/js/tinymce-plugin.js'; return $plugins; } public function register_gutenberg_blocks() { register_block_type('aicc/writing-assistant', [ 'editor_script' => 'aicc-gutenberg-blocks', 'render_callback' => [$this, 'render_writing_assistant_block'] ]); } public function handle_ajax_request() { check_ajax_referer('aicc_writing_assist_nonce', 'nonce'); $action = sanitize_text_field($_POST['action_type']); $content = wp_kses_post($_POST['content']); $options = isset($_POST['options']) ? $_POST['options'] : []; switch ($action) { case 'expand_idea': $result = $this->expand_idea($content, $options); break; case 'improve_sentence': $result = $this->improve_sentence($content, $options); break; case 'generate_headline': $result = $this->generate_headline($content, $options); break; case 'check_grammar': $result = $this->check_grammar($content); break; default: $result = new WP_Error('invalid_action', '无效的操作类型'); } if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } else { wp_send_json_success($result); } } public function expand_idea($idea, $options = []) { $prompt = "请扩展以下想法,形成完整的段落:nn{$idea}"; if (!empty($options['tone'])) { $prompt .= "nn使用{$options['tone']}的语气。"; } return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 500, 'temperature' => 0.7 ]); } public function improve_sentence($sentence, $options = []) { $improvement_type = $options['type'] ?? 'clarity'; $instructions = [ 'clarity' => '提高清晰度和可读性', 'conciseness' => '使表达更简洁', 'formality' => '使语气更正式', 'creativity' => '增加创意和表现力' ]; $instruction = isset($instructions[$improvement_type]) ? $instructions[$improvement_type] : '改进句子'; $prompt = "请{$instruction}:nn{$sentence}nn提供3个改进版本。"; 3.3 内容优化与SEO增强功能 class SEO_Analyzer { public function analyze_content($content, $focus_keyword = '') { $analysis = [ 'score' => 0, 'checks' => [], 'suggestions' => [] ]; // 基础分析 $analysis['checks']['word_count'] = $this->check_word_count($content); $analysis['checks']['headings'] = $this->check_headings($content); $analysis['checks']['paragraph_length'] = $this->check_paragraph_length($content); $analysis['checks']['link_count'] = $this->check_link_count($content); // SEO特定分析 if (!empty($focus_keyword)) { $analysis['checks']['keyword_density'] = $this->check_keyword_density($content, $focus_keyword); $analysis['checks']['keyword_in_title'] = $this->check_keyword_in_title($content, $focus_keyword); $analysis['checks']['keyword_in_meta'] = $this->check_keyword_in_meta($content, $focus_keyword); $analysis['checks']['keyword_in_first_paragraph'] = $this->check_keyword_in_first_paragraph($content, $focus_keyword); } // 可读性分析 $analysis['checks']['readability'] = $this->check_readability($content); // 计算总分 $analysis['score'] = $this->calculate_total_score($analysis['checks']); // 生成建议 $analysis['suggestions'] = $this->generate_suggestions($analysis['checks']); return $analysis; } private function check_word_count($content) { $word_count = str_word_count(strip_tags($content)); $result = [ 'passed' => false, 'value' => $word_count, 'message' => '' ]; if ($word_count >= 300) { $result['passed'] = true; $result['message'] = "文章长度合适({$word_count}字)"; } else { $result['message'] = "文章可能过短({$word_count}字),建议至少300字"; } return $result; } private function check_keyword_density($content, $keyword) { $clean_content = strip_tags(strtolower($content)); $keyword_lower = strtolower($keyword); $total_words = str_word_count($clean_content); $keyword_count = substr_count($clean_content, $keyword_lower); $density = $total_words > 0 ? ($keyword_count / $total_words) * 100 : 0; $result = [ 'passed' => false, 'value' => round($density, 2), 'message' => '' ]; if ($density >= 0.5 && $density <= 2.5) { $result['passed'] = true; $result['message'] = "关键词密度合适({$result['value']}%)"; } elseif ($density < 0.5) { $result['message'] = "关键词密度偏低({$result['value']}%),建议适当增加"; } else { $result['message'] = "关键词密度偏高({$result['value']}%),可能被判定为关键词堆砌"; } return $result; } public function generate_seo_title($content, $keyword = '') { $prompt = "为以下内容生成一个SEO友好的标题"; if (!empty($keyword)) { $prompt .= ",需要包含关键词'{$keyword}'"; } $prompt .= ":nn" . substr(strip_tags($content), 0, 500); $titles = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 200, 'temperature' => 0.7 ]); if (is_wp_error($titles)) { return $titles; } return $this->extract_titles_from_response($titles); } public function generate_meta_description($content, $keyword = '') { $prompt = "为以下内容生成一个吸引点击的meta描述(150-160字符)"; if (!empty($keyword)) { $prompt .= ",自然地包含关键词'{$keyword}'"; } $prompt .= ":nn" . substr(strip_tags($content), 0, 300); $description = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 100, 'temperature' => 0.6 ]); if (is_wp_error($description)) { return $description; } // 清理和截断描述 $clean_description = preg_replace('/s+/', ' ', strip_tags($description)); return substr($clean_description, 0, 160); } public function suggest_lsi_keywords($content, $main_keyword) { $prompt = "基于以下内容和主关键词'{$main_keyword}',生成5个相关的LSI(潜在语义索引)关键词:nn" . substr(strip_tags($content), 0, 1000); $keywords = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 150, 'temperature' => 0.5 ]); if (is_wp_error($keywords)) { return $keywords; } return $this->extract_keywords_from_response($keywords); } } 第四部分:常用互联网小工具集成 4.1 多功能工具管理器 class Tools_Manager { private $tools = []; public function __construct() { $this->register_default_tools(); add_action('init', [$this, 'init_tools']); } private function register_default_tools() { $this->register_tool('seo_analyzer', [ 'name' => 'SEO分析工具', 'description' => '分析内容SEO表现并提供优化建议', 'class' => 'SEO_Analyzer', 'ajax_action' => 'aicc_analyze_seo' ]); $this->register_tool('readability_checker', [ 'name' => '可读性检查', 'description' => '检查内容可读性水平', 'class' => 'Readability_Checker', 'ajax_action' => 'aicc_check_readability' ]); $this->register_tool('plagiarism_checker', [ 'name' => '原创性检查', 'description' => '检查内容原创性', 'class' => 'Plagiarism_Checker', 'ajax_action' => 'aicc_check_plagiarism' ]); $this->register_tool('text_summarizer', [ 'name' => '文本摘要', 'description' => '自动生成内容摘要', 'class' => 'Text_Summarizer', 'ajax_action' => 'aicc_summarize_text' ]); $this->register_tool('image_alt_generator', [ 'name' => '图片ALT文本生成', 'description' => '为图片生成SEO友好的ALT文本', 'class' => 'Image_Alt_Generator', 'ajax_action' => 'aicc_generate_alt_text' ]); } public function register_tool($tool_id, $config) { $this->tools[$tool_id] = wp_parse_args($config, [ 'name' => $tool_id, 'description' => '', 'class' => '', 'ajax_action' => '', 'enabled' => true, 'settings' => [] ]); } public function init_tools() { foreach ($this->tools as $tool_id => $tool) { if ($tool['enabled'] && !empty($tool['ajax_action'])) { add_action("wp_ajax_{$tool['ajax_action']}", [$this, "handle_{$tool_id}_request"]); if (!empty($tool['class']) && class_exists($tool['class'])) { $this->tools[$tool_id]['instance'] = new $tool['class'](); } } } // 注册短代码 $this->register_tool_shortcodes(); } public function register_tool_shortcodes() { add_shortcode('aicc_tool', [$this, 'render_tool_shortcode']); add_shortcode('aicc_seo_analyzer', [$this, 'render_seo_analyzer']); add_shortcode('aicc_readability_check', [$this, 'render_readability_checker']); add_shortcode('aicc_text_summarizer', [$this, 'render_text_summarizer']); } public function render_tool_shortcode($atts) { $atts = shortcode_atts([ 'tool' => 'seo_analyzer', 'title' => '', 'width' => '100%', 'height' => '400px' ], $atts); $tool_id = sanitize_text_field($atts['tool']); if (!isset($this->tools[$tool_id]) || !$this->tools[$tool_id]['enabled']) { return '<p>工具不可用</p>'; } ob_start(); ?> <div class="aicc-tool-container" data-tool="<?php echo esc_attr($tool_id); ?>"> <?php if (!empty($atts['title'])): ?> <h3><?php echo esc_html($atts['title']); ?></h3> <?php endif; ?> <div class="aicc-tool-content"> <?php $this->render_tool_interface($tool_id); ?> </div> </div> <style> .aicc-tool-container { width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>; border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin: 20px 0; background: #f9f9f9; } </style> <?php return ob_get_clean(); } private function render_tool_interface($tool_id) { switch ($tool_id) { case 'seo_analyzer': $this->render_seo_analyzer_interface(); break; case 'readability_checker': $this->render_readability_checker_interface(); break; case 'text_summarizer': $this->render_text_summarizer_interface(); break; default: echo '<p>该工具界面正在开发中</p>'; } } private function render_seo_analyzer_interface() { ?> <div class="aicc-seo-analyzer"> <textarea id="aicc-seo-content" placeholder="请输入要分析的内容..." rows="8" style="width:100%;"></textarea> <input type="text" id="aicc-seo-keyword" placeholder="主关键词(可选)" style="width:100%; margin:10px 0; padding:8px;"> <div style="margin:15px 0;"> <label> <input type="checkbox" id="aicc-check-readability" checked> 检查可读性 </label> <label style="margin-left:15px;"> <input type="checkbox" id="aicc-check-structure" checked> 检查结构 </label> </div> <button id="aicc-analyze-seo-btn" class="button button-primary">分析SEO</button> <div id="aicc-seo-results" style="margin-top:20px; display:none;"> <div class="aicc-score-circle" style="width:100px; height:100px; border-radius:50%; border:5px solid #ddd; display:flex; align-items:center; justify-content:center; margin:0 auto;"> <span id="aicc-seo-score" style="font-size:24px; font-weight:bold;">0</span> </div> <div id="aicc-seo-checks" style="margin-top:20px;"></div> <div id="aicc-seo-suggestions" style="margin-top:20px; padding:15px; background:#fff; border-radius:5px;"></div> </div> </div> <script> jQuery(document).ready(function($) { $('#aicc-analyze-seo-btn').on('click', function() { var content = $('#aicc-seo-content').val(); var keyword = $('#aicc-seo-keyword').val(); if (!content.trim()) { alert('请输入要分析的内容'); return; } $(this).prop('disabled', true).text('分析中...'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'aicc_analyze_seo', content: content, keyword: keyword, nonce: '<?php echo wp_create_nonce("aicc_seo_analysis"); ?>' }, success: function(response) { if (response.success) { var data = response.data; $('#aicc-seo-score').text(data.score); $('#aicc-seo-results').show(); // 更新检查结果 var checksHtml = '<h4>检查结果:</h4><ul>'; for (var check in data.checks) { var checkData = data.checks[check]; var statusIcon = checkData.passed ? '✅' : '❌'; checksHtml += '<li>' + statusIcon + ' ' + checkData.message + '</li>'; } checksHtml += '</ul>'; $('#aicc-seo-checks').html(checksHtml); // 更新建议 if (data.suggestions && data.suggestions.length > 0) { var suggestionsHtml = '<h4>优化建议:</h4><ul>'; data.suggestions.forEach(function(suggestion) { suggestionsHtml += '<li>💡 ' + suggestion + '</li>'; }); suggestionsHtml += '</ul>'; $('#aicc-seo-suggestions').html(suggestionsHtml); } // 更新分数圆环颜色 var scoreCircle = $('.aicc-score-circle'); var score = parseInt(data.score); var color; if (score >= 80) color = '#4CAF50'; else if (score >= 60) color = '#FFC107'; else color = '#F44336'; scoreCircle.css('border-color', color); } else { alert('分析失败:' + response.data); } }, error: function() { alert('请求失败,请重试'); }, complete: function() { $('#aicc-analyze-seo-btn').prop('disabled', false).text('分析SEO'); } }); }); }); </script> <?php } } 4.2 实用小工具集合实现 class Text_Summarizer { public function summarize($text, $options = []) { $default_options = [ 'length' => 'medium', // short, medium, long 'format' => 'paragraph', // paragraph, bullet_points 'focus' => 'main_points' // main_points, key_facts, conclusions ]; $options = wp_parse_args($options, $default_options); $length_instructions = [ 'short' => '生成一个非常简短的摘要(约50字)', 'medium' => '生成一个中等长度的摘要(约100-150字)', 'long' => '生成一个详细的摘要(约200-300字)' ]; $format_instructions = [ 'paragraph' => '以段落形式呈现', 'bullet_points' => '以要点列表形式呈现' ]; $focus_instructions = [ 'main_points' => '重点关注主要观点', 'key_facts' => '重点关注关键事实和数据', 'conclusions' => '重点关注结论和建议' ]; $instruction = $length_instructions[$options['length']] . ',' . $format_instructions[$options['format']] . ',' . $focus_instructions[$options['focus']]; $prompt = "请为以下文本{$instruction}:nn{$text}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => $this->get_token_limit($options['length']), 'temperature' => 0.5 ]); } public function generate_tldr($text) { $prompt = "为以下文本生成一个'太长不看版'(TL;DR)摘要,要求简洁明了:nn{$text}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 100, 'temperature' => 0.4 ]); } } class Readability_Checker { public function analyze($text) { $analysis = [ 'flesch_reading_ease' => $this->calculate_flesch_reading_ease($text), 'flesch_kincaid_grade' => $this->calculate_flesch_kincaid_grade($text), 'gunning_fog' => $this->calculate_gunning_fog_index($text), 'smog_index' => $this->calculate_smog_index($text), 'automated_readability' => $this->calculate_automated_readability($text), 'coleman_liau' => $this->calculate_coleman_liau_index($text) ]; $analysis['overall_level'] = $this->determine_readability_level($analysis);
发表评论