跳至内容

分类: 应用软件

一步步教你,为WordPress网站添加智能内容推荐引擎

一步步教你,为WordPress网站添加智能内容推荐引擎 引言:为什么你的WordPress网站需要智能推荐? 在信息过载的互联网时代,用户注意力已成为最稀缺的资源。据统计,拥有个性化内容推荐的网站,用户停留时间平均增加48%,页面浏览量提升35%,转化率提高20%以上。对于WordPress网站运营者而言,仅仅发布优质内容已远远不够,如何让访客发现更多相关内容,降低跳出率,提高用户粘性,已成为决定网站成败的关键因素。 目前市面上虽然有许多推荐插件,但它们往往存在诸多限制:要么功能过于简单,要么算法不够智能,要么需要支付高昂的订阅费用。更重要的是,这些通用插件无法完全契合每个网站独特的业务逻辑和用户需求。通过代码二次开发,我们不仅能打造完全符合自身需求的智能推荐引擎,还能将其深度集成到网站生态中,实现数据闭环和持续优化。 本文将带你从零开始,通过WordPress程序的代码二次开发,构建一个功能完整、算法可调的智能内容推荐系统。这个系统将包含基于内容的过滤、协同过滤、混合推荐等多种策略,并能通过用户行为数据不断自我优化。 第一部分:准备工作与环境搭建 1.1 理解WordPress推荐系统的基本原理 智能推荐引擎的核心是通过分析用户行为和内容特征,预测用户可能感兴趣的内容。在WordPress环境中,我们需要处理三种基本数据类型: 用户数据:注册用户信息、浏览历史、点击行为、停留时间、评分/点赞等 内容数据:文章标签、分类、关键词、元数据、作者、发布时间等 交互数据:用户与内容的每一次互动记录 推荐算法主要分为以下几类: 基于内容的推荐:分析用户过去喜欢的内容特征,推荐具有相似特征的新内容 协同过滤:找到与目标用户兴趣相似的其他用户,将他们喜欢的内容推荐给目标用户 混合推荐:结合多种推荐策略,取长补短,提高推荐质量 1.2 开发环境配置 在开始开发前,请确保你的环境满足以下条件: WordPress版本:5.6或更高版本(确保REST API功能完整) PHP版本:7.4或更高(推荐8.0+以获得更好性能) MySQL版本:5.7或更高(推荐8.0+) 必要的PHP扩展:JSON, cURL, MBString, XML 创建专用开发插件是推荐的做法,这可以确保你的代码与主题和其他插件隔离,便于维护和更新: /* Plugin Name: 智能内容推荐引擎 Plugin URI: https://yourwebsite.com/ Description: 为WordPress网站添加智能内容推荐功能 Version: 1.0.0 Author: 你的名字 License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SRE_VERSION', '1.0.0'); define('SRE_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SRE_PLUGIN_URL', plugin_dir_url(__FILE__)); 1.3 数据库表设计 我们需要创建专门的数据库表来存储用户行为数据和推荐模型: register_activation_hook(__FILE__, 'sre_create_tables'); function sre_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_prefix = $wpdb->prefix . 'sre_'; // 用户行为记录表 $user_actions_table = $table_prefix . 'user_actions'; $sql1 = "CREATE TABLE IF NOT EXISTS $user_actions_table ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) DEFAULT NULL, session_id varchar(100) NOT NULL, post_id bigint(20) NOT NULL, action_type varchar(50) NOT NULL COMMENT 'view, click, like, share, etc.', action_value decimal(5,2) DEFAULT 1.0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY user_id (user_id), KEY post_id (post_id), KEY action_type (action_type), KEY created_at (created_at) ) $charset_collate;"; // 推荐缓存表 $recommendations_table = $table_prefix . 'recommendations'; $sql2 = "CREATE TABLE IF NOT EXISTS $recommendations_table ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) DEFAULT NULL, session_id varchar(100) DEFAULT NULL, post_id bigint(20) NOT NULL, recommendation_type varchar(50) NOT NULL, score decimal(5,4) NOT NULL, algorithm_params text, created_at datetime DEFAULT CURRENT_TIMESTAMP, expires_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY unique_recommendation (user_id, session_id, post_id, recommendation_type(20)), KEY score (score), KEY expires_at (expires_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); } 第二部分:用户行为追踪系统 2.1 设计用户行为数据模型 用户行为是推荐系统的燃料。我们需要设计一个灵活的系统来捕获各种用户交互: class SRE_User_Tracker { private static $instance = null; private $session_id; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->session_id = $this->generate_session_id(); $this->init_hooks(); } private function generate_session_id() { if (isset($_COOKIE['sre_session_id']) && $_COOKIE['sre_session_id']) { return sanitize_text_field($_COOKIE['sre_session_id']); } $session_id = wp_generate_uuid4(); setcookie('sre_session_id', $session_id, time() + 3600 * 24 * 30, '/'); return $session_id; } private function init_hooks() { // 追踪页面浏览 add_action('wp', array($this, 'track_page_view')); // 追踪点击事件(通过AJAX) add_action('wp_ajax_sre_track_click', array($this, 'track_click_ajax')); add_action('wp_ajax_nopriv_sre_track_click', array($this, 'track_click_ajax')); // 追踪滚动深度 add_action('wp_footer', array($this, 'add_scroll_tracking_script')); } public function track_page_view() { if (is_single() || is_page()) { global $post; $user_id = is_user_logged_in() ? get_current_user_id() : null; $this->record_action(array( 'user_id' => $user_id, 'post_id' => $post->ID, 'action_type' => 'view', 'action_value' => 1.0, 'metadata' => array( 'referrer' => wp_get_referer(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'scroll_depth' => 0 // 初始值,将通过JS更新 ) )); } } public function record_action($data) { global $wpdb; $table_name = $wpdb->prefix . 'sre_user_actions'; $defaults = array( 'user_id' => null, 'session_id' => $this->session_id, 'post_id' => 0, 'action_type' => 'unknown', 'action_value' => 1.0, 'metadata' => array(), 'created_at' => current_time('mysql') ); $data = wp_parse_args($data, $defaults); // 序列化元数据 $data['metadata'] = maybe_serialize($data['metadata']); $wpdb->insert( $table_name, $data, array('%d', '%s', '%d', '%s', '%f', '%s', '%s') ); // 触发行为记录钩子,供其他功能使用 do_action('sre_user_action_recorded', $data); return $wpdb->insert_id; } // 更多追踪方法... } 2.2 实现实时行为追踪前端脚本 用户行为追踪需要前后端配合,以下是一个完整的前端追踪脚本: // sre-frontend-tracker.js (function() { 'use strict'; class SREFrontendTracker { constructor() { this.config = window.sreTrackerConfig || {}; this.sessionId = this.getSessionId(); this.userId = this.config.userId || 0; this.currentPostId = this.config.postId || 0; this.trackingQueue = []; this.isTracking = false; this.init(); } getSessionId() { let sessionId = this.getCookie('sre_session_id'); if (!sessionId) { sessionId = this.generateUUID(); this.setCookie('sre_session_id', sessionId, 30); } return sessionId; } init() { // 追踪链接点击 this.trackClicks(); // 追踪滚动深度 this.trackScrollDepth(); // 追踪阅读时间 this.trackReadingTime(); // 定期发送追踪数据 setInterval(() => this.flushQueue(), 5000); // 页面卸载前发送剩余数据 window.addEventListener('beforeunload', () => this.flushQueueSync()); } trackClicks() { document.addEventListener('click', (e) => { let target = e.target; // 向上查找最近的链接 while (target && target !== document) { if (target.tagName === 'A') { this.handleLinkClick(target, e); break; } target = target.parentNode; } }, true); } handleLinkClick(link, event) { const href = link.getAttribute('href'); // 只追踪内部链接 if (!href || href.startsWith('#') || href.startsWith('javascript:')) { return; } const isInternal = this.isInternalLink(href); const linkText = link.textContent.trim().substring(0, 100); const linkClasses = link.className; const trackingData = { action_type: 'click', target_url: href, link_text: linkText, link_classes: linkClasses, is_internal: isInternal, position_x: event.clientX, position_y: event.clientY }; // 如果是内部链接,记录推荐点击 if (isInternal && link.closest('.sre-recommendation')) { trackingData.action_type = 'recommendation_click'; const recType = link.closest('.sre-recommendation').dataset.recommendationType; const recScore = link.closest('.sre-recommendation').dataset.recommendationScore; trackingData.recommendation_type = recType; trackingData.recommendation_score = recScore; } this.queueTracking('click', trackingData); } trackScrollDepth() { let maxScrollDepth = 0; let scrollCheckpoints = [25, 50, 75, 90, 100]; let reportedCheckpoints = new Set(); window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const currentDepth = scrollHeight > 0 ? Math.round((scrollTop / scrollHeight) * 100) : 0; if (currentDepth > maxScrollDepth) { maxScrollDepth = currentDepth; // 报告达到的检查点 scrollCheckpoints.forEach(checkpoint => { if (currentDepth >= checkpoint && !reportedCheckpoints.has(checkpoint)) { this.queueTracking('scroll', { scroll_depth: checkpoint, max_depth: maxScrollDepth }); reportedCheckpoints.add(checkpoint); } }); } }, { passive: true }); } trackReadingTime() { let startTime = Date.now(); let activeTime = 0; let lastActive = startTime; let isActive = true; // 检测用户活动 const activityEvents = ['mousemove', 'keydown', 'click', 'scroll']; const resetActive = () => { if (!isActive) { isActive = true; lastActive = Date.now(); } }; activityEvents.forEach(event => { document.addEventListener(event, resetActive, { passive: true }); }); // 每10秒检查一次活动状态 setInterval(() => { const now = Date.now(); if (isActive && now - lastActive < 10000) { activeTime += now - lastActive; } lastActive = now; isActive = false; // 每分钟报告一次阅读时间 if (activeTime >= 60000) { const minutes = Math.floor(activeTime / 60000); this.queueTracking('reading_time', { minutes: minutes, total_active_ms: activeTime }); activeTime = activeTime % 60000; } }, 10000); } queueTracking(actionType, data) { this.trackingQueue.push({ timestamp: Date.now(), action_type: actionType, data: data }); // 如果队列太长,立即发送 if (this.trackingQueue.length > 20) { this.flushQueue(); } } async flushQueue() { if (this.isTracking || this.trackingQueue.length === 0) { return; } this.isTracking = true; const queueToSend = [...this.trackingQueue]; this.trackingQueue = []; try { const response = await fetch(this.config.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'sre_track_batch', nonce: this.config.nonce, session_id: this.sessionId, user_id: this.userId, post_id: this.currentPostId, events: JSON.stringify(queueToSend) }) }); if (!response.ok) { // 如果发送失败,将数据重新放回队列(去重) const failedEvents = queueToSend.filter(event => !this.trackingQueue.some(e => e.timestamp === event.timestamp) ); this.trackingQueue = [...failedEvents, ...this.trackingQueue]; } } catch (error) { console.error('SRE Tracking Error:', error); // 网络错误,重新放回队列 this.trackingQueue = [...queueToSend, ...this.trackingQueue]; } finally { this.isTracking = false; } } // 同步刷新队列(用于页面卸载前) flushQueueSync() { if (this.trackingQueue.length === 0) return; const data = new URLSearchParams({ action: 'sre_track_batch', nonce: this.config.nonce, session_id: this.sessionId, user_id: this.userId, post_id: this.currentPostId, events: JSON.stringify(this.trackingQueue), sync: '1' }); // 使用navigator.sendBeacon异步发送,不阻塞页面卸载 navigator.sendBeacon(this.config.ajaxUrl, data); } // 工具方法... isInternalLink(href) { try { const url = new URL(href, window.location.origin); return url.hostname === window.location.hostname; } catch { return false; } } generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? decodeURIComponent(match[2]) : null; } setCookie(name, value, days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/`; } } // 初始化追踪器 document.addEventListener('DOMContentLoaded', () => { window.sreTracker = new SREFrontendTracker(); }); })(); 第三部分:内容特征提取与向量化 3.1 构建内容特征模型 为了进行智能推荐,我们需要将文章内容转化为机器可理解的数值特征: class SRE_Content_Analyzer { private $stop_words; public function __construct() { $this->load_stop_words(); } private function load_stop_words() { // 中文停用词列表(简化版) $chinese_stop_words = array( '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', 第三部分:内容特征提取与向量化(续) 3.2 实现TF-IDF特征提取 TF-IDF(词频-逆文档频率)是内容推荐中常用的特征提取方法,它能识别出对文档最具代表性的词语: class SRE_TFIDF_Processor { private $word_doc_freq = array(); // 词语的文档频率 private $total_docs = 0; // 总文档数 private $vocabulary = array(); // 词汇表 private $min_word_length = 2; // 最小词长 private $max_features = 1000; // 最大特征数 public function __construct() { $this->load_existing_model(); } /** * 从数据库加载已有的TF-IDF模型 */ private function load_existing_model() { global $wpdb; $table_name = $wpdb->prefix . 'sre_tfidf_model'; // 检查表是否存在 $table_exists = $wpdb->get_var( $wpdb->prepare("SHOW TABLES LIKE %s", $table_name) ); if ($table_exists) { $model_data = $wpdb->get_results( "SELECT word, doc_frequency FROM $table_name ORDER BY doc_frequency DESC LIMIT $this->max_features" ); foreach ($model_data as $row) { $this->word_doc_freq[$row->word] = (int)$row->doc_frequency; $this->vocabulary[] = $row->word; } $this->total_docs = (int)$wpdb->get_var( "SELECT option_value FROM {$wpdb->options} WHERE option_name = 'sre_total_docs'" ) ?: 0; } } /** * 训练TF-IDF模型 */ public function train_model($force_retrain = false) { if (!$force_retrain && !empty($this->vocabulary) && $this->total_docs > 0) { return true; // 模型已存在 } global $wpdb; // 获取所有公开的文章 $posts = $wpdb->get_results( "SELECT ID, post_title, post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post' ORDER BY ID DESC LIMIT 5000" // 限制数量以提高性能 ); $this->total_docs = count($posts); $doc_word_freq = array(); // 文档-词语频率矩阵 // 第一遍:统计词语的文档频率 foreach ($posts as $post) { $words = $this->extract_words($post); $unique_words = array_unique($words); foreach ($unique_words as $word) { if (!isset($this->word_doc_freq[$word])) { $this->word_doc_freq[$word] = 0; } $this->word_doc_freq[$word]++; } // 保存文档的词语频率 $doc_word_freq[$post->ID] = array_count_values($words); } // 按文档频率排序,选择最重要的特征词 arsort($this->word_doc_freq); $this->vocabulary = array_slice(array_keys($this->word_doc_freq), 0, $this->max_features); // 保存模型到数据库 $this->save_model(); // 计算并保存每篇文章的TF-IDF向量 $this->calculate_document_vectors($posts, $doc_word_freq); return true; } /** * 从文章中提取词语 */ private function extract_words($post) { $text = $post->post_title . ' ' . $post->post_content; // 移除HTML标签 $text = wp_strip_all_tags($text); // 中文分词(简化版,实际应用中应使用专业分词库) if (function_exists('mb_split')) { // 使用正则表达式匹配中文字符 preg_match_all('/[x{4e00}-x{9fa5}]+/u', $text, $matches); $chinese_words = $matches[0] ?? array(); // 提取英文单词 preg_match_all('/b[a-zA-Z]{2,}b/', $text, $english_matches); $english_words = $english_matches[0] ?? array(); $words = array_merge($chinese_words, $english_words); } else { // 简单的空格分割(适用于英文) $words = preg_split('/s+/', $text); } // 过滤停用词和短词 $words = array_filter($words, function($word) { $length = mb_strlen($word, 'UTF-8'); return $length >= $this->min_word_length && !$this->is_stop_word($word); }); return array_values($words); } /** * 判断是否为停用词 */ private function is_stop_word($word) { $stop_words = array( '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', '那', 'the', 'and', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of' ); return in_array(mb_strtolower($word, 'UTF-8'), $stop_words); } /** * 计算TF-IDF值 */ private function calculate_tfidf($term_freq, $doc_freq) { if ($doc_freq == 0) return 0; $tf = 1 + log($term_freq); // 词频的对数变换 $idf = log(($this->total_docs + 1) / ($doc_freq + 1)) + 1; // 平滑处理 return $tf * $idf; } /** * 计算文档向量 */ private function calculate_document_vectors($posts, $doc_word_freq) { global $wpdb; $vectors_table = $wpdb->prefix . 'sre_document_vectors'; // 批量插入数据 $batch_size = 100; $batch_data = array(); foreach ($posts as $post) { $vector = array(); $word_freq = $doc_word_freq[$post->ID] ?? array(); foreach ($this->vocabulary as $word) { $term_freq = $word_freq[$word] ?? 0; $doc_freq = $this->word_doc_freq[$word] ?? 0; if ($term_freq > 0 && $doc_freq > 0) { $tfidf = $this->calculate_tfidf($term_freq, $doc_freq); $vector[$word] = round($tfidf, 6); } } // 归一化向量 $vector = $this->normalize_vector($vector); $batch_data[] = array( 'post_id' => $post->ID, 'vector_data' => maybe_serialize($vector), 'updated_at' => current_time('mysql') ); if (count($batch_data) >= $batch_size) { $this->batch_insert_vectors($batch_data); $batch_data = array(); } } // 插入剩余数据 if (!empty($batch_data)) { $this->batch_insert_vectors($batch_data); } } /** * 批量插入向量数据 */ private function batch_insert_vectors($batch_data) { global $wpdb; $table_name = $wpdb->prefix . 'sre_document_vectors'; $values = array(); $placeholders = array(); $data = array(); foreach ($batch_data as $row) { $placeholders[] = "(%d, %s, %s)"; $data[] = $row['post_id']; $data[] = $row['vector_data']; $data[] = $row['updated_at']; } $query = "INSERT INTO $table_name (post_id, vector_data, updated_at) VALUES " . implode(', ', $placeholders) . " ON DUPLICATE KEY UPDATE vector_data = VALUES(vector_data), updated_at = VALUES(updated_at)"; $wpdb->query($wpdb->prepare($query, $data)); } /** * 归一化向量(余弦归一化) */ private function normalize_vector($vector) { $norm = 0; foreach ($vector as $value) { $norm += $value * $value; } if ($norm > 0) { $norm = sqrt($norm); foreach ($vector as $key => $value) { $vector[$key] = $value / $norm; } } return $vector; } /** * 获取文章的TF-IDF向量 */ public function get_document_vector($post_id) { global $wpdb; $table_name = $wpdb->prefix . 'sre_document_vectors'; $vector_data = $wpdb->get_var( $wpdb->prepare( "SELECT vector_data FROM $table_name WHERE post_id = %d", $post_id ) ); if ($vector_data) { return maybe_unserialize($vector_data); } // 如果向量不存在,实时计算 $post = get_post($post_id); if (!$post) { return array(); } $words = $this->extract_words($post); $word_freq = array_count_values($words); $vector = array(); foreach ($this->vocabulary as $word) { $term_freq = $word_freq[$word] ?? 0; $doc_freq = $this->word_doc_freq[$word] ?? 0; if ($term_freq > 0 && $doc_freq > 0) { $tfidf = $this->calculate_tfidf($term_freq, $doc_freq); $vector[$word] = round($tfidf, 6); } } $vector = $this->normalize_vector($vector); // 保存到数据库 $wpdb->replace( $table_name, array( 'post_id' => $post_id, 'vector_data' => maybe_serialize($vector), 'updated_at' => current_time('mysql') ), array('%d', '%s', '%s') ); return $vector; } /** * 计算两个向量的余弦相似度 */ public function cosine_similarity($vector1, $vector2) { if (empty($vector1) || empty($vector2)) { return 0; } $dot_product = 0; $norm1 = 0; $norm2 = 0; // 合并所有键 $all_keys = array_unique(array_merge(array_keys($vector1), array_keys($vector2))); foreach ($all_keys as $key) { $v1 = $vector1[$key] ?? 0; $v2 = $vector2[$key] ?? 0; $dot_product += $v1 * $v2; $norm1 += $v1 * $v1; $norm2 += $v2 * $v2; } if ($norm1 == 0 || $norm2 == 0) { return 0; } return $dot_product / (sqrt($norm1) * sqrt($norm2)); } /** * 保存模型到数据库 */ private function save_model() { global $wpdb; $table_name = $wpdb->prefix . 'sre_tfidf_model'; // 清空旧数据 $wpdb->query("TRUNCATE TABLE $table_name"); // 批量插入新数据 $batch_size = 500; $batch_data = array(); foreach ($this->word_doc_freq as $word => $freq) { $batch_data[] = array( 'word' => $word, 'doc_frequency' => $freq ); if (count($batch_data) >= $batch_size) { $this->batch_insert_model($batch_data); $batch_data = array(); } } if (!empty($batch_data)) { $this->batch_insert_model($batch_data); } // 保存总文档数 update_option('sre_total_docs', $this->total_docs, false); } private function batch_insert_model($batch_data) { global $wpdb; $table_name = $wpdb->prefix . 'sre_tfidf_model'; $values = array(); $placeholders = array(); $data = array(); foreach ($batch_data as $row) { $placeholders[] = "(%s, %d)"; $data[] = $row['word']; $data[] = $row['doc_frequency']; } $query = "INSERT INTO $table_name (word, doc_frequency) VALUES " . implode(', ', $placeholders); $wpdb->query($wpdb->prepare($query, $data)); } } 3.3 构建内容元数据特征 除了文本内容,文章的元数据也是重要的推荐特征: class SRE_Metadata_Extractor { /** * 提取文章的元数据特征 */ public function extract_metadata_features($post_id) { $post = get_post($post_id); if (!$post) { return array(); } $features = array(); // 1. 分类特征 $categories = wp_get_post_categories($post_id, array('fields' => 'names')); foreach ($categories as $category) { $features['cat_' . sanitize_title($category)] = 1; } // 2. 标签特征 $tags = wp_get_post_tags($post_id, array('fields' => 'names')); foreach ($tags as $tag) { $features['tag_' . sanitize_title($tag)] = 1; } // 3. 作者特征 $author = get_the_author_meta('display_name', $post->post_author); $features['author_' . sanitize_title($author)] = 1; // 4. 发布时间特征 $post_date = strtotime($post->post_date); $features['year_' . date('Y', $post_date)] = 1; $features['month_' . date('m', $post_date)] = 1; $features['weekday_' . date('w', $post_date)] = 1; // 5. 文章长度特征 $content_length = mb_strlen(wp_strip_all_tags($post->post_content), 'UTF-8'); $features['length_short'] = $content_length < 1000 ? 1 : 0; $features['length_medium'] = ($content_length >= 1000 && $content_length < 3000) ? 1 : 0; $features['length_long'] = $content_length >= 3000 ? 1 : 0; // 6. 是否有特色图片 $features['has_thumbnail'] = has_post_thumbnail($post_id) ? 1 : 0; // 7. 评论数量特征 $comment_count = get_comments_number($post_id); $features['comments_none'] = $comment_count == 0 ? 1 : 0; $features['comments_few'] = ($comment_count > 0 && $comment_count <= 10) ? 1 : 0; $features['comments_many'] = $comment_count > 10 ? 1 : 0; // 8. 阅读时间估计(基于字数) $word_count = str_word_count(wp_strip_all_tags($post->post_content)); $reading_time = ceil($word_count / 200); // 假设每分钟阅读200字 $features['reading_time_quick'] = $reading_time <= 3 ? 1 : 0; $features['reading_time_medium'] = ($reading_time > 3 && $reading_time <= 10) ? 1 : 0; $features['reading_time_long'] = $reading_time > 10 ? 1 : 0; return $features; } /** * 计算元数据特征的相似度 */ public function metadata_similarity($features1, $features2) { if (empty($features1) || empty($features2)) { return 0; } $intersection = array_intersect_key($features1, $features2); $union = array_merge($features1, $features2); if (empty($union)) { return 0; } // Jaccard相似系数 return count($intersection) / count(array_unique(array_keys($union))); } /** * 获取所有文章的元数据特征(用于批量处理) */ public function get_all_posts_metadata($limit = 1000) { global $wpdb; $posts = $wpdb->get_results( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts}

发表评论

详细教程,集成网站访问统计与流量分析工具

详细教程:集成网站访问统计与流量分析工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么需要自定义统计与工具集成? 在当今数字化时代,网站不仅仅是信息展示的平台,更是与用户互动、收集数据、优化体验的重要工具。对于WordPress网站管理员而言,了解访客行为、分析流量来源、优化内容策略是持续成功的关键。虽然市面上有Google Analytics、百度统计等成熟解决方案,但通过WordPress代码二次开发实现自定义统计和小工具集成,能够带来以下独特优势: 数据自主性:完全掌控数据收集、存储和处理流程 深度定制:根据特定业务需求设计统计维度 性能优化:减少对外部服务的依赖,提升页面加载速度 隐私合规:更好地适应GDPR等数据保护法规 成本控制:长期来看可能降低第三方服务费用 本教程将详细指导您如何通过WordPress代码二次开发,实现网站访问统计与流量分析功能,并集成常用互联网小工具,打造一个功能全面、自主可控的网站生态系统。 第一部分:环境准备与基础架构设计 1.1 开发环境搭建 在开始二次开发前,需要确保您的环境满足以下条件: WordPress安装:建议使用最新版本的WordPress(5.8+) 本地开发环境:推荐使用Local by Flywheel、XAMPP或MAMP 代码编辑器:VS Code、PHPStorm或Sublime Text 数据库管理工具:phpMyAdmin或Adminer 版本控制系统:Git(可选但推荐) 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('CUSTOM_STATS_VERSION', '1.0.0'); define('CUSTOM_STATS_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CUSTOM_STATS_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once CUSTOM_STATS_PLUGIN_DIR . 'includes/class-core.php'; require_once CUSTOM_STATS_PLUGIN_DIR . 'includes/class-database.php'; require_once CUSTOM_STATS_PLUGIN_DIR . 'includes/class-tracker.php'; // 启动插件 function custom_stats_init() { $core = new Custom_Stats_Core(); $core->run(); } add_action('plugins_loaded', 'custom_stats_init'); 1.3 数据库表设计 创建专门的数据表来存储访问统计信息: // includes/class-database.php class Custom_Stats_Database { public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'custom_visits'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, session_id varchar(64) NOT NULL, user_id bigint(20) DEFAULT NULL, ip_address varchar(45) DEFAULT NULL, user_agent text, referrer text, landing_page varchar(512) NOT NULL, exit_page varchar(512) DEFAULT NULL, page_views int(11) DEFAULT 1, visit_duration int(11) DEFAULT 0, country_code varchar(2) DEFAULT NULL, city varchar(100) DEFAULT NULL, device_type varchar(20) DEFAULT NULL, browser varchar(50) DEFAULT NULL, os varchar(50) DEFAULT NULL, screen_resolution varchar(20) DEFAULT NULL, is_new_visit tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY session_id (session_id), KEY user_id (user_id), KEY created_at (created_at), KEY country_code (country_code), KEY device_type (device_type) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建页面访问详情表 $page_views_table = $wpdb->prefix . 'custom_page_views'; $sql2 = "CREATE TABLE IF NOT EXISTS $page_views_table ( id bigint(20) NOT NULL AUTO_INCREMENT, visit_id bigint(20) NOT NULL, page_url varchar(512) NOT NULL, page_title varchar(255) DEFAULT NULL, time_on_page int(11) DEFAULT 0, scroll_depth int(3) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY visit_id (visit_id), KEY page_url (page_url(191)) ) $charset_collate;"; dbDelta($sql2); } public function update_tables() { // 未来版本更新时修改表结构 } } 第二部分:核心访问统计功能实现 2.1 访问数据追踪器 创建主追踪器类,负责收集和存储访问数据: // includes/class-tracker.php class Custom_Stats_Tracker { private $db; private $session_id; private $current_visit_id; public function __construct() { global $wpdb; $this->db = $wpdb; $this->init_session(); } private function init_session() { if (!session_id()) { session_start(); } if (!isset($_SESSION['custom_stats_session_id'])) { $_SESSION['custom_stats_session_id'] = $this->generate_session_id(); } $this->session_id = $_SESSION['custom_stats_session_id']; } private function generate_session_id() { return md5(uniqid(mt_rand(), true) . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); } public function track_visit() { // 检查是否为机器人或排除的IP if ($this->is_excluded()) { return; } $current_url = $this->get_current_url(); $referrer = $this->get_referrer(); // 检查是否为新访问 $is_new_visit = $this->is_new_visit(); if ($is_new_visit) { $this->current_visit_id = $this->record_new_visit($current_url, $referrer); } else { $this->update_existing_visit($current_url); } // 记录页面浏览详情 $this->record_page_view($current_url); // 设置JavaScript变量用于前端追踪 $this->set_frontend_data(); } private function is_excluded() { $excluded_ips = get_option('custom_stats_excluded_ips', []); $current_ip = $this->get_client_ip(); // 检查IP是否在排除列表中 if (in_array($current_ip, $excluded_ips)) { return true; } // 检查是否为搜索引擎机器人 $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $bots = ['bot', 'crawl', 'spider', 'slurp', 'search', 'archiver']; foreach ($bots as $bot) { if (stripos($user_agent, $bot) !== false) { return true; } } return false; } private function get_client_ip() { $ip_keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR']; foreach ($ip_keys as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (explode(',', $_SERVER[$key]) as $ip) { $ip = trim($ip); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } private function get_current_url() { $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http"; return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } private function get_referrer() { return $_SERVER['HTTP_REFERER'] ?? ''; } private function is_new_visit() { $table_name = $this->db->prefix . 'custom_visits'; $query = $this->db->prepare( "SELECT id FROM $table_name WHERE session_id = %s AND DATE(created_at) = CURDATE() ORDER BY created_at DESC LIMIT 1", $this->session_id ); $result = $this->db->get_var($query); return empty($result); } private function record_new_visit($url, $referrer) { $table_name = $this->db->prefix . 'custom_visits'; // 获取地理位置信息(简化版,实际应使用IP数据库) $geo_info = $this->get_geo_info(); // 解析用户代理 $device_info = $this->parse_user_agent(); $data = [ 'session_id' => $this->session_id, 'user_id' => get_current_user_id() ?: null, 'ip_address' => $this->get_client_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'referrer' => $referrer, 'landing_page' => $url, 'country_code' => $geo_info['country_code'], 'city' => $geo_info['city'], 'device_type' => $device_info['device_type'], 'browser' => $device_info['browser'], 'os' => $device_info['os'], 'screen_resolution' => $this->get_screen_resolution(), 'is_new_visit' => 1, 'created_at' => current_time('mysql') ]; $this->db->insert($table_name, $data); return $this->db->insert_id; } private function get_geo_info() { // 简化版,实际应集成MaxMind GeoIP或类似服务 $ip = $this->get_client_ip(); // 本地IP返回空信息 if ($ip === '127.0.0.1' || strpos($ip, '192.168.') === 0) { return ['country_code' => null, 'city' => null]; } // 这里可以调用第三方API或本地数据库 // 示例:使用ipapi.co(免费版有限制) return ['country_code' => null, 'city' => null]; } private function parse_user_agent() { $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 = 'Unknown'; 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'; } elseif (preg_match('/Opera|OPR/i', $user_agent)) { $browser = 'Opera'; } // 操作系统检测 $os = 'Unknown'; if (preg_match('/Windows NT 10.0/i', $user_agent)) { $os = 'Windows 10'; } elseif (preg_match('/Windows NT 6.3/i', $user_agent)) { $os = 'Windows 8.1'; } elseif (preg_match('/Windows NT 6.2/i', $user_agent)) { $os = 'Windows 8'; } elseif (preg_match('/Windows NT 6.1/i', $user_agent)) { $os = 'Windows 7'; } elseif (preg_match('/Macintosh|Mac OS X/i', $user_agent)) { $os = 'macOS'; } elseif (preg_match('/Linux/i', $user_agent)) { $os = 'Linux'; } elseif (preg_match('/Android/i', $user_agent)) { $os = 'Android'; } elseif (preg_match('/iPhone|iPad|iPod/i', $user_agent)) { $os = 'iOS'; } return [ 'device_type' => $device_type, 'browser' => $browser, 'os' => $os ]; } private function get_screen_resolution() { // 通过JavaScript获取,这里先返回空值 return ''; } private function set_frontend_data() { add_action('wp_footer', function() { ?> <script> window.customStats = { sessionId: '<?php echo esc_js($this->session_id); ?>', visitId: <?php echo esc_js($this->current_visit_id); ?>, nonce: '<?php echo wp_create_nonce('custom_stats_nonce'); ?>', ajaxUrl: '<?php echo admin_url('admin-ajax.php'); ?>' }; </script> <?php }); } } 2.2 前端JavaScript追踪 创建前端JavaScript文件,用于收集客户端数据: // assets/js/frontend-tracker.js (function() { 'use strict'; class FrontendTracker { constructor() { this.config = window.customStats || {}; this.pageStartTime = Date.now(); this.maxScrollDepth = 0; this.init(); } init() { this.trackPageView(); this.trackScrollDepth(); this.trackTimeOnPage(); this.trackClicks(); this.trackFormSubmissions(); this.setupBeforeUnload(); } trackPageView() { const data = { action: 'custom_stats_track_pageview', nonce: this.config.nonce, visit_id: this.config.visitId, page_url: window.location.href, page_title: document.title, screen_resolution: this.getScreenResolution(), viewport_size: this.getViewportSize() }; this.sendRequest(data); } trackScrollDepth() { let scrollCheckTimer; const checkScrollDepth = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; const scrollPercentage = scrollHeight > 0 ? Math.round((scrollTop / scrollHeight) * 100) : 0; if (scrollPercentage > this.maxScrollDepth) { this.maxScrollDepth = scrollPercentage; // 记录25%、50%、75%、100%的关键点 if ([25, 50, 75, 100].includes(scrollPercentage)) { const data = { action: 'custom_stats_track_scroll', nonce: this.config.nonce, visit_id: this.config.visitId, scroll_depth: scrollPercentage, page_url: window.location.href }; this.sendRequest(data); } } }; window.addEventListener('scroll', () => { clearTimeout(scrollCheckTimer); scrollCheckTimer = setTimeout(checkScrollDepth, 100); }); } trackTimeOnPage() { // 定期发送时间更新 setInterval(() => { const timeOnPage = Math.round((Date.now() - this.pageStartTime) / 1000); const data = { action: 'custom_stats_update_time', nonce: this.config.nonce, visit_id: this.config.visitId, time_on_page: timeOnPage }; this.sendRequest(data); }, 30000); // 每30秒发送一次 } trackClicks() { document.addEventListener('click', (e) => { const target = e.target; const link = target.closest('a'); if (link && link.href) { const data = { action: 'custom_stats_track_click', nonce: this.config.nonce, visit_id: this.config.visitId, link_url: link.href, link_text: link.textContent.substring(0, 100), page_url: window.location.href }; 2.3 AJAX请求处理与数据存储 继续前端JavaScript代码: // 如果是外部链接,立即发送数据 if (this.isExternalLink(link.href)) { this.sendRequest(data, true); // 同步发送 } else { this.sendRequest(data); } } }); } trackFormSubmissions() { document.addEventListener('submit', (e) => { const form = e.target; const formId = form.id || form.name || 'unknown'; const data = { action: 'custom_stats_track_form', nonce: this.config.nonce, visit_id: this.config.visitId, form_id: formId, form_action: form.action, page_url: window.location.href }; this.sendRequest(data); }); } setupBeforeUnload() { window.addEventListener('beforeunload', () => { const timeOnPage = Math.round((Date.now() - this.pageStartTime) / 1000); const data = { action: 'custom_stats_track_exit', nonce: this.config.nonce, visit_id: this.config.visitId, time_on_page: timeOnPage, scroll_depth: this.maxScrollDepth, exit_page: window.location.href }; // 使用navigator.sendBeacon进行可靠的离开页面数据发送 this.sendBeacon(data); }); } sendRequest(data, sync = false) { const formData = new FormData(); for (const key in data) { formData.append(key, data[key]); } if (sync) { // 同步请求(用于离开页面时) const xhr = new XMLHttpRequest(); xhr.open('POST', this.config.ajaxUrl, false); xhr.send(formData); } else { // 异步请求 fetch(this.config.ajaxUrl, { method: 'POST', body: formData, keepalive: true // 允许在页面卸载后继续发送 }).catch(error => { console.error('Tracking error:', error); }); } } sendBeacon(data) { const blob = new Blob([JSON.stringify(data)], {type: 'application/json'}); navigator.sendBeacon(this.config.ajaxUrl + '?action=custom_stats_track_exit', blob); } getScreenResolution() { return window.screen.width + 'x' + window.screen.height; } getViewportSize() { return window.innerWidth + 'x' + window.innerHeight; } isExternalLink(url) { const currentHost = window.location.hostname; const linkHost = (new URL(url, window.location.href)).hostname; return linkHost !== currentHost && linkHost !== ''; } } // 初始化追踪器 document.addEventListener('DOMContentLoaded', () => { if (window.customStats) { new FrontendTracker(); } }); })(); 2.4 PHP端AJAX处理 创建AJAX请求处理类: // includes/class-ajax-handler.php class Custom_Stats_Ajax_Handler { private $db; public function __construct() { global $wpdb; $this->db = $wpdb; $this->register_ajax_actions(); } private function register_ajax_actions() { // 页面浏览追踪 add_action('wp_ajax_custom_stats_track_pageview', [$this, 'handle_pageview']); add_action('wp_ajax_nopriv_custom_stats_track_pageview', [$this, 'handle_pageview']); // 滚动深度追踪 add_action('wp_ajax_custom_stats_track_scroll', [$this, 'handle_scroll']); add_action('wp_ajax_nopriv_custom_stats_track_scroll', [$this, 'handle_scroll']); // 点击追踪 add_action('wp_ajax_custom_stats_track_click', [$this, 'handle_click']); add_action('wp_ajax_nopriv_custom_stats_track_click', [$this, 'handle_click']); // 表单提交追踪 add_action('wp_ajax_custom_stats_track_form', [$this, 'handle_form']); add_action('wp_ajax_nopriv_custom_stats_track_form', [$this, 'handle_form']); // 退出页面追踪 add_action('wp_ajax_custom_stats_track_exit', [$this, 'handle_exit']); add_action('wp_ajax_nopriv_custom_stats_track_exit', [$this, 'handle_exit']); // 时间更新 add_action('wp_ajax_custom_stats_update_time', [$this, 'update_time']); add_action('wp_ajax_nopriv_custom_stats_update_time', [$this, 'update_time']); } public function handle_pageview() { $this->verify_nonce(); $visit_id = intval($_POST['visit_id']); $page_url = sanitize_text_field($_POST['page_url']); $page_title = sanitize_text_field($_POST['page_title']); $screen_resolution = sanitize_text_field($_POST['screen_resolution']); $table_name = $this->db->prefix . 'custom_page_views'; $data = [ 'visit_id' => $visit_id, 'page_url' => $page_url, 'page_title' => $page_title, 'created_at' => current_time('mysql') ]; // 更新访问记录中的屏幕分辨率 if (!empty($screen_resolution)) { $visits_table = $this->db->prefix . 'custom_visits'; $this->db->update( $visits_table, ['screen_resolution' => $screen_resolution], ['id' => $visit_id] ); } $this->db->insert($table_name, $data); wp_die('1'); } public function handle_scroll() { $this->verify_nonce(); $visit_id = intval($_POST['visit_id']); $scroll_depth = intval($_POST['scroll_depth']); $page_url = sanitize_text_field($_POST['page_url']); $table_name = $this->db->prefix . 'custom_page_views'; // 找到当前页面的最新记录并更新滚动深度 $this->db->query( $this->db->prepare( "UPDATE $table_name SET scroll_depth = %d WHERE visit_id = %d AND page_url = %s ORDER BY created_at DESC LIMIT 1", $scroll_depth, $visit_id, $page_url ) ); wp_die('1'); } public function handle_click() { $this->verify_nonce(); $visit_id = intval($_POST['visit_id']); $link_url = esc_url_raw($_POST['link_url']); $link_text = sanitize_text_field($_POST['link_text']); $page_url = sanitize_text_field($_POST['page_url']); $table_name = $this->db->prefix . 'custom_clicks'; // 创建点击记录表(如果不存在) $this->create_clicks_table(); $data = [ 'visit_id' => $visit_id, 'link_url' => $link_url, 'link_text' => $link_text, 'page_url' => $page_url, 'created_at' => current_time('mysql') ]; $this->db->insert($table_name, $data); wp_die('1'); } private function create_clicks_table() { $table_name = $this->db->prefix . 'custom_clicks'; if ($this->db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) { $charset_collate = $this->db->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, visit_id bigint(20) NOT NULL, link_url varchar(512) NOT NULL, link_text varchar(255) DEFAULT NULL, page_url varchar(512) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY visit_id (visit_id), KEY link_url (link_url(191)) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } public function handle_form() { $this->verify_nonce(); $visit_id = intval($_POST['visit_id']); $form_id = sanitize_text_field($_POST['form_id']); $form_action = esc_url_raw($_POST['form_action']); $page_url = sanitize_text_field($_POST['page_url']); $table_name = $this->db->prefix . 'custom_form_submissions'; // 创建表单提交表(如果不存在) $this->create_forms_table(); $data = [ 'visit_id' => $visit_id, 'form_id' => $form_id, 'form_action' => $form_action, 'page_url' => $page_url, 'created_at' => current_time('mysql') ]; $this->db->insert($table_name, $data); wp_die('1'); } private function create_forms_table() { $table_name = $this->db->prefix . 'custom_form_submissions'; if ($this->db->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) { $charset_collate = $this->db->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, visit_id bigint(20) NOT NULL, form_id varchar(100) NOT NULL, form_action varchar(512) NOT NULL, page_url varchar(512) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY visit_id (visit_id), KEY form_id (form_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } public function handle_exit() { // 对于sendBeacon请求,数据在php://input中 if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST)) { $input = file_get_contents('php://input'); $data = json_decode($input, true); if ($data) { $_POST = $data; } } $visit_id = intval($_POST['visit_id']); $time_on_page = intval($_POST['time_on_page']); $scroll_depth = intval($_POST['scroll_depth']); $exit_page = sanitize_text_field($_POST['exit_page']); $table_name = $this->db->prefix . 'custom_visits'; $this->db->update( $table_name, [ 'exit_page' => $exit_page, 'visit_duration' => $time_on_page, 'updated_at' => current_time('mysql') ], ['id' => $visit_id] ); // 更新最后页面的滚动深度 $page_views_table = $this->db->prefix . 'custom_page_views'; $this->db->query( $this->db->prepare( "UPDATE $page_views_table SET scroll_depth = %d WHERE visit_id = %d ORDER BY created_at DESC LIMIT 1", $scroll_depth, $visit_id ) ); wp_die('1'); } public function update_time() { $this->verify_nonce(); $visit_id = intval($_POST['visit_id']); $time_on_page = intval($_POST['time_on_page']); $table_name = $this->db->prefix . 'custom_visits'; $this->db->update( $table_name, [ 'visit_duration' => $time_on_page, 'updated_at' => current_time('mysql') ], ['id' => $visit_id] ); wp_die('1'); } private function verify_nonce() { if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'custom_stats_nonce')) { wp_die('Security check failed', 403); } } } 第三部分:数据分析与报表系统 3.1 数据统计类 创建数据统计和报表生成类: // includes/class-statistics.php class Custom_Stats_Statistics { private $db; public function __construct() { global $wpdb; $this->db = $wpdb; } public function get_daily_stats($date = null) { if (!$date) { $date = current_time('Y-m-d'); } $visits_table = $this->db->prefix . 'custom_visits'; $page_views_table = $this->db->prefix . 'custom_page_views'; $stats = [ 'date' => $date, 'visits' => 0, 'unique_visitors' => 0, 'pageviews' => 0, 'avg_duration' => 0, 'bounce_rate' => 0 ]; // 获取总访问数 $stats['visits'] = $this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM $visits_table WHERE DATE(created_at) = %s", $date ) ); // 获取独立访客数 $stats['unique_visitors'] = $this->db->get_var( $this->db->prepare( "SELECT COUNT(DISTINCT session_id) FROM $visits_table WHERE DATE(created_at) = %s", $date ) ); // 获取总页面浏览量 $stats['pageviews'] = $this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM $page_views_table pv INNER JOIN $visits_table v ON pv.visit_id = v.id WHERE DATE(v.created_at) = %s", $date ) ); // 获取平均访问时长 $avg_duration = $this->db->get_var( $this->db->prepare( "SELECT AVG(visit_duration) FROM $visits_table WHERE DATE(created_at) = %s AND visit_duration > 0", $date ) ); $stats['avg_duration'] = $avg_duration ? round($avg_duration) : 0; // 计算跳出率(只浏览一个页面且停留时间短于30秒) $bounce_count = $this->db->get_var( $this->db->prepare( "SELECT COUNT(*) FROM ( SELECT v.id, COUNT(pv.id) as page_count, v.visit_duration FROM $visits_table v LEFT JOIN $page_views_table pv ON v.id = pv.visit_id WHERE DATE(v.created_at) = %s GROUP BY v.id HAVING page_count = 1 AND visit_duration < 30 ) as bounces", $date ) ); if ($stats['visits'] > 0) { $stats['bounce_rate'] = round(($bounce_count / $stats['visits']) * 100, 2); } return $stats; } public function get_top_pages($limit = 10, $period = 'today') { $page_views_table = $this->db->prefix . 'custom_page_views'; $visits_table = $this->db->prefix . 'custom_visits'; $where_clause = $this->get_period_where_clause($period, 'v.created_at'); $query = $this->db->prepare( "SELECT pv.page_url, pv.page_title, COUNT(pv.id) as views, COUNT(DISTINCT v.session_id) as unique_visitors, AVG(pv.scroll_depth) as avg_scroll_depth, AVG(v.visit_duration) as avg_time_on_page FROM $page_views_table pv INNER JOIN $visits_table v ON pv.visit_id = v.id WHERE 1=1 $where_clause GROUP BY pv.page_url ORDER BY views DESC LIMIT %d", $limit ); return $this->db->get_results($query); } public function get_traffic_sources($period = 'today') { $visits_table = $this->db->prefix . 'custom_visits'; $where_clause = $this->get_period_where_clause($period); $query = "SELECT CASE WHEN referrer = '' THEN '直接访问' WHEN referrer LIKE '%' || %s || '%' THEN '内部链接' WHEN referrer LIKE '%google.%' THEN 'Google' WHEN referrer LIKE '%bing.%' THEN 'Bing' WHEN referrer LIKE '%baidu.%' THEN '百度' WHEN referrer LIKE '%yahoo.%' THEN 'Yahoo' WHEN referrer LIKE '%facebook.%' THEN 'Facebook' WHEN referrer LIKE '%twitter.%' THEN 'Twitter' WHEN referrer LIKE '%linkedin.%' THEN 'LinkedIn' ELSE '其他推荐' END as source, COUNT(*) as visits, COUNT(DISTINCT session_id) as unique_visitors, AVG(visit_duration) as avg_duration FROM $visits_table

发表评论

WordPress开发教程,打造内部团队协作与任务看板

WordPress开发教程:打造内部团队协作与任务看板,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么选择WordPress作为团队协作平台? 在当今数字化工作环境中,高效的团队协作工具已成为企业提升生产力的关键因素。虽然市面上已有Trello、Asana、Jira等专业协作工具,但许多企业仍面临数据孤岛、定制化不足和高昂成本等问题。此时,利用WordPress构建内部协作平台成为一个值得考虑的解决方案。 WordPress作为全球最流行的内容管理系统,占据互联网超过43%的网站份额。其优势不仅在于易用性和庞大的插件生态,更在于其开源特性带来的无限定制可能。通过代码二次开发,我们可以将WordPress从一个简单的博客系统转变为功能强大的内部协作平台,同时集成各种常用互联网小工具,打造一体化工作环境。 本教程将详细指导您如何通过WordPress二次开发,构建一个包含任务看板、团队协作和常用工具的综合性平台,既能满足特定业务需求,又能保持成本可控。 第一部分:环境准备与基础配置 1.1 WordPress开发环境搭建 在开始开发前,我们需要搭建一个适合的开发环境。推荐使用本地开发环境,如Local by Flywheel、XAMPP或MAMP,这些工具可以快速配置PHP、MySQL和Web服务器环境。 对于团队协作平台的开发,建议选择以下技术栈: WordPress 5.8或更高版本 PHP 7.4或更高版本(推荐PHP 8.0+) MySQL 5.6或更高版本 代码编辑器(VS Code、PHPStorm等) 安装WordPress后,需要进行一些基础配置优化: 设置固定链接结构为“文章名”模式,便于API调用 禁用不必要的插件和主题,保持系统简洁 配置WP_DEBUG模式,便于开发调试 1.2 创建自定义主题或插件 对于团队协作平台,我们有两种开发方式:创建自定义主题或开发独立插件。考虑到协作功能的独立性,建议创建专用插件,这样可以在任何主题上使用。 创建插件的基本结构: team-collaboration-platform/ ├── team-collaboration-platform.php ├── includes/ │ ├── class-database.php │ ├── class-task-manager.php │ └── class-user-manager.php ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ ├── languages/ └── vendor/ 插件主文件头部信息: <?php /** * Plugin Name: 团队协作与任务看板平台 * Plugin URI: https://yourcompany.com/ * Description: 基于WordPress的团队协作、任务管理和工具集成平台 * Version: 1.0.0 * Author: 您的团队 * License: GPL v2 or later */ 第二部分:数据库设计与用户权限系统 2.1 自定义数据库表设计 团队协作平台需要存储任务、项目、团队成员关系等复杂数据。虽然可以使用WordPress自带的文章类型,但对于复杂关系,创建自定义表更为合适。 创建数据库表的示例代码: class Collaboration_Database { public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 项目表 $projects_table = $wpdb->prefix . 'collab_projects'; $projects_sql = "CREATE TABLE IF NOT EXISTS $projects_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(200) NOT NULL, description text, status varchar(50) DEFAULT 'active', created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 任务表 $tasks_table = $wpdb->prefix . 'collab_tasks'; $tasks_sql = "CREATE TABLE IF NOT EXISTS $tasks_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, title varchar(500) NOT NULL, description longtext, status varchar(50) DEFAULT 'todo', priority varchar(20) DEFAULT 'medium', assignee_id bigint(20), due_date date, estimated_hours decimal(5,2), actual_hours decimal(5,2) DEFAULT 0, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY project_id (project_id), KEY assignee_id (assignee_id) ) $charset_collate;"; // 团队成员关系表 $team_members_table = $wpdb->prefix . 'collab_team_members'; $team_members_sql = "CREATE TABLE IF NOT EXISTS $team_members_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, user_id bigint(20) NOT NULL, role varchar(50) DEFAULT 'member', joined_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY project_user (project_id, user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($projects_sql); dbDelta($tasks_sql); dbDelta($team_members_sql); } } 2.2 用户角色与权限系统 WordPress自带的用户角色系统可能无法满足团队协作的复杂需求,我们需要扩展或创建自定义角色系统。 创建自定义用户角色和权限: class User_Role_Manager { public function add_collaboration_roles() { // 添加项目经理角色 add_role('project_manager', '项目经理', array( 'read' => true, 'edit_posts' => true, 'delete_posts' => true, 'publish_posts' => true, 'upload_files' => true, 'manage_collab_projects' => true, 'assign_collab_tasks' => true, 'edit_collab_tasks' => true, 'delete_collab_tasks' => false, )); // 添加团队成员角色 add_role('team_member', '团队成员', array( 'read' => true, 'edit_posts' => true, 'upload_files' => true, 'edit_assigned_tasks' => true, 'update_task_status' => true, )); } public function add_capabilities_to_roles() { // 为管理员添加所有协作权限 $admin_role = get_role('administrator'); $admin_role->add_cap('manage_collab_projects'); $admin_role->add_cap('assign_collab_tasks'); $admin_role->add_cap('edit_collab_tasks'); $admin_role->add_cap('delete_collab_tasks'); $admin_role->add_cap('view_all_tasks'); } } 第三部分:任务看板系统开发 3.1 看板式任务管理界面 看板(Kanban)是团队协作中常用的任务可视化工具。我们将使用HTML5、CSS3和JavaScript(配合Vue.js或React)创建交互式看板界面。 创建看板主模板: class Task_Kanban { public function display_kanban_board($project_id = null) { ob_start(); ?> <div id="task-kanban-app" data-project-id="<?php echo esc_attr($project_id); ?>"> <div class="kanban-header"> <h2>任务看板</h2> <div class="kanban-actions"> <button class="add-task-btn" @click="showAddTaskModal">+ 添加任务</button> <button class="filter-btn" @click="toggleFilters">筛选</button> </div> </div> <div class="kanban-filters" v-if="showFilters"> <!-- 筛选条件组件 --> </div> <div class="kanban-board"> <div class="kanban-column" v-for="column in columns" :key="column.id"> <div class="column-header"> <h3>{{ column.name }} ({{ column.tasks.length }})</h3> </div> <div class="column-body" @dragover.prevent @drop="dropTask($event, column.id)"> <div class="task-card" v-for="task in column.tasks" :key="task.id" draggable="true" @dragstart="dragTask($event, task.id)"> <div class="task-priority" :class="'priority-' + task.priority"></div> <div class="task-content"> <h4>{{ task.title }}</h4> <p class="task-description">{{ task.description | truncate(100) }}</p> <div class="task-meta"> <span class="assignee">{{ task.assignee_name }}</span> <span class="due-date" :class="{ 'overdue': task.isOverdue }"> {{ task.due_date | formatDate }} </span> </div> </div> </div> </div> </div> </div> <!-- 任务详情模态框 --> <div class="modal" v-if="showTaskModal"> <!-- 模态框内容 --> </div> </div> <?php return ob_get_clean(); } } 3.2 拖放功能与实时更新 实现拖放功能和实时更新是看板系统的核心。我们将使用HTML5 Drag & Drop API和WebSocket或AJAX轮询实现实时同步。 JavaScript拖放实现: // 在Vue组件中实现拖放逻辑 const TaskKanban = { data() { return { columns: [ { id: 'todo', name: '待处理', tasks: [] }, { id: 'in_progress', name: '进行中', tasks: [] }, { id: 'review', name: '审核中', tasks: [] }, { id: 'done', name: '已完成', tasks: [] } ], draggedTaskId: null, showFilters: false, showTaskModal: false }; }, methods: { dragTask(event, taskId) { event.dataTransfer.setData('text/plain', taskId); this.draggedTaskId = taskId; }, async dropTask(event, columnId) { event.preventDefault(); const taskId = event.dataTransfer.getData('text/plain'); // 发送AJAX请求更新任务状态 try { const response = await fetch('/wp-json/collab/v1/tasks/' + taskId, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': collabData.nonce }, body: JSON.stringify({ status: columnId }) }); if (response.ok) { this.moveTaskToColumn(taskId, columnId); this.showNotification('任务状态已更新', 'success'); } } catch (error) { console.error('更新任务失败:', error); this.showNotification('更新失败,请重试', 'error'); } }, moveTaskToColumn(taskId, targetColumnId) { // 在前端移动任务卡片 let task = null; let sourceColumnIndex = -1; // 查找任务所在列 this.columns.forEach((column, colIndex) => { const taskIndex = column.tasks.findIndex(t => t.id == taskId); if (taskIndex !== -1) { task = column.tasks[taskIndex]; sourceColumnIndex = colIndex; column.tasks.splice(taskIndex, 1); } }); // 将任务添加到目标列 if (task && sourceColumnIndex !== -1) { const targetColumn = this.columns.find(col => col.id === targetColumnId); if (targetColumn) { task.status = targetColumnId; targetColumn.tasks.push(task); } } }, // 实时更新任务状态 setupRealTimeUpdates() { // 使用WebSocket或AJAX轮询 if ('WebSocket' in window) { this.setupWebSocket(); } else { this.setupPolling(); } }, setupWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/collab`; this.ws = new WebSocket(wsUrl); this.ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'task_update') { this.updateTask(data.task); } else if (data.type === 'task_created') { this.addTask(data.task); } }; } }, mounted() { this.loadTasks(); this.setupRealTimeUpdates(); } }; 第四部分:团队协作功能实现 4.1 实时聊天与讨论系统 团队协作离不开实时沟通。我们将集成一个简单的实时聊天系统,支持一对一和群组聊天。 聊天系统数据库设计: // 聊天消息表 $chat_messages_sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}collab_chat_messages ( id bigint(20) NOT NULL AUTO_INCREMENT, room_id varchar(100) NOT NULL, sender_id bigint(20) NOT NULL, message_type varchar(20) DEFAULT 'text', content text NOT NULL, attachment_id bigint(20), parent_id bigint(20), is_read tinyint(1) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY room_id (room_id), KEY sender_id (sender_id), KEY created_at (created_at) ) $charset_collate;"; // 聊天室表 $chat_rooms_sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}collab_chat_rooms ( id varchar(100) NOT NULL, name varchar(200), type varchar(20) DEFAULT 'direct', -- direct, group, project project_id mediumint(9), created_by bigint(20), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 聊天室成员表 $chat_room_members_sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}collab_chat_room_members ( id bigint(20) NOT NULL AUTO_INCREMENT, room_id varchar(100) NOT NULL, user_id bigint(20) NOT NULL, joined_at datetime DEFAULT CURRENT_TIMESTAMP, last_seen_at datetime, PRIMARY KEY (id), UNIQUE KEY room_user (room_id, user_id) ) $charset_collate;"; 实时聊天前端实现: class ChatSystem { constructor() { this.socket = null; this.currentRoom = null; this.initialize(); } initialize() { this.connectWebSocket(); this.bindEvents(); this.loadChatRooms(); } connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/chat`; this.socket = new WebSocket(wsUrl); this.socket.onopen = () => { console.log('WebSocket连接已建立'); this.authenticate(); }; this.socket.onmessage = (event) => { this.handleMessage(JSON.parse(event.data)); }; this.socket.onclose = () => { console.log('WebSocket连接已关闭,尝试重连...'); setTimeout(() => this.connectWebSocket(), 3000); }; } authenticate() { this.send({ type: 'auth', token: this.getAuthToken() }); } sendMessage(content, roomId) { const message = { type: 'chat_message', room_id: roomId, content: content, timestamp: new Date().toISOString() }; this.send(message); // 本地显示消息 this.displayMessage({ ...message, sender_id: currentUserId, sender_name: currentUserName, is_own: true }); } handleMessage(data) { switch (data.type) { case 'chat_message': this.displayMessage(data.message); break; case 'user_online': this.updateUserStatus(data.user_id, true); break; case 'user_offline': this.updateUserStatus(data.user_id, false); break; case 'typing': this.showTypingIndicator(data.user_id, data.room_id); break; } } displayMessage(message) { const chatContainer = document.querySelector(`.chat-room[data-room-id="${message.room_id}"] .chat-messages`); const messageElement = document.createElement('div'); messageElement.className = `chat-message ${message.is_own ? 'own-message' : 'other-message'}`; messageElement.innerHTML = ` <div class="message-sender">${message.sender_name}</div> <div class="message-content">${this.formatMessageContent(message)}</div> <div class="message-time">${this.formatTime(message.timestamp)}</div> `; chatContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; // 播放消息提示音(如果不是自己的消息) if (!message.is_own) { this.playNotificationSound(); } } } 4.2 文件共享与版本控制 文件共享是团队协作的重要功能。我们将扩展WordPress媒体库,添加团队共享文件夹和版本控制功能。 文件共享系统实现: class File_Sharing_System { 4.2 文件共享与版本控制(续) class File_Sharing_System { public function create_team_folders() { // 为每个项目创建专用文件夹 add_action('project_created', function($project_id) { $project = $this->get_project($project_id); $folder_name = sanitize_title($project->name) . '-' . $project_id; $upload_dir = wp_upload_dir(); $team_folder = $upload_dir['basedir'] . '/team-files/' . $folder_name; if (!file_exists($team_folder)) { wp_mkdir_p($team_folder); // 创建子文件夹结构 $subfolders = ['documents', 'images', 'designs', 'code', 'archive']; foreach ($subfolders as $subfolder) { wp_mkdir_p($team_folder . '/' . $subfolder); } // 记录文件夹信息到数据库 $this->save_folder_to_db($project_id, $folder_name, $team_folder); } }); } public function handle_file_upload($file, $project_id, $user_id) { // 验证文件类型和大小 $allowed_types = $this->get_allowed_file_types(); $max_size = 100 * 1024 * 1024; // 100MB if ($file['size'] > $max_size) { return new WP_Error('file_too_large', '文件大小不能超过100MB'); } $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_types)) { return new WP_Error('invalid_file_type', '不支持的文件类型'); } // 生成唯一文件名 $unique_name = wp_unique_filename($this->get_project_upload_dir($project_id), $file['name']); // 移动文件 $destination = $this->get_project_upload_dir($project_id) . '/' . $unique_name; if (move_uploaded_file($file['tmp_name'], $destination)) { // 保存文件信息到数据库 $file_id = $this->save_file_metadata(array( 'project_id' => $project_id, 'user_id' => $user_id, 'original_name' => $file['name'], 'file_name' => $unique_name, 'file_path' => $destination, 'file_size' => $file['size'], 'file_type' => $file_ext, 'version' => 1 )); // 生成缩略图(如果是图片) if (in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif'])) { $this->generate_thumbnails($destination, $file_id); } return $file_id; } return new WP_Error('upload_failed', '文件上传失败'); } public function create_file_version($file_id, $new_file, $user_id, $comment = '') { global $wpdb; // 获取原文件信息 $original = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}collab_files WHERE id = %d", $file_id )); if (!$original) { return new WP_Error('file_not_found', '文件不存在'); } // 创建新版本 $new_version = $original->version + 1; // 保存旧版本到版本历史 $wpdb->insert("{$wpdb->prefix}collab_file_versions", array( 'file_id' => $file_id, 'version' => $original->version, 'file_name' => $original->file_name, 'file_path' => $original->file_path, 'user_id' => $user_id, 'comment' => $comment, 'created_at' => current_time('mysql') )); // 更新主文件记录 $wpdb->update("{$wpdb->prefix}collab_files", array( 'file_name' => $new_file['name'], 'file_path' => $new_file['path'], 'file_size' => $new_file['size'], 'version' => $new_version, 'updated_at' => current_time('mysql') ), array('id' => $file_id)); return $new_version; } public function get_file_version_history($file_id) { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT v.*, u.display_name as user_name FROM {$wpdb->prefix}collab_file_versions v LEFT JOIN {$wpdb->users} u ON v.user_id = u.ID WHERE v.file_id = %d ORDER BY v.version DESC", $file_id )); } } 第五部分:常用互联网小工具集成 5.1 代码片段共享与协作编辑器 class Code_Collaboration_Tool { public function init() { // 注册代码片段自定义文章类型 add_action('init', array($this, 'register_code_snippet_post_type')); // 添加代码编辑器短代码 add_shortcode('code_editor', array($this, 'code_editor_shortcode')); // 注册REST API端点 add_action('rest_api_init', array($this, 'register_code_api')); } public function register_code_snippet_post_type() { $args = array( 'public' => true, 'label' => '代码片段', 'supports' => array('title', 'editor', 'author', 'revisions'), 'show_in_rest' => true, 'capability_type' => 'code_snippet', 'capabilities' => array( 'edit_posts' => 'edit_code_snippets', 'edit_others_posts' => 'edit_others_code_snippets', 'publish_posts' => 'publish_code_snippets', 'read_post' => 'read_code_snippet', 'delete_post' => 'delete_code_snippet' ) ); register_post_type('code_snippet', $args); } public function code_editor_shortcode($atts) { $atts = shortcode_atts(array( 'language' => 'javascript', 'theme' => 'monokai', 'height' => '400px', 'readonly' => false, 'snippet_id' => 0 ), $atts); ob_start(); ?> <div class="code-collaboration-editor" data-language="<?php echo esc_attr($atts['language']); ?>" data-theme="<?php echo esc_attr($atts['theme']); ?>" data-snippet-id="<?php echo esc_attr($atts['snippet_id']); ?>"> <div class="editor-toolbar"> <select class="language-selector"> <option value="javascript">JavaScript</option> <option value="php">PHP</option> <option value="python">Python</option> <option value="html">HTML</option> <option value="css">CSS</option> <option value="sql">SQL</option> </select> <button class="run-code-btn">运行代码</button> <button class="save-code-btn">保存</button> <button class="share-code-btn">分享</button> <div class="collaborators"> <span class="online-users">在线用户: <span class="count">0</span></span> </div> </div> <div class="editor-container" style="height: <?php echo esc_attr($atts['height']); ?>;"> <div class="code-editor" id="code-editor-<?php echo uniqid(); ?>"></div> </div> <div class="output-container"> <div class="output-header">输出结果</div> <div class="output-content"></div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { initializeCodeEditor('code-editor-<?php echo uniqid(); ?>', { language: '<?php echo $atts["language"]; ?>', theme: '<?php echo $atts["theme"]; ?>', readonly: <?php echo $atts["readonly"] ? 'true' : 'false'; ?>, snippetId: <?php echo $atts["snippet_id"]; ?> }); }); </script> <?php return ob_get_clean(); } public function register_code_api() { register_rest_route('collab/v1', '/code/execute', array( 'methods' => 'POST', 'callback' => array($this, 'execute_code'), 'permission_callback' => function() { return current_user_can('edit_code_snippets'); } )); register_rest_route('collab/v1', '/code/save', array( 'methods' => 'POST', 'callback' => array($this, 'save_code_snippet'), 'permission_callback' => function() { return current_user_can('edit_code_snippets'); } )); } public function execute_code($request) { $code = $request->get_param('code'); $language = $request->get_param('language'); // 安全执行代码(使用沙箱环境) $result = $this->safe_execute_code($code, $language); return rest_ensure_response(array( 'success' => true, 'output' => $result['output'], 'error' => $result['error'], 'execution_time' => $result['execution_time'] )); } } 5.2 API测试工具与Webhook管理 class API_Testing_Tool { private $endpoints = array(); public function init() { add_action('admin_menu', array($this, 'add_api_testing_page')); add_action('wp_ajax_save_api_endpoint', array($this, 'save_api_endpoint')); add_action('wp_ajax_test_api_endpoint', array($this, 'test_api_endpoint')); add_action('wp_ajax_save_webhook', array($this, 'save_webhook')); } public function add_api_testing_page() { add_menu_page( 'API测试工具', 'API测试', 'manage_options', 'api-testing-tool', array($this, 'render_api_testing_page'), 'dashicons-rest-api', 30 ); } public function render_api_testing_page() { ?> <div class="wrap api-testing-tool"> <h1>API测试与Webhook管理</h1> <div class="api-tool-container"> <!-- API测试界面 --> <div class="api-tester-section"> <h2>API请求测试</h2> <div class="request-builder"> <div class="request-method"> <select id="request-method"> <option value="GET">GET</option> <option value="POST">POST</option> <option value="PUT">PUT</option> <option value="DELETE">DELETE</option> <option value="PATCH">PATCH</option> </select> <input type="text" id="request-url" placeholder="https://api.example.com/endpoint" style="width: 60%;"> <button id="send-request" class="button button-primary">发送请求</button> </div> <div class="request-tabs"> <ul> <li><a href="#params-tab">参数</a></li> <li><a href="#headers-tab">请求头</a></li> <li><a href="#body-tab">请求体</a></li> <li><a href="#auth-tab">认证</a></li> </ul> <div id="params-tab"> <table class="params-table"> <thead> <tr> <th>参数名</th> <th>值</th> <th>操作</th> </tr> </thead> <tbody id="params-body"> <tr> <td><input type="text" class="param-key" placeholder="参数名"></td> <td><input type="text" class="param-value" placeholder="值"></td> <td><button class="remove-param button">删除</button></td> </tr> </tbody> </table> <button id="add-param" class="button">添加参数</button> </div> <div id="headers-tab"> <!-- 请求头配置界面 --> </div> <div id="body-tab"> <select id="body-type"> <option value="none">无</option> <option value="form-data">表单数据</option> <option value="x-www-form-urlencoded">x-www-form-urlencoded</option> <option value="raw">原始数据</option> <option value="json">JSON</option> </select> <textarea id="request-body" rows="10" style="width: 100%;"></textarea> </div> <div id="auth-tab"> <!-- 认证配置界面 --> </div> </div> </div> <div class="response-section"> <h3>响应结果</h3> <div class="response-info"> <div class="response-status"> 状态码: <span id="response-status">-</span> 响应时间: <span id="response-time">-</span>ms 大小: <span id="response-size">-</span> </div> <div class="response-tabs"> <ul> <li><a href="#response-body-tab">响应体</a></li> <li><a href="#response-headers-tab">响应头</a></li> <li><a href="#response-cookies-tab">Cookies</a></li> </ul> <div id="response-body-tab"> <pre id="response-body"></pre> </div> <div id="response-headers-tab"> <pre id="response-headers"></pre> </div> </div> </div> </div> </div> <!-- Webhook管理界面 --> <div class="webhook-manager-section"> <h2>Webhook管理</h2> <button id="add-webhook" class="button button-primary">添加Webhook</button> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>名称</th> <th>URL</th> <th>事件</th> <th>状态</th> <th>最后触发</th> <th>操作</th> </tr> </thead> <tbody id="webhooks-list"> <?php $this->render_webhooks_list(); ?> </tbody> </table> <div class="webhook-details" style="display: none;"> <h3>Webhook详情</h3> <form id="webhook-form"> <input type="hidden" id="webhook-id"> <table class="form-table"> <tr> <th><label for="webhook-name">名称</label></th> <td><input type="text" id="webhook-name" class="regular-text" required></td> </tr> <tr> <th><label for="webhook-url">URL</label></th> <td><input type="url" id="webhook-url" class="regular-text" required></td> </tr> <tr> <th><label for="webhook-events">触发事件</label></th> <td> <select id="webhook-events" multiple style="width: 100%; height: 100px;"> <option value="task_created">任务创建</option> <option value="task_updated">任务更新</option> <option value="task_completed">任务完成</option> <option value="file_uploaded">文件上传</option> <option value="comment_added">评论添加</option> <option value="user_joined">用户加入</option> </select> </td> </tr> <tr> <th><label for="webhook-secret">签名密钥</label></th> <td> <input type="text" id="webhook-secret" class="regular-text"> <button type="button" id="generate-secret" class="button">生成密钥</button> </td> </tr> <tr> <th><label for="webhook-active">状态</label></th> <td> <label> <input type="checkbox" id="webhook-active" checked> 启用 </label> </td> </tr> </table> <div class="submit"> <button type="submit" class="button button-primary">保存</button> <button type="button" id="cancel-webhook" class="button">取消</button> <button type="button" id="test-webhook" class="button">测试Webhook</button> </div> </form> </div> </div> </div> </div> <script> jQuery(document).ready(function($) { // API测试功能 $('#send-request').on('click', function() { const method = $('#request-method').val(); const url = $('#request-url').val(); const params = {}; $('#params-body tr').each(function() { const key = $(this).find('.param-key').val();

发表评论

实战教学,在网站中添加浮动在线联系与反馈窗口

实战教学:在WordPress网站中添加浮动在线联系与反馈窗口 引言:为什么需要浮动联系与反馈窗口? 在当今互联网时代,用户体验已成为网站成功的关键因素之一。根据研究,网站访问者平均只花费15秒决定是否继续浏览一个网站。在这短暂的时间内,能否快速找到联系方式和反馈渠道直接影响着转化率和用户满意度。 浮动在线联系与反馈窗口作为一种常见的互联网小工具,具有以下优势: 即时可访问性:无论用户滚动到页面哪个位置,联系入口始终可见 提升转化率:减少用户寻找联系方式的步骤,提高咨询可能性 增强用户体验:提供便捷的反馈渠道,收集用户意见 专业形象展示:展示企业的在线服务能力和响应速度 本文将详细指导您如何通过WordPress代码二次开发,实现一个功能完善、美观实用的浮动在线联系与反馈窗口。 第一部分:准备工作与环境配置 1.1 开发环境要求 在开始之前,请确保您的WordPress环境满足以下要求: WordPress版本:5.0或更高 PHP版本:7.2或更高(推荐7.4+) 基本的HTML、CSS、JavaScript知识 代码编辑器(如VS Code、Sublime Text等) FTP客户端或WordPress文件管理器访问权限 1.2 创建子主题(安全开发实践) 为了避免主题更新导致自定义代码丢失,我们强烈建议使用子主题进行开发: 在WordPress的wp-content/themes/目录下创建新文件夹,命名为yourtheme-child(将"yourtheme"替换为您的主题名称) 在子主题文件夹中创建style.css文件,添加以下内容: /* Theme Name: YourTheme Child Theme URI: https://example.com/ Description: Child theme for adding floating contact widget Author: Your Name Author URI: https://example.com/ Template: yourtheme Version: 1.0.0 */ 创建functions.php文件,添加以下代码启用子主题: <?php add_action('wp_enqueue_scripts', 'my_child_theme_enqueue_styles'); function my_child_theme_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); wp_enqueue_style('child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style')); } ?> 在WordPress后台"外观"→"主题"中激活您的子主题 第二部分:浮动联系窗口基础结构实现 2.1 HTML结构设计 在子主题文件夹中创建floating-contact.php文件,添加以下HTML结构: <?php /** * Floating Contact Widget HTML Structure */ function floating_contact_html() { ob_start(); ?> <!-- 浮动联系窗口主容器 --> <div id="floating-contact-widget" class="floating-contact-container"> <!-- 主按钮 --> <div class="floating-contact-main-btn"> <span class="contact-icon">💬</span> <span class="contact-text">联系我们</span> </div> <!-- 展开面板 --> <div class="floating-contact-panel"> <div class="panel-header"> <h3>在线联系</h3> <button class="close-panel">×</button> </div> <div class="panel-content"> <!-- 联系渠道选择 --> <div class="contact-methods"> <div class="method-item" data-method="chat"> <div class="method-icon">💬</div> <div class="method-info"> <h4>在线聊天</h4> <p>即时响应,快速解答</p> </div> </div> <div class="method-item" data-method="phone"> <div class="method-icon">📞</div> <div class="method-info"> <h4>电话联系</h4> <p>400-123-4567</p> </div> </div> <div class="method-item" data-method="email"> <div class="method-icon">✉️</div> <div class="method-info"> <h4>邮件反馈</h4> <p>24小时内回复</p> </div> </div> <div class="method-item" data-method="form"> <div class="method-icon">📝</div> <div class="method-info"> <h4>提交表单</h4> <p>详细问题反馈</p> </div> </div> </div> <!-- 聊天界面 --> <div class="chat-interface" style="display:none;"> <div class="chat-header"> <button class="back-to-methods">←</button> <h4>在线客服</h4> </div> <div class="chat-messages"> <!-- 聊天消息将通过JavaScript动态加载 --> </div> <div class="chat-input-area"> <textarea placeholder="请输入您的问题..." rows="3"></textarea> <button class="send-message">发送</button> </div> </div> <!-- 反馈表单 --> <div class="feedback-form" style="display:none;"> <div class="form-header"> <button class="back-to-methods">←</button> <h4>问题反馈</h4> </div> <form id="feedbackForm"> <div class="form-group"> <label for="feedbackName">姓名 *</label> <input type="text" id="feedbackName" name="name" required> </div> <div class="form-group"> <label for="feedbackEmail">邮箱 *</label> <input type="email" id="feedbackEmail" name="email" required> </div> <div class="form-group"> <label for="feedbackSubject">主题</label> <input type="text" id="feedbackSubject" name="subject"> </div> <div class="form-group"> <label for="feedbackMessage">内容 *</label> <textarea id="feedbackMessage" name="message" rows="5" required></textarea> </div> <div class="form-group"> <button type="submit" class="submit-feedback">提交反馈</button> </div> </form> </div> </div> </div> </div> <?php return ob_get_clean(); } ?> 2.2 CSS样式设计 在子主题的style.css文件中添加以下样式: /* 浮动联系窗口基础样式 */ .floating-contact-container { position: fixed; bottom: 30px; right: 30px; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; } /* 主按钮样式 */ .floating-contact-main-btn { background: linear-gradient(135deg, #4a6ee0 0%, #6a11cb 100%); color: white; padding: 15px 20px; border-radius: 50px; cursor: pointer; display: flex; align-items: center; gap: 10px; box-shadow: 0 5px 15px rgba(106, 17, 203, 0.3); transition: all 0.3s ease; user-select: none; } .floating-contact-main-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(106, 17, 203, 0.4); } .contact-icon { font-size: 20px; } .contact-text { font-weight: 600; font-size: 16px; } /* 展开面板样式 */ .floating-contact-panel { position: absolute; bottom: 70px; right: 0; width: 350px; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); overflow: hidden; display: none; animation: slideUp 0.3s ease; } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .panel-header { background: linear-gradient(135deg, #4a6ee0 0%, #6a11cb 100%); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; } .panel-header h3 { margin: 0; font-size: 18px; } .close-panel { background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 1; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background 0.2s; } .close-panel:hover { background: rgba(255, 255, 255, 0.2); } .panel-content { padding: 20px; } /* 联系方法样式 */ .contact-methods { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } .method-item { background: #f8f9fa; border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.2s ease; border: 1px solid #e9ecef; } .method-item:hover { background: #e9ecef; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .method-icon { font-size: 24px; margin-bottom: 8px; } .method-info h4 { margin: 0 0 5px 0; font-size: 14px; color: #333; } .method-info p { margin: 0; font-size: 12px; color: #666; } /* 聊天界面样式 */ .chat-interface, .feedback-form { animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .chat-header, .form-header { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .back-to-methods { background: none; border: none; font-size: 20px; cursor: pointer; color: #6a11cb; padding: 5px; border-radius: 4px; transition: background 0.2s; } .back-to-methods:hover { background: #f0f0f0; } .chat-messages { height: 300px; overflow-y: auto; margin-bottom: 15px; padding: 10px; background: #f9f9f9; border-radius: 8px; } .chat-input-area { display: flex; flex-direction: column; gap: 10px; } .chat-input-area textarea { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; resize: none; font-family: inherit; font-size: 14px; } .send-message { background: #4a6ee0; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: 600; transition: background 0.3s; align-self: flex-end; } .send-message:hover { background: #3a5ed0; } /* 反馈表单样式 */ .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 14px; } .form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; font-family: inherit; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #4a6ee0; box-shadow: 0 0 0 2px rgba(74, 110, 224, 0.2); } .submit-feedback { background: linear-gradient(135deg, #4a6ee0 0%, #6a11cb 100%); color: white; border: none; padding: 12px 30px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 16px; width: 100%; transition: transform 0.2s; } .submit-feedback:hover { transform: translateY(-2px); } /* 响应式设计 */ @media (max-width: 768px) { .floating-contact-container { bottom: 20px; right: 20px; } .floating-contact-panel { width: 300px; right: -10px; } .contact-methods { grid-template-columns: 1fr; } } @media (max-width: 480px) { .floating-contact-panel { width: calc(100vw - 40px); right: -20px; } .floating-contact-main-btn { padding: 12px 16px; } .contact-text { display: none; } } 第三部分:JavaScript交互功能实现 3.1 基础交互逻辑 在子主题文件夹中创建floating-contact.js文件: /** * 浮动联系窗口交互功能 */ document.addEventListener('DOMContentLoaded', function() { // 获取DOM元素 const floatingWidget = document.getElementById('floating-contact-widget'); const mainBtn = floatingWidget.querySelector('.floating-contact-main-btn'); const contactPanel = floatingWidget.querySelector('.floating-contact-panel'); const closeBtn = floatingWidget.querySelector('.close-panel'); const methodItems = floatingWidget.querySelectorAll('.method-item'); const backButtons = floatingWidget.querySelectorAll('.back-to-methods'); const chatInterface = floatingWidget.querySelector('.chat-interface'); const feedbackForm = floatingWidget.querySelector('.feedback-form'); const contactMethods = floatingWidget.querySelector('.contact-methods'); const sendMessageBtn = floatingWidget.querySelector('.send-message'); const chatInput = floatingWidget.querySelector('.chat-input-area textarea'); const chatMessages = floatingWidget.querySelector('.chat-messages'); const feedbackFormElement = document.getElementById('feedbackForm'); // 初始状态 let isPanelOpen = false; let currentView = 'methods'; // methods, chat, form // 切换面板显示/隐藏 function togglePanel() { isPanelOpen = !isPanelOpen; if (isPanelOpen) { contactPanel.style.display = 'block'; mainBtn.style.transform = 'rotate(45deg)'; } else { contactPanel.style.display = 'none'; mainBtn.style.transform = 'rotate(0deg)'; resetToMethodsView(); } } // 重置到联系方法视图 function resetToMethodsView() { currentView = 'methods'; contactMethods.style.display = 'grid'; chatInterface.style.display = 'none'; feedbackForm.style.display = 'none'; } // 切换到聊天界面 function showChatInterface() { currentView = 'chat'; contactMethods.style.display = 'none'; chatInterface.style.display = 'block'; feedbackForm.style.display = 'none'; chatInput.focus(); // 添加欢迎消息 if (!chatMessages.querySelector('.welcome-message')) { addChatMessage('system', '您好!我是在线客服,有什么可以帮您的吗?', 'welcome-message'); } } // 切换到反馈表单 function showFeedbackForm() { currentView = 'form'; contactMethods.style.display = 'none'; chatInterface.style.display = 'none'; feedbackForm.style.display = 'block'; } // 添加聊天消息 function addChatMessage(type, content, className = '') { const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${type}-message ${className}`; const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); messageDiv.innerHTML = ` <div class="message-content">${content}</div> <div class="message-time">${timestamp}</div> `; chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; } // 发送聊天消息 function sendChatMessage() { const message = chatInput.value.trim(); if (!message) return; // 添加用户消息 addChatMessage('user', message); chatInput.value = ''; // 模拟客服回复(实际应用中应通过AJAX发送到服务器) setTimeout(() => { const responses = [ '感谢您的提问,我会尽快为您解答。', '我明白了,请您稍等,我正在查询相关信息。', '这是一个很好的问题,我们的专家会尽快回复您。', '我已经记录下您的问题,稍后会有专人联系您。' ]; const randomResponse = responses[Math.floor(Math.random() * responses.length)]; addChatMessage('system', randomResponse); 第四部分:功能完善与数据交互 4.1 电话与邮件功能实现 继续在floating-contact.js文件中添加以下代码: // 处理电话联系 function handlePhoneCall() { const phoneNumber = '400-123-4567'; if (confirm(`是否要拨打 ${phoneNumber}?`)) { window.location.href = `tel:${phoneNumber}`; } } // 处理邮件联系 function handleEmail() { const email = 'support@example.com'; const subject = encodeURIComponent('网站咨询'); const body = encodeURIComponent('您好,我想咨询以下问题:nn'); window.location.href = `mailto:${email}?subject=${subject}&body=${body}`; } // 处理联系方法点击 methodItems.forEach(item => { item.addEventListener('click', function() { const method = this.dataset.method; switch(method) { case 'chat': showChatInterface(); break; case 'phone': handlePhoneCall(); break; case 'email': handleEmail(); break; case 'form': showFeedbackForm(); break; } }); }); // 返回按钮事件 backButtons.forEach(btn => { btn.addEventListener('click', resetToMethodsView); }); // 发送消息按钮事件 sendMessageBtn.addEventListener('click', sendChatMessage); // 回车发送消息 chatInput.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChatMessage(); } }); // 主按钮点击事件 mainBtn.addEventListener('click', togglePanel); // 关闭按钮事件 closeBtn.addEventListener('click', togglePanel); // 点击面板外部关闭 document.addEventListener('click', function(e) { if (isPanelOpen && !floatingWidget.contains(e.target) && !e.target.closest('.floating-contact-container')) { togglePanel(); } }); // 反馈表单提交处理 feedbackFormElement.addEventListener('submit', function(e) { e.preventDefault(); // 收集表单数据 const formData = { name: document.getElementById('feedbackName').value, email: document.getElementById('feedbackEmail').value, subject: document.getElementById('feedbackSubject').value, message: document.getElementById('feedbackMessage').value, timestamp: new Date().toISOString(), page: window.location.href }; // 显示加载状态 const submitBtn = this.querySelector('.submit-feedback'); const originalText = submitBtn.textContent; submitBtn.textContent = '提交中...'; submitBtn.disabled = true; // 发送数据到WordPress后台 fetch('/wp-admin/admin-ajax.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'submit_feedback', nonce: floatingContactData.nonce, ...formData }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('感谢您的反馈!我们会尽快处理。'); feedbackFormElement.reset(); togglePanel(); } else { alert('提交失败,请稍后重试。'); } }) .catch(error => { console.error('Error:', error); alert('网络错误,请检查连接后重试。'); }) .finally(() => { submitBtn.textContent = originalText; submitBtn.disabled = false; }); }); // 添加聊天消息样式 const style = document.createElement('style'); style.textContent = ` .chat-message { margin-bottom: 15px; padding: 10px 15px; border-radius: 18px; max-width: 80%; position: relative; animation: messageAppear 0.3s ease; } @keyframes messageAppear { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .user-message { background: #4a6ee0; color: white; margin-left: auto; border-bottom-right-radius: 5px; } .system-message { background: #f0f0f0; color: #333; margin-right: auto; border-bottom-left-radius: 5px; } .message-content { word-wrap: break-word; line-height: 1.4; } .message-time { font-size: 11px; opacity: 0.7; margin-top: 5px; text-align: right; } .user-message .message-time { color: rgba(255, 255, 255, 0.8); } .system-message .message-time { color: #666; } `; document.head.appendChild(style); }); 4.2 PHP后端处理 在子主题的functions.php文件中添加后端处理代码: <?php /** * 浮动联系窗口功能集成 */ // 1. 注册并加载必要的脚本和样式 function floating_contact_enqueue_scripts() { // 加载CSS样式 wp_enqueue_style('floating-contact-style', get_stylesheet_directory_uri() . '/style.css'); // 加载JavaScript wp_enqueue_script('floating-contact-script', get_stylesheet_directory_uri() . '/floating-contact.js', array('jquery'), '1.0.0', true); // 传递数据到JavaScript wp_localize_script('floating-contact-script', 'floatingContactData', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('floating_contact_nonce') )); } add_action('wp_enqueue_scripts', 'floating_contact_enqueue_scripts'); // 2. 在网站中插入浮动联系窗口 function insert_floating_contact_widget() { if (!is_admin()) { echo floating_contact_html(); } } add_action('wp_footer', 'insert_floating_contact_widget'); // 3. 处理反馈表单提交 function handle_feedback_submission() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'floating_contact_nonce')) { wp_die('安全验证失败'); } // 验证必填字段 $required_fields = array('name', 'email', 'message'); foreach ($required_fields as $field) { if (empty($_POST[$field])) { wp_send_json_error('请填写所有必填字段'); } } // 验证邮箱格式 if (!is_email($_POST['email'])) { wp_send_json_error('邮箱格式不正确'); } // 清理输入数据 $name = sanitize_text_field($_POST['name']); $email = sanitize_email($_POST['email']); $subject = sanitize_text_field($_POST['subject']); $message = sanitize_textarea_field($_POST['message']); $page_url = esc_url_raw($_POST['page']); // 创建反馈数据数组 $feedback_data = array( 'post_title' => $subject ?: '来自 ' . $name . ' 的反馈', 'post_content' => $message, 'post_status' => 'pending', // 设置为待审核状态 'post_type' => 'feedback', // 自定义文章类型 'meta_input' => array( 'feedback_email' => $email, 'feedback_page' => $page_url, 'feedback_date' => current_time('mysql') ) ); // 插入到数据库 $post_id = wp_insert_post($feedback_data); if ($post_id && !is_wp_error($post_id)) { // 发送邮件通知管理员 $admin_email = get_option('admin_email'); $mail_subject = '新的网站反馈:' . ($subject ?: '无主题'); $mail_message = " 收到新的网站反馈: 姓名:{$name} 邮箱:{$email} 来源页面:{$page_url} 提交时间:" . date('Y-m-d H:i:s') . " 反馈内容: {$message} 您可以在WordPress后台查看和处理此反馈。 "; wp_mail($admin_email, $mail_subject, $mail_message); wp_send_json_success('反馈提交成功'); } else { wp_send_json_error('提交失败,请稍后重试'); } } add_action('wp_ajax_submit_feedback', 'handle_feedback_submission'); add_action('wp_ajax_nopriv_submit_feedback', 'handle_feedback_submission'); // 4. 创建自定义文章类型存储反馈 function create_feedback_post_type() { register_post_type('feedback', array( 'labels' => array( 'name' => '用户反馈', 'singular_name' => '反馈', 'menu_name' => '用户反馈', 'all_items' => '所有反馈', 'view_item' => '查看反馈', 'add_new_item' => '添加新反馈', 'add_new' => '新反馈', 'edit_item' => '编辑反馈', 'update_item' => '更新反馈', 'search_items' => '搜索反馈', 'not_found' => '未找到', 'not_found_in_trash' => '回收站中未找到' ), 'public' => false, 'show_ui' => true, 'show_in_menu' => true, 'show_in_admin_bar' => true, 'menu_position' => 25, 'menu_icon' => 'dashicons-format-chat', 'capability_type' => 'post', 'hierarchical' => false, 'supports' => array('title', 'editor'), 'has_archive' => false, 'rewrite' => false, 'exclude_from_search' => true, 'publicly_queryable' => false, 'show_in_rest' => false ) ); } add_action('init', 'create_feedback_post_type'); // 5. 添加快捷码支持 function floating_contact_shortcode($atts) { $atts = shortcode_atts(array( 'position' => 'right', 'color' => '#4a6ee0' ), $atts); // 这里可以添加根据短码参数自定义样式的逻辑 return floating_contact_html(); } add_shortcode('floating_contact', 'floating_contact_shortcode'); ?> 第五部分:高级功能扩展 5.1 数据库优化与反馈管理 在functions.php中添加以下代码来增强反馈管理功能: <?php /** * 反馈管理功能增强 */ // 1. 添加自定义管理列 function add_feedback_columns($columns) { $new_columns = array( 'cb' => $columns['cb'], 'title' => '主题', 'feedback_email' => '邮箱', 'feedback_page' => '来源页面', 'feedback_date' => '提交时间', 'feedback_status' => '状态' ); return $new_columns; } add_filter('manage_feedback_posts_columns', 'add_feedback_columns'); // 2. 填充自定义列内容 function custom_feedback_column($column, $post_id) { switch ($column) { case 'feedback_email': echo get_post_meta($post_id, 'feedback_email', true); break; case 'feedback_page': $page_url = get_post_meta($post_id, 'feedback_page', true); echo '<a href="' . esc_url($page_url) . '" target="_blank">查看页面</a>'; break; case 'feedback_date': echo get_post_meta($post_id, 'feedback_date', true); break; case 'feedback_status': $status = get_post_status($post_id); $status_labels = array( 'publish' => '已处理', 'pending' => '待处理', 'draft' => '草稿' ); echo isset($status_labels[$status]) ? $status_labels[$status] : $status; break; } } add_action('manage_feedback_posts_custom_column', 'custom_feedback_column', 10, 2); // 3. 添加快速操作 function feedback_quick_actions($actions, $post) { if ($post->post_type == 'feedback') { // 添加"标记为已处理"操作 if ($post->post_status == 'pending') { $actions['process'] = sprintf( '<a href="%s" style="color:#46b450;">标记为已处理</a>', wp_nonce_url(admin_url("admin-ajax.php?action=process_feedback&post_id=$post->ID"), 'process_feedback') ); } // 添加"发送回复"操作 $email = get_post_meta($post->ID, 'feedback_email', true); if ($email) { $actions['reply'] = sprintf( '<a href="mailto:%s?subject=回复您的反馈">发送邮件回复</a>', esc_attr($email) ); } } return $actions; } add_filter('post_row_actions', 'feedback_quick_actions', 10, 2); // 4. 处理快速操作请求 function process_feedback_ajax() { if (!isset($_GET['post_id']) || !wp_verify_nonce($_GET['_wpnonce'], 'process_feedback')) { wp_die('安全验证失败'); } $post_id = intval($_GET['post_id']); $updated = wp_update_post(array( 'ID' => $post_id, 'post_status' => 'publish' )); if ($updated) { wp_redirect(admin_url('edit.php?post_type=feedback&processed=1')); exit; } else { wp_die('处理失败'); } } add_action('wp_ajax_process_feedback', 'process_feedback_ajax'); // 5. 添加反馈统计仪表板小工具 function add_feedback_dashboard_widget() { wp_add_dashboard_widget( 'feedback_stats_widget', '用户反馈统计', 'display_feedback_stats' ); } add_action('wp_dashboard_setup', 'add_feedback_dashboard_widget'); function display_feedback_stats() { global $wpdb; // 获取统计数据 $total = wp_count_posts('feedback')->publish + wp_count_posts('feedback')->pending; $pending = wp_count_posts('feedback')->pending; $processed = wp_count_posts('feedback')->publish; // 最近7天反馈数量 $recent_feedback = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'feedback' AND post_date >= %s", date('Y-m-d', strtotime('-7 days')) )); echo '<div class="feedback-stats">'; echo '<p><strong>总反馈数:</strong>' . $total . '</p>'; echo '<p><strong>待处理:</strong>' . $pending . '</p>'; echo '<p><strong>已处理:</strong>' . $processed . '</p>'; echo '<p><strong>最近7天:</strong>' . $recent_feedback . '</p>'; echo '</div>'; // 如果有待处理反馈,显示链接 if ($pending > 0) { echo '<p><a href="' . admin_url('edit.php?post_type=feedback&post_status=pending') . '" class="button button-primary">查看待处理反馈</a></p>'; } } ?> 5.2 实时聊天功能增强 创建新的JavaScript文件chat-enhanced.js: /** * 增强版实时聊天功能 */ class EnhancedChat { constructor() { this.socket = null; this.userId = this.generateUserId(); this.sessionId = this.generateSessionId(); this.isConnected = false; this.typingTimeout = null; this.init(); } generateUserId() { let userId = localStorage.getItem('chat_user_id'); if (!userId) { userId = 'user_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('chat_user_id', userId); } return userId; } generateSessionId() { return 'session_' + Date.now(); } init() { this.connectWebSocket(); this.setupEventListeners(); this.loadChatHistory(); } connectWebSocket() { // 这里应该使用您的WebSocket服务器地址 const wsUrl = 'wss://your-websocket-server.com/chat'; try { this.socket = new WebSocket(wsUrl); this.socket.onopen = () => { this.isConnected = true; this.sendSystemMessage('已连接到客服系统'); this.registerUser(); }; this.socket.onmessage = (event) => { const data = JSON.parse(event.data); this.handleIncomingMessage(data); }; this.socket.onclose = () => { this.isConnected = false; this.sendSystemMessage('连接已断开,正在尝试重连...'); setTimeout(() => this.connectWebSocket(), 5000); }; this.socket.onerror = (error) => { console.error('WebSocket错误:', error); }; } catch (error) { console.error('连接失败:', error); this.fallbackToAJAX(); } } fallbackToAJAX() {

发表评论

手把手教程,为WordPress集成SEO分析与优化工具

手把手教程:为WordPress集成SEO分析与优化工具,通过代码二次开发实现常用互联网小工具功能 引言:为什么WordPress需要SEO工具集成? 在当今数字时代,拥有一个网站只是第一步,让网站在搜索引擎中获得良好排名才是成功的关键。WordPress作为全球最受欢迎的内容管理系统,虽然拥有众多SEO插件,但很多时候这些通用解决方案无法完全满足特定需求。通过代码二次开发,我们可以为WordPress网站集成更精准、更高效的SEO分析与优化工具,同时实现各种实用的互联网小工具功能。 本教程将引导您从零开始,通过PHP代码开发,为WordPress网站添加自定义SEO分析功能,并集成多种实用工具。无论您是WordPress开发者、网站管理员还是SEO专家,都能从中获得实用的技术指导。 第一章:准备工作与环境搭建 1.1 开发环境要求 在开始之前,请确保您具备以下环境: WordPress 5.0或更高版本 PHP 7.4或更高版本(建议使用PHP 8.0+) MySQL 5.6或更高版本 代码编辑器(如VS Code、Sublime Text或PHPStorm) FTP客户端或服务器直接访问权限 基本的PHP、HTML、CSS和JavaScript知识 1.2 创建自定义插件框架 为了避免主题更新导致代码丢失,我们将创建一个独立的WordPress插件: <?php /** * Plugin Name: SEO分析与工具集成套件 * Plugin URI: https://yourwebsite.com/ * Description: 自定义SEO分析工具与互联网小工具集成 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SEOTOOLS_VERSION', '1.0.0'); define('SEOTOOLS_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SEOTOOLS_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 function seotools_init() { // 插件初始化代码将在这里添加 } add_action('plugins_loaded', 'seotools_init'); 1.3 安全注意事项 在开始开发前,请牢记以下安全准则: 所有用户输入必须经过验证和清理 使用WordPress非ce和权限检查功能 对数据库查询使用预处理语句 限制API密钥和敏感信息的访问 第二章:核心SEO分析功能开发 2.1 页面SEO评分系统 我们将创建一个页面SEO评分系统,自动分析文章和页面的SEO表现: // 在插件主文件中添加以下类 class SEOTools_Analyzer { private $post_id; private $score; private $issues; public function __construct($post_id) { $this->post_id = $post_id; $this->score = 0; $this->issues = array(); } // 分析文章标题 public function analyze_title() { $title = get_the_title($this->post_id); $title_length = mb_strlen($title); if ($title_length == 0) { $this->add_issue('标题为空', 'high'); return 0; } elseif ($title_length < 30) { $this->add_issue('标题过短(建议30-60字符)', 'medium'); return 5; } elseif ($title_length > 60) { $this->add_issue('标题过长(建议30-60字符)', 'medium'); return 5; } else { $this->add_issue('标题长度合适', 'positive'); return 10; } } // 分析元描述 public function analyze_meta_description() { $meta_description = get_post_meta($this->post_id, '_yoast_wpseo_metadesc', true); if (empty($meta_description)) { $this->add_issue('元描述为空', 'high'); return 0; } $desc_length = mb_strlen($meta_description); if ($desc_length < 120) { $this->add_issue('元描述过短(建议120-160字符)', 'medium'); return 5; } elseif ($desc_length > 160) { $this->add_issue('元描述过长(建议120-160字符)', 'medium'); return 5; } else { $this->add_issue('元描述长度合适', 'positive'); return 10; } } // 分析内容可读性 public function analyze_readability() { $content = get_post_field('post_content', $this->post_id); $content = strip_tags($content); // 计算段落数 $paragraphs = preg_split('/ns*n/', $content); $paragraph_count = count($paragraphs); // 计算句子数 $sentences = preg_split('/[.!?]+/', $content); $sentence_count = count($sentences); // 计算平均段落长度 if ($paragraph_count > 0 && $sentence_count > 0) { $avg_sentences_per_para = $sentence_count / $paragraph_count; if ($avg_sentences_per_para > 5) { $this->add_issue('段落过长,建议拆分', 'medium'); return 5; } elseif ($avg_sentences_per_para < 2) { $this->add_issue('段落过短,建议合并', 'low'); return 7; } else { $this->add_issue('段落长度合适', 'positive'); return 10; } } return 5; } // 分析图片ALT属性 public function analyze_images() { $content = get_post_field('post_content', $this->post_id); $images_without_alt = 0; $total_images = 0; if (preg_match_all('/<img[^>]+>/i', $content, $matches)) { foreach ($matches[0] as $img_tag) { $total_images++; if (!preg_match('/alt=["'][^"']*["']/i', $img_tag)) { $images_without_alt++; } } } if ($total_images == 0) { $this->add_issue('内容中缺少图片', 'medium'); return 5; } elseif ($images_without_alt > 0) { $percentage = ($images_without_alt / $total_images) * 100; $this->add_issue(sprintf('%d%%的图片缺少ALT属性', $percentage), 'medium'); return max(0, 10 - ($images_without_alt * 2)); } else { $this->add_issue('所有图片都有ALT属性', 'positive'); return 10; } } // 添加问题到列表 private function add_issue($message, $severity) { $this->issues[] = array( 'message' => $message, 'severity' => $severity ); } // 执行完整分析 public function run_analysis() { $scores = array( 'title' => $this->analyze_title(), 'meta_description' => $this->analyze_meta_description(), 'readability' => $this->analyze_readability(), 'images' => $this->analyze_images() ); $this->score = array_sum($scores) / count($scores); return array( 'score' => round($this->score, 1), 'issues' => $this->issues, 'details' => $scores ); } } // 在文章编辑页面添加SEO分析框 function seotools_add_meta_box() { add_meta_box( 'seotools_analysis', 'SEO分析报告', 'seotools_meta_box_callback', array('post', 'page'), 'side', 'high' ); } add_action('add_meta_boxes', 'seotools_add_meta_box'); function seotools_meta_box_callback($post) { $analyzer = new SEOTools_Analyzer($post->ID); $analysis = $analyzer->run_analysis(); echo '<div class="seotools-analysis">'; echo '<div class="seotools-score-circle" style="width: 80px; height: 80px; border-radius: 50%; background: conic-gradient(#4CAF50 ' . ($analysis['score'] * 3.6) . 'deg, #f0f0f0 0deg); display: flex; align-items: center; justify-content: center; margin: 0 auto 15px;">'; echo '<div style="background: white; width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px;">' . $analysis['score'] . '/10</div>'; echo '</div>'; echo '<h3>发现的问题:</h3>'; echo '<ul style="margin-top: 10px;">'; foreach ($analysis['issues'] as $issue) { $color = '#888'; if ($issue['severity'] == 'high') $color = '#f44336'; if ($issue['severity'] == 'medium') $color = '#ff9800'; if ($issue['severity'] == 'low') $color = '#ffc107'; if ($issue['severity'] == 'positive') $color = '#4CAF50'; echo '<li style="margin-bottom: 8px; padding-left: 5px; border-left: 3px solid ' . $color . ';">'; echo $issue['message']; echo '</li>'; } echo '</ul>'; echo '</div>'; // 添加内联样式 echo '<style> .seotools-analysis h3 { margin-top: 0; border-bottom: 1px solid #ddd; padding-bottom: 8px; } .seotools-analysis ul { margin-left: 0; padding-left: 0; list-style: none; } </style>'; } 2.2 关键词密度分析器 关键词密度是SEO的重要指标之一。下面我们创建一个关键词密度分析工具: class Keyword_Density_Analyzer { public static function analyze($content, $keywords = array()) { $results = array(); // 清理内容,移除HTML标签和短代码 $clean_content = strip_tags($content); $clean_content = strip_shortcodes($clean_content); // 转换为小写 $clean_content = mb_strtolower($clean_content); // 获取所有词语 $words = preg_split('/s+/', $clean_content); $total_words = count($words); // 如果没有提供关键词,自动提取常见词 if (empty($keywords)) { $keywords = self::extract_potential_keywords($clean_content); } // 分析每个关键词 foreach ($keywords as $keyword) { $keyword_lower = mb_strtolower($keyword); $keyword_count = 0; // 计算关键词出现次数 foreach ($words as $word) { if ($word == $keyword_lower) { $keyword_count++; } } // 计算密度 $density = ($total_words > 0) ? ($keyword_count / $total_words) * 100 : 0; $results[$keyword] = array( 'count' => $keyword_count, 'density' => round($density, 2), 'recommendation' => self::get_density_recommendation($density) ); } return array( 'results' => $results, 'total_words' => $total_words ); } private static function extract_potential_keywords($content) { $words = preg_split('/s+/', $content); $word_freq = array(); // 统计词频 foreach ($words as $word) { // 过滤短词和常见停用词 if (mb_strlen($word) > 3 && !in_array($word, self::get_stop_words())) { if (!isset($word_freq[$word])) { $word_freq[$word] = 0; } $word_freq[$word]++; } } // 按频率排序 arsort($word_freq); // 返回前10个最常出现的词 return array_slice(array_keys($word_freq), 0, 10); } private static function get_stop_words() { return array('the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'any', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'man', 'new', 'now', 'old', 'see', 'two', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she', 'too', 'use'); } private static function get_density_recommendation($density) { if ($density < 0.5) { return '密度过低,建议增加关键词出现次数'; } elseif ($density > 2.5) { return '密度过高,可能被搜索引擎视为关键词堆砌'; } else { return '密度适中,保持当前水平'; } } } // 在文章编辑页面添加关键词分析功能 function seotools_add_keyword_analysis() { global $post; if (!$post) return; $content = $post->post_content; $analysis = Keyword_Density_Analyzer::analyze($content); echo '<div class="keyword-analysis">'; echo '<h3>关键词密度分析</h3>'; echo '<p>总词数: ' . $analysis['total_words'] . '</p>'; if (!empty($analysis['results'])) { echo '<table class="widefat fixed" cellspacing="0">'; echo '<thead><tr><th>关键词</th><th>出现次数</th><th>密度</th><th>建议</th></tr></thead>'; echo '<tbody>'; foreach ($analysis['results'] as $keyword => $data) { echo '<tr>'; echo '<td>' . htmlspecialchars($keyword) . '</td>'; echo '<td>' . $data['count'] . '</td>'; echo '<td>' . $data['density'] . '%</td>'; echo '<td>' . $data['recommendation'] . '</td>'; echo '</tr>'; } echo '</tbody></table>'; } else { echo '<p>未检测到显著关键词</p>'; } echo '</div>'; } add_action('edit_form_after_editor', 'seotools_add_keyword_analysis'); 第三章:集成外部SEO工具API 3.1 集成Google PageSpeed Insights Google PageSpeed Insights是评估网站性能的重要工具。下面我们将其集成到WordPress后台: class PageSpeed_Integration { private $api_key; public function __construct($api_key = '') { $this->api_key = $api_key; } public function analyze_url($url, $strategy = 'desktop') { $api_url = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'; $params = array( 'url' => $url, 'strategy' => $strategy, 'locale' => 'zh_CN' ); if (!empty($this->api_key)) { $params['key'] = $this->api_key; } $api_url .= '?' . http_build_query($params); $response = wp_remote_get($api_url, array( 'timeout' => 30, 'sslverify' => false )); 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 (isset($data['error'])) { return array( 'success' => false, 'error' => $data['error']['message'] ); } return array( 'success' => true, 'data' => $this->parse_results($data) ); } private function parse_results($data) { $result = array(); // 提取性能分数 if (isset($data['lighthouseResult']['categories']['performance']['score'])) { $result['performance_score'] = $data['lighthouseResult']['categories']['performance']['score'] * 100; } // 提取关键指标 $audits = $data['lighthouseResult']['audits']; $important_metrics = array( 'first-contentful-paint' => '首次内容绘制(FCP)', 'largest-contentful-paint' => '最大内容绘制(LCP)', 'cumulative-layout-shift' => '累积布局偏移(CLS)', 'total-blocking-time' => '总阻塞时间(TBT)', 'speed-index' => '速度指数' ); foreach ($important_metrics as $key => $label) { if (isset($audits[$key])) { $result['metrics'][$label] = array( 'score' => isset($audits[$key]['score']) ? $audits[$key]['score'] : null, displayValue']) ? $audits[$key]['displayValue'] : 'N/A', 'description' => isset($audits[$key]['description']) ? $audits[$key]['description'] : '' ); } } // 提取优化建议 $result['opportunities'] = array(); $opportunity_audits = array( 'render-blocking-resources', 'unused-css-rules', 'unused-javascript', 'modern-image-formats', 'offscreen-images' ); foreach ($opportunity_audits as $audit_key) { if (isset($audits[$audit_key]) && isset($audits[$audit_key]['details']['items'])) { $result['opportunities'][$audits[$audit_key]['title']] = array( 'impact' => $audits[$audit_key]['score'] ? (1 - $audits[$audit_key]['score']) * 100 : 0, 'items' => count($audits[$audit_key]['details']['items']) ); } } return $result; } // 创建后台页面 public function create_admin_page() { add_submenu_page( 'tools.php', 'PageSpeed分析', 'PageSpeed分析', 'manage_options', 'pagespeed-analysis', array($this, 'render_admin_page') ); } public function render_admin_page() { $results = array(); if (isset($_POST['analyze_url'])) { $url = esc_url_raw($_POST['url']); $strategy = sanitize_text_field($_POST['strategy']); if (!empty($url)) { $results = $this->analyze_url($url, $strategy); } } ?> <div class="wrap"> <h1>Google PageSpeed Insights分析</h1> <form method="post" action=""> <table class="form-table"> <tr> <th scope="row"><label for="url">网站URL</label></th> <td> <input type="url" id="url" name="url" value="<?php echo isset($_POST['url']) ? esc_attr($_POST['url']) : get_site_url(); ?>" class="regular-text" required> </td> </tr> <tr> <th scope="row"><label for="strategy">分析策略</label></th> <td> <select id="strategy" name="strategy"> <option value="desktop" <?php selected(isset($_POST['strategy']) ? $_POST['strategy'] : 'desktop', 'desktop'); ?>>桌面设备</option> <option value="mobile" <?php selected(isset($_POST['strategy']) ? $_POST['strategy'] : 'desktop', 'mobile'); ?>>移动设备</option> </select> </td> </tr> </table> <?php submit_button('开始分析', 'primary', 'analyze_url'); ?> </form> <?php if (!empty($results)): ?> <?php if ($results['success']): ?> <div class="pagespeed-results"> <h2>分析结果</h2> <!-- 性能分数 --> <div class="performance-score"> <h3>性能分数: <?php echo round($results['data']['performance_score']); ?>/100</h3> <div class="score-bar"> <div class="score-fill" style="width: <?php echo $results['data']['performance_score']; ?>%; background-color: <?php echo $this->get_score_color($results['data']['performance_score']); ?>;"> </div> </div> </div> <!-- 核心指标 --> <h3>核心Web指标</h3> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>指标</th> <th>分数</th> <th>数值</th> <th>说明</th> </tr> </thead> <tbody> <?php foreach ($results['data']['metrics'] as $label => $metric): ?> <tr> <td><?php echo esc_html($label); ?></td> <td> <?php if ($metric['score'] !== null): ?> <span class="metric-score" style="color: <?php echo $this->get_score_color($metric['score'] * 100); ?>;"> <?php echo round($metric['score'] * 100); ?>/100 </span> <?php else: ?> N/A <?php endif; ?> </td> <td><?php echo esc_html($metric['displayValue']); ?></td> <td><?php echo wp_kses_post($metric['description']); ?></td> </tr> <?php endforeach; ?> </tbody> </table> <!-- 优化建议 --> <?php if (!empty($results['data']['opportunities'])): ?> <h3>优化建议</h3> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>优化项</th> <th>影响程度</th> <th>问题数量</th> </tr> </thead> <tbody> <?php foreach ($results['data']['opportunities'] as $title => $opportunity): ?> <tr> <td><?php echo esc_html($title); ?></td> <td> <div class="impact-bar"> <div class="impact-fill" style="width: <?php echo $opportunity['impact']; ?>%;"></div> <span><?php echo round($opportunity['impact']); ?>%</span> </div> </td> <td><?php echo $opportunity['items']; ?>个</td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <style> .score-bar, .impact-bar { height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; position: relative; } .score-fill, .impact-fill { height: 100%; transition: width 0.5s ease; } .impact-bar span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 12px; font-weight: bold; } .metric-score { font-weight: bold; } </style> <?php else: ?> <div class="notice notice-error"> <p>分析失败: <?php echo esc_html($results['error']); ?></p> </div> <?php endif; ?> <?php endif; ?> </div> <?php } private function get_score_color($score) { if ($score >= 90) return '#4CAF50'; if ($score >= 50) return '#FFC107'; return '#F44336'; } } // 初始化PageSpeed集成$pagespeed_integration = new PageSpeed_Integration();add_action('admin_menu', array($pagespeed_integration, 'create_admin_page')); ### 3.2 集成百度SEO数据查询 针对中文网站,集成百度SEO数据查询功能: class Baidu_SEO_Integration { public function create_admin_page() { add_submenu_page( 'tools.php', '百度SEO查询', '百度SEO查询', 'manage_options', 'baidu-seo-check', array($this, 'render_admin_page') ); } public function render_admin_page() { $results = array(); if (isset($_POST['check_url'])) { $url = esc_url_raw($_POST['url']); if (!empty($url)) { $results = $this->check_baidu_seo($url); } } ?> <div class="wrap"> <h1>百度SEO数据查询</h1> <form method="post" action=""> <table class="form-table"> <tr> <th scope="row"><label for="url">查询URL</label></th> <td> <input type="url" id="url" name="url" value="<?php echo isset($_POST['url']) ? esc_attr($_POST['url']) : get_site_url(); ?>" class="regular-text" required> <p class="description">请输入完整的URL地址</p> </td> </tr> </table> <?php submit_button('查询SEO数据', 'primary', 'check_url'); ?> </form> <?php if (!empty($results)): ?> <div class="baidu-results"> <h2>查询结果</h2> <div class="seo-metrics-grid"> <div class="metric-card"> <h3>百度收录</h3> <div class="metric-value"><?php echo $results['indexed'] ? '已收录' : '未收录'; ?></div> <?php if ($results['indexed']): ?> <p class="metric-desc">该页面已被百度收录</p> <?php else: ?> <p class="metric-desc">该页面可能未被百度收录</p> <?php endif; ?> </div> <div class="metric-card"> <h3>百度权重</h3> <div class="metric-value"><?php echo esc_html($results['weight']); ?></div> <p class="metric-desc">预估百度权重值</p> </div> <div class="metric-card"> <h3>反链数量</h3> <div class="metric-value"><?php echo esc_html($results['backlinks']); ?></div> <p class="metric-desc">外部链接数量</p> </div> <div class="metric-card"> <h3>关键词排名</h3> <div class="metric-value"><?php echo esc_html($results['keywords_count']); ?>个</div> <p class="metric-desc">有排名的关键词数量</p> </div> </div> <?php if (!empty($results['keywords'])): ?> <h3>关键词排名详情</h3> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>关键词</th> <th>排名</th> <th>搜索量</th> <th>难度</th> </tr> </thead> <tbody> <?php foreach ($results['keywords'] as $keyword): ?> <tr> <td><?php echo esc_html($keyword['keyword']); ?></td> <td> <span class="rank-badge rank-<?php echo $this->get_rank_class($keyword['rank']); ?>"> 第<?php echo $keyword['rank']; ?>名 </span> </td> <td><?php echo $this->format_search_volume($keyword['volume']); ?></td> <td> <div class="difficulty-bar"> <div class="difficulty-fill" style="width: <?php echo $keyword['difficulty']; ?>%;"></div> <span><?php echo $keyword['difficulty']; ?>%</span> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <style> .seo-metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .metric-card { background: white; border: 1px solid #ddd; border-radius: 8px; padding: 20px; text-align: center; } .metric-card h3 { margin-top: 0; color: #333; } .metric-value { font-size: 32px; font-weight: bold; color: #2271b1; margin: 10px 0; } .metric-desc { color: #666; font-size: 14px; margin: 0; } .rank-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; } .rank-1 { background: #4CAF50; color: white; } .rank-2 { background: #8BC34A; color: white; } .rank-3 { background: #FFC107; color: black; } .rank-4 { background: #FF9800; color: white; } .rank-5 { background: #F44336; color: white; } .difficulty-bar { height: 20px; background: #f0f0f0; border-radius: 10px; overflow: hidden; position: relative; } .difficulty-fill { height: 100%; background: linear-gradient(90deg, #4CAF50, #FFC107, #F44336); } .difficulty-bar span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 12px; font-weight: bold; } </style> <?php endif; ?> </div> <?php } private function check_baidu_seo($url) { // 注意:这里使用模拟数据,实际应用中需要调用百度API // 由于百度官方API限制,这里展示如何构建查询逻辑 $parsed_url = parse_url($url); $domain = $parsed_url['host']; // 模拟数据 - 实际应用中应调用相应API return array( 'indexed' => true, 'weight' => '2', 'backlinks' => '1,234', 'keywords_count' => '45', 'keywords' => array( array('keyword' => 'WordPress教程', 'rank' => 3, 'volume' => 2400, 'difficulty' => 65), array('keyword' => 'SEO优化', 'rank' => 8, 'volume' => 4800, 'difficulty' => 78), array('keyword' => '网站建设', 'rank' => 12, 'volume' => 3200, 'difficulty' => 72), array('keyword' => '内容管理系统', 'rank' => 5, 'volume' => 1200, 'difficulty' => 58) ) ); } private function get_rank_class($rank) { if ($rank <= 3) return '1'; if ($rank <= 10) return '2'; if ($rank <= 20) return '3'; if ($rank <= 50) return '4'; return '5'; } private function format_search_volume($volume) { if ($volume >= 1000) { return round($volume / 1000, 1) . 'K'; } return $volume; } } // 初始化百度SEO集成$baidu_seo_integration = new Baidu_SEO_Integration();add_action('admin_menu', array($baidu_seo_integration, 'create_admin_page')); ## 第四章:实用互联网小工具集成 ### 4.1 网站Uptime监控工具 创建一个简单的网站正常运行时间监控工具: class Uptime_Monitor { private $check_interval = 300; // 5分钟检查一次 public function __construct() { // 添加定时任务 add_action('init', array($this, 'schedule_checks')); add_action('uptime_check_event', array($this, 'perform_uptime_check')); // 添加后台页面 add_action('admin_menu', array($this, 'add_admin_page')); } public function schedule_checks() { if (!wp_next_scheduled('uptime_check_event')) { wp_schedule_event(time(), 'five_minutes', 'uptime_check_event'); } } // 自定义定时任务间隔 public function add_cron_interval($schedules) { $schedules['five_minutes'] = array( 'interval' => 300, 'display' => __('每5分钟') ); return $schedules; } public function perform_uptime_check() { $url = get_site_url(); $start_time = microtime(true); $response = wp_remote_get($url, array( 'timeout' => 30, 'sslverify' => false )); $end_time = microtime(true); $response_time = round(($end_time - $start_time) * 1000); // 转换为毫秒 $status = array( 'timestamp' => current_time('timestamp'), 'response_code' => wp_remote_retrieve_response_code($response), 'response_time' => $response_time, 'success' => !is_wp_error($response) && wp_remote_retrieve_response_code($response) == 200 ); $this->save_check_result($status); // 如果检测到宕机,发送通知 if (!$status['success']) { $this->send_downtime_notification($status); } } private function save_check_result($status) { $history = get_option('uptime_monitor_history', array()); // 只保留最近24小时的数据(288条,每5分钟一条) $history[] = $status; if (count($history) > 288) { $history = array_slice($history, -

发表评论

详细指南,开发网站会员积分与等级系统

详细指南:开发WordPress网站会员积分与等级系统 引言:为什么需要会员积分与等级系统? 在当今竞争激烈的互联网环境中,网站用户留存和活跃度已成为衡量网站成功的重要指标。会员积分与等级系统作为一种成熟的用户激励策略,能够有效提升用户参与度、增加用户粘性,并最终促进网站的商业转化。无论是电商平台、内容社区还是在线教育网站,一个设计良好的积分等级系统都能显著提升用户体验和忠诚度。 WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和灵活性。通过代码二次开发,我们可以在WordPress网站上实现功能完善的会员积分与等级系统,而无需依赖昂贵的第三方插件。本指南将详细介绍如何从零开始,通过代码开发实现这一系统。 第一章:系统设计与规划 1.1 确定系统目标与功能需求 在开始编码之前,我们需要明确积分等级系统的核心目标: 用户激励:鼓励用户完成特定行为(发布内容、评论、登录等) 用户分层:根据用户活跃度划分不同等级,提供差异化权益 数据收集:获取用户行为数据,为后续运营决策提供支持 社区建设:促进用户互动,形成良性竞争氛围 基于这些目标,我们规划以下核心功能: 积分获取规则:定义用户可通过哪些行为获得积分 等级划分体系:设置多个用户等级及升级条件 特权与权益:不同等级用户享有不同特权 积分消耗机制:用户可使用积分兑换礼品或特权 数据统计与展示:用户可查看自己的积分、等级和排名 1.2 数据库结构设计 我们需要在WordPress现有数据库基础上,添加以下自定义表: -- 用户积分表 CREATE TABLE wp_user_points ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, points INT DEFAULT 0, total_points INT DEFAULT 0, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 积分记录表 CREATE TABLE wp_points_log ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, points_change INT NOT NULL, action_type VARCHAR(50) NOT NULL, related_id BIGINT(20), description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 用户等级表 CREATE TABLE wp_user_levels ( id INT AUTO_INCREMENT PRIMARY KEY, level_name VARCHAR(50) NOT NULL, level_num INT NOT NULL, min_points INT NOT NULL, max_points INT, privileges TEXT, badge_url VARCHAR(255) ); -- 用户等级关系表 CREATE TABLE wp_user_level_relations ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, level_id INT NOT NULL, achieved_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE, FOREIGN KEY (level_id) REFERENCES wp_user_levels(id) ON DELETE CASCADE ); 1.3 系统架构设计 我们的积分等级系统将采用以下架构: 核心管理层:处理积分计算、等级评定等核心逻辑 行为监听层:监听用户行为并触发积分奖励 数据存储层:管理积分和等级数据的存储与检索 前端展示层:向用户展示积分、等级和排名信息 管理界面层:为管理员提供系统配置和监控功能 第二章:开发环境搭建与基础配置 2.1 创建自定义插件 为了避免主题更新导致代码丢失,我们将创建一个独立的WordPress插件: <?php /** * Plugin Name: 会员积分与等级系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加会员积分与等级功能 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('MPL_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('MPL_PLUGIN_URL', plugin_dir_url(__FILE__)); define('MPL_VERSION', '1.0.0'); // 包含必要文件 require_once MPL_PLUGIN_DIR . 'includes/class-database.php'; require_once MPL_PLUGIN_DIR . 'includes/class-points-manager.php'; require_once MPL_PLUGIN_DIR . 'includes/class-levels-manager.php'; require_once MPL_PLUGIN_DIR . 'includes/class-actions-listener.php'; require_once MPL_PLUGIN_DIR . 'includes/class-shortcodes.php'; require_once MPL_PLUGIN_DIR . 'includes/class-admin-panel.php'; 2.2 数据库表创建与更新 创建数据库管理类,负责表的创建和更新: <?php // includes/class-database.php class MPL_Database { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { // 注册激活钩子 register_activation_hook(__FILE__, array($this, 'create_tables')); // 注册更新检查 add_action('plugins_loaded', array($this, 'check_for_updates')); } public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 用户积分表 $table_points = $wpdb->prefix . 'user_points'; $sql_points = "CREATE TABLE IF NOT EXISTS $table_points ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, points INT DEFAULT 0, total_points INT DEFAULT 0, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY user_id (user_id) ) $charset_collate;"; // 积分记录表 $table_log = $wpdb->prefix . 'points_log'; $sql_log = "CREATE TABLE IF NOT EXISTS $table_log ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, points_change INT NOT NULL, action_type VARCHAR(50) NOT NULL, related_id BIGINT(20), description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY user_id (user_id), KEY action_type (action_type) ) $charset_collate;"; // 用户等级表 $table_levels = $wpdb->prefix . 'user_levels'; $sql_levels = "CREATE TABLE IF NOT EXISTS $table_levels ( id INT AUTO_INCREMENT PRIMARY KEY, level_name VARCHAR(50) NOT NULL, level_num INT NOT NULL, min_points INT NOT NULL, max_points INT, privileges TEXT, badge_url VARCHAR(255), UNIQUE KEY level_num (level_num) ) $charset_collate;"; // 用户等级关系表 $table_level_relations = $wpdb->prefix . 'user_level_relations'; $sql_level_relations = "CREATE TABLE IF NOT EXISTS $table_level_relations ( id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) NOT NULL, level_id INT NOT NULL, achieved_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY user_level (user_id, level_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_points); dbDelta($sql_log); dbDelta($sql_levels); dbDelta($sql_level_relations); // 插入默认等级数据 $this->insert_default_levels(); // 设置插件版本 update_option('mpl_db_version', MPL_VERSION); } private function insert_default_levels() { global $wpdb; $table_levels = $wpdb->prefix . 'user_levels'; // 检查是否已有数据 $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_levels"); if ($count == 0) { $default_levels = array( array('level_name' => '新手', 'level_num' => 1, 'min_points' => 0, 'max_points' => 100), array('level_name' => '初级会员', 'level_num' => 2, 'min_points' => 101, 'max_points' => 500), array('level_name' => '中级会员', 'level_num' => 3, 'min_points' => 501, 'max_points' => 2000), array('level_name' => '高级会员', 'level_num' => 4, 'min_points' => 2001, 'max_points' => 10000), array('level_name' => '至尊会员', 'level_num' => 5, 'min_points' => 10001, 'max_points' => NULL), ); foreach ($default_levels as $level) { $wpdb->insert($table_levels, $level); } } } public function check_for_updates() { $current_version = get_option('mpl_db_version', '0'); if (version_compare($current_version, MPL_VERSION, '<')) { $this->create_tables(); } } } 第三章:核心功能开发 3.1 积分管理类实现 积分管理类是系统的核心,负责处理所有与积分相关的操作: <?php // includes/class-points-manager.php class MPL_Points_Manager { private static $instance = null; private $actions_points = array(); public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { // 初始化积分规则 $this->init_points_rules(); // 添加用户注册时初始化积分记录 add_action('user_register', array($this, 'init_user_points')); // 添加删除用户时清理积分记录 add_action('delete_user', array($this, 'delete_user_points')); } private function init_points_rules() { // 定义各种行为对应的积分值 $this->actions_points = array( 'user_register' => 100, // 注册账号 'daily_login' => 10, // 每日登录 'publish_post' => 50, // 发布文章 'publish_comment' => 5, // 发表评论 'comment_approved' => 10, // 评论被审核通过 'post_liked' => 2, // 文章被点赞 'comment_liked' => 1, // 评论被点赞 'post_shared' => 20, // 分享文章 'profile_completed' => 30, // 完善个人资料 'referral_user' => 200, // 推荐用户注册 ); // 允许通过过滤器修改积分规则 $this->actions_points = apply_filters('mpl_points_rules', $this->actions_points); } public function init_user_points($user_id) { global $wpdb; $table_points = $wpdb->prefix . 'user_points'; // 检查用户是否已有积分记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_points WHERE user_id = %d", $user_id )); if (!$existing) { $wpdb->insert($table_points, array( 'user_id' => $user_id, 'points' => 0, 'total_points' => 0 )); // 为新用户添加注册积分 $this->add_points($user_id, 'user_register', $user_id); } } public function add_points($user_id, $action_type, $related_id = null, $description = '') { global $wpdb; // 检查用户是否存在 if (!get_userdata($user_id)) { return false; } // 获取该行为对应的积分值 $points = isset($this->actions_points[$action_type]) ? $this->actions_points[$action_type] : 0; if ($points <= 0) { return false; } // 检查今日是否已获得过该类型积分(防止刷分) if ($this->is_action_limited($user_id, $action_type)) { return false; } $table_points = $wpdb->prefix . 'user_points'; $table_log = $wpdb->prefix . 'points_log'; // 开始事务 $wpdb->query('START TRANSACTION'); try { // 更新用户总积分 $result = $wpdb->query($wpdb->prepare( "UPDATE $table_points SET points = points + %d, total_points = total_points + %d WHERE user_id = %d", $points, $points, $user_id )); if ($result === false) { throw new Exception('更新用户积分失败'); } // 记录积分日志 $log_data = array( 'user_id' => $user_id, 'points_change' => $points, 'action_type' => $action_type, 'related_id' => $related_id, 'description' => $description ?: $this->get_action_description($action_type) ); $log_result = $wpdb->insert($table_log, $log_data); if ($log_result === false) { throw new Exception('记录积分日志失败'); } // 提交事务 $wpdb->query('COMMIT'); // 触发积分添加钩子 do_action('mpl_points_added', $user_id, $points, $action_type, $related_id); // 检查用户等级是否需要更新 $this->check_user_level($user_id); return true; } catch (Exception $e) { // 回滚事务 $wpdb->query('ROLLBACK'); error_log('MPL积分系统错误: ' . $e->getMessage()); return false; } } public function deduct_points($user_id, $points, $reason, $related_id = null) { global $wpdb; if ($points <= 0) { return false; } $table_points = $wpdb->prefix . 'user_points'; // 检查用户是否有足够积分 $current_points = $this->get_user_points($user_id); if ($current_points < $points) { return false; } // 开始事务 $wpdb->query('START TRANSACTION'); try { // 扣除积分 $result = $wpdb->query($wpdb->prepare( "UPDATE $table_points SET points = points - %d WHERE user_id = %d", $points, $user_id )); if ($result === false) { throw new Exception('扣除用户积分失败'); } // 记录积分日志(负值表示扣除) $table_log = $wpdb->prefix . 'points_log'; $log_data = array( 'user_id' => $user_id, 'points_change' => -$points, 'action_type' => 'points_deducted', 'related_id' => $related_id, 'description' => $reason ); $log_result = $wpdb->insert($table_log, $log_data); if ($log_result === false) { throw new Exception('记录积分扣除日志失败'); } // 提交事务 $wpdb->query('COMMIT'); // 触发积分扣除钩子 do_action('mpl_points_deducted', $user_id, $points, $reason, $related_id); return true; } catch (Exception $e) { // 回滚事务 $wpdb->query('ROLLBACK'); error_log('MPL积分系统错误: ' . $e->getMessage()); return false; } } public function get_user_points($user_id) { global $wpdb; $table_points = $wpdb->prefix . 'user_points'; $points = $wpdb->get_var($wpdb->prepare( "SELECT points FROM $table_points WHERE user_id = %d", $user_id )); return $points ? intval($points) : 0; } public function get_user_total_points($user_id) { global $wpdb; $table_points = $wpdb->prefix . 'user_points'; $total_points = $wpdb->get_var($wpdb->prepare( "SELECT total_points FROM $table_points WHERE user_id = %d", $user_id )); return $total_points ? intval($total_points) : 0; } public function get_points_log($user_id, $limit = 20, $offset = 0) { global $wpdb; $table_log = $wpdb->prefix . 'points_log'; $logs = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_log WHERE user_id = %d ORDER BY created_at DESC LIMIT %d OFFSET %d", $user_id, $limit, $offset )); return $logs; } private function is_action_limited($user_id, $action_type) { // 对于某些行为,限制每日获取次数 3.2 等级管理类实现 等级管理类负责处理用户等级评定、升级逻辑和特权管理: <?php // includes/class-levels-manager.php class MPL_Levels_Manager { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { // 初始化钩子 add_action('mpl_points_added', array($this, 'check_user_level_on_points_change'), 10, 2); } public function check_user_level($user_id) { global $wpdb; $points_manager = MPL_Points_Manager::get_instance(); $current_points = $points_manager->get_user_points($user_id); // 获取用户当前等级 $current_level = $this->get_user_level($user_id); // 获取所有等级定义 $levels = $this->get_all_levels(); // 根据积分确定应属等级 $target_level = null; foreach ($levels as $level) { if ($current_points >= $level->min_points && ($level->max_points === null || $current_points <= $level->max_points)) { $target_level = $level; break; } } // 如果用户没有等级或需要升级 if (!$current_level || ($target_level && $target_level->level_num > $current_level->level_num)) { $this->update_user_level($user_id, $target_level->id); // 触发等级升级钩子 do_action('mpl_level_upgraded', $user_id, $current_level, $target_level); // 发送升级通知 $this->send_level_up_notification($user_id, $target_level); return true; } return false; } public function get_user_level($user_id) { global $wpdb; $table_level_relations = $wpdb->prefix . 'user_level_relations'; $table_levels = $wpdb->prefix . 'user_levels'; $level = $wpdb->get_row($wpdb->prepare( "SELECT l.* FROM $table_levels l INNER JOIN $table_level_relations r ON l.id = r.level_id WHERE r.user_id = %d ORDER BY l.level_num DESC LIMIT 1", $user_id )); return $level; } public function update_user_level($user_id, $level_id) { global $wpdb; $table_level_relations = $wpdb->prefix . 'user_level_relations'; // 检查是否已拥有该等级 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_level_relations WHERE user_id = %d AND level_id = %d", $user_id, $level_id )); if (!$existing) { $wpdb->insert($table_level_relations, array( 'user_id' => $user_id, 'level_id' => $level_id )); return true; } return false; } public function get_all_levels() { global $wpdb; $table_levels = $wpdb->prefix . 'user_levels'; $levels = $wpdb->get_results( "SELECT * FROM $table_levels ORDER BY level_num ASC" ); return $levels; } public function get_level_by_points($points) { global $wpdb; $table_levels = $wpdb->prefix . 'user_levels'; $level = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_levels WHERE min_points <= %d AND (max_points >= %d OR max_points IS NULL) ORDER BY level_num DESC LIMIT 1", $points, $points )); return $level; } public function get_next_level($current_level_num) { global $wpdb; $table_levels = $wpdb->prefix . 'user_levels'; $next_level = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_levels WHERE level_num > %d ORDER BY level_num ASC LIMIT 1", $current_level_num )); return $next_level; } public function get_level_progress($user_id) { $points_manager = MPL_Points_Manager::get_instance(); $current_points = $points_manager->get_user_points($user_id); $current_level = $this->get_user_level($user_id); $next_level = $this->get_next_level($current_level->level_num); if (!$next_level) { return array( 'current_level' => $current_level, 'next_level' => null, 'progress_percentage' => 100, 'points_to_next' => 0, 'current_points' => $current_points ); } $points_range = $next_level->min_points - $current_level->min_points; $points_in_current = $current_points - $current_level->min_points; $progress_percentage = $points_range > 0 ? min(100, round(($points_in_current / $points_range) * 100, 2)) : 0; $points_to_next = max(0, $next_level->min_points - $current_points); return array( 'current_level' => $current_level, 'next_level' => $next_level, 'progress_percentage' => $progress_percentage, 'points_to_next' => $points_to_next, 'current_points' => $current_points ); } public function get_level_privileges($level_id) { global $wpdb; $table_levels = $wpdb->prefix . 'user_levels'; $privileges_json = $wpdb->get_var($wpdb->prepare( "SELECT privileges FROM $table_levels WHERE id = %d", $level_id )); if ($privileges_json) { return json_decode($privileges_json, true); } return array(); } public function check_user_privilege($user_id, $privilege_key) { $user_level = $this->get_user_level($user_id); if (!$user_level) { return false; } $privileges = $this->get_level_privileges($user_level->id); return isset($privileges[$privilege_key]) && $privileges[$privilege_key] === true; } private function send_level_up_notification($user_id, $new_level) { $user = get_userdata($user_id); if (!$user) { return; } $subject = sprintf('恭喜!您已升级为%s', $new_level->level_name); $message = sprintf( "亲爱的%s,nn恭喜您!您的会员等级已提升至【%s】。nn" . "这是对您长期支持的认可,您现在可以享受更多专属特权。nn" . "继续努力,向更高等级迈进吧!nn" . "祝您使用愉快!n%s团队", $user->display_name, $new_level->level_name, get_bloginfo('name') ); wp_mail($user->user_email, $subject, $message); // 同时发送站内通知 if (function_exists('bp_notifications_add_notification')) { // 如果使用BuddyPress,添加通知 bp_notifications_add_notification(array( 'user_id' => $user_id, 'item_id' => $new_level->id, 'secondary_item_id' => 0, 'component_name' => 'mpl_points', 'component_action' => 'level_upgraded', 'date_notified' => bp_core_current_time(), 'is_new' => 1, )); } } public function check_user_level_on_points_change($user_id, $points_added) { $this->check_user_level($user_id); } } 3.3 行为监听类实现 行为监听类负责监听用户的各种行为并触发相应的积分奖励: <?php // includes/class-actions-listener.php class MPL_Actions_Listener { private static $instance = null; private $points_manager; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->points_manager = MPL_Points_Manager::get_instance(); // 初始化所有监听器 $this->init_listeners(); } private function init_listeners() { // 文章发布监听 add_action('publish_post', array($this, 'on_publish_post'), 10, 2); // 评论发布监听 add_action('comment_post', array($this, 'on_comment_post'), 10, 3); add_action('wp_set_comment_status', array($this, 'on_comment_approved'), 10, 2); // 每日登录监听 add_action('wp_login', array($this, 'on_user_login'), 10, 2); // 点赞系统集成(需要与点赞插件配合) add_action('mpl_post_liked', array($this, 'on_post_liked'), 10, 2); add_action('mpl_comment_liked', array($this, 'on_comment_liked'), 10, 2); // 分享监听 add_action('mpl_content_shared', array($this, 'on_content_shared'), 10, 2); // 个人资料完善监听 add_action('profile_update', array($this, 'on_profile_updated'), 10, 2); // 推荐用户注册监听 add_action('user_register', array($this, 'on_referral_registration'), 10, 1); } public function on_publish_post($post_id, $post) { // 确保是新建文章,而不是更新 if ($post->post_date !== $post->post_modified) { return; } $user_id = $post->post_author; $this->points_manager->add_points( $user_id, 'publish_post', $post_id, sprintf('发布文章《%s》', get_the_title($post_id)) ); } public function on_comment_post($comment_id, $comment_approved, $commentdata) { if ($comment_approved == 1) { $user_id = $commentdata['user_id']; // 匿名评论没有用户ID if ($user_id > 0) { $this->points_manager->add_points( $user_id, 'publish_comment', $comment_id, sprintf('发表评论于文章《%s》', get_the_title($commentdata['comment_post_ID'])) ); } } } public function on_comment_approved($comment_id, $comment_status) { if ($comment_status == 'approve') { $comment = get_comment($comment_id); if ($comment->user_id > 0) { // 如果之前已经因为评论获得过积分,不再重复奖励 global $wpdb; $table_log = $wpdb->prefix . 'points_log'; $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_log WHERE user_id = %d AND related_id = %d AND action_type = 'comment_approved'", $comment->user_id, $comment_id )); if (!$existing) { $this->points_manager->add_points( $comment->user_id, 'comment_approved', $comment_id, sprintf('评论在文章《%s》中被审核通过', get_the_title($comment->comment_post_ID)) ); } } } } public function on_user_login($user_login, $user) { $user_id = $user->ID; $today = date('Y-m-d'); // 检查今日是否已登录过 global $wpdb; $table_log = $wpdb->prefix . 'points_log'; $already_logged_today = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_log WHERE user_id = %d AND action_type = 'daily_login' AND DATE(created_at) = %s", $user_id, $today )); if (!$already_logged_today) { $this->points_manager->add_points( $user_id, 'daily_login', null, '每日登录奖励' ); } } public function on_post_liked($post_id, $user_id) { $post_author = get_post_field('post_author', $post_id); if ($post_author && $post_author != $user_id) { $this->points_manager->add_points( $post_author, 'post_liked', $post_id, sprintf('文章《%s》被用户点赞', get_the_title($post_id)) ); } } public function on_comment_liked($comment_id, $user_id) { $comment = get_comment($comment_id); if ($comment->user_id && $comment->user_id != $user_id) { $this->points_manager->add_points( $comment->user_id, 'comment_liked', $comment_id, '评论被用户点赞' ); } } public function on_content_shared($content_id, $user_id) { $content_type = get_post_type($content_id); if ($content_type === 'post') { $this->points_manager->add_points( $user_id, 'post_shared', $content_id, sprintf('分享文章《%s》', get_the_title($content_id)) ); } } public function on_profile_updated($user_id, $old_user_data) { $user = get_userdata($user_id); $old_user = $old_user_data; // 检查是否完善了个人资料 $profile_completed = false; // 检查头像 if (get_user_meta($user_id, 'avatar_updated', true)) { $profile_completed = true; } // 检查个人简介 if (!empty($user->description) && empty($old_user->description)) { $profile_completed = true; } // 检查其他必填字段 $required_fields = array('first_name', 'last_name', 'user_url'); foreach ($required_fields as $field) { if (!empty($user->$field) && empty($old_user->$field)) { $profile_completed = true; break; } } if ($profile_completed) { // 确保只奖励一次 $already_rewarded = get_user_meta($user_id, 'profile_completion_rewarded', true); if (!$already_rewarded) { $this->points_manager->add_points( $user_id, 'profile_completed', null, '完善个人资料' ); update_user_meta($user_id, 'profile_completion_rewarded', true); } } } public function on_referral_registration($user_id) { // 检查是否有推荐人 $referrer_id = get_user_meta($user_id, 'referred_by', true); if ($referrer_id) { $this->points_manager->add_points( $referrer_id, 'referral_user', $user_id, sprintf('成功推荐用户 %s 注册', get_userdata($user_id)->display_name) ); } } } 第四章:前端展示与用户界面 4.1 短代码类实现 短代码类提供前端展示功能,让用户可以在任何页面查看积分和等级信息: <?php // includes/class-shortcodes.php class MPL_Shortcodes { private static $instance = null; private $points_manager; private $levels_manager; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->points_manager = MPL_Points_Manager::get_instance(); $this->levels_manager = MPL_Levels_Manager::get_instance(); // 注册短代码 add_shortcode('mpl_user_points', array($this, 'user_points_shortcode')); add_shortcode('mpl_user_level', array($this, 'user_level_shortcode')); add_shortcode('mpl_level_progress', array($this, 'level_progress_shortcode')); add_shortcode('mpl_points_log', array($this, 'points_log_shortcode')); add_shortcode('mpl_leaderboard', array($this, 'leaderboard_shortcode')); add_shortcode('mpl_levels_list', array($this, 'levels_list_shortcode')); // 注册样式和脚本 add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); } public function enqueue_frontend_assets() { wp_enqueue_style( 'mpl-frontend', MPL_PLUGIN_URL . 'assets/css/frontend.css', array(), MPL_VERSION ); wp_enqueue_script( 'mpl-frontend',

发表评论

WordPress教程,集成网站内容自动备份与恢复工具

WordPress教程:集成网站内容自动备份与恢复工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么WordPress网站需要自动化备份与智能工具集成? 在当今数字化时代,网站已成为企业、个人展示和运营的重要平台。WordPress作为全球最受欢迎的内容管理系统,驱动着超过40%的网站。然而,随着网站功能的日益复杂和数据量的不断增长,网站安全、数据保护和功能扩展成为每个WordPress管理员必须面对的核心挑战。 数据丢失可能源于多种因素:服务器故障、黑客攻击、插件冲突、人为操作失误,甚至是更新过程中的意外错误。据行业统计,超过60%的小型网站在遭遇数据丢失后无法完全恢复,导致业务中断、品牌声誉受损和直接经济损失。与此同时,用户对网站功能的需求也日益多样化,从简单的社交分享到复杂的数据展示,传统插件往往无法完全满足个性化需求。 本教程将深入探讨如何通过WordPress代码二次开发,构建一个集自动化备份恢复与实用小工具于一体的综合解决方案。这不仅能够提升网站的数据安全性,还能通过定制化工具增强网站功能,减少对第三方插件的依赖,提高网站性能和可维护性。 第一部分:WordPress备份机制深度解析与自动化策略 1.1 WordPress数据架构与备份内容分析 要构建有效的备份系统,首先需要全面理解WordPress的数据架构。WordPress数据主要分为两大类别: 数据库内容: 核心数据表(wp_posts, wp_postmeta, wp_users等) 选项设置(wp_options) 评论数据(wp_comments) 用户关系数据(wp_usermeta) 文件系统内容: 主题文件(/wp-content/themes/) 插件文件(/wp-content/plugins/) 上传文件(/wp-content/uploads/) WordPress核心文件(/wp-admin/, /wp-includes/) 配置文件(wp-config.php) 一个完整的备份方案必须同时涵盖数据库和文件系统,并考虑它们之间的关联性。例如,媒体库中的文件在数据库中有关联记录,备份时需要保持这种关联的完整性。 1.2 传统备份方法的局限性分析 大多数WordPress用户依赖以下几种备份方式: 手动备份:通过phpMyAdmin导出数据库,通过FTP下载文件 插件备份:使用UpdraftPlus、BackupBuddy等插件 主机商备份:依赖主机提供的备份服务 这些方法各有局限:手动备份效率低下且容易遗漏;插件备份可能增加服务器负载,且与某些主题/插件存在兼容性问题;主机商备份通常不提供细粒度恢复选项,且恢复时间无法保证。 1.3 自动化备份系统的设计原则 基于以上分析,一个理想的自动化备份系统应遵循以下设计原则: 完整性:备份所有必要数据,无遗漏 增量性:支持增量备份,减少存储空间和服务器负载 可恢复性:确保备份数据能够顺利恢复 安全性:备份数据加密存储,防止未授权访问 监控性:提供备份状态监控和失败告警 效率性:优化备份过程,减少对网站性能的影响 第二部分:构建WordPress自动化备份系统 2.1 系统架构设计 我们的自动化备份系统将采用模块化设计,包含以下核心组件: 备份调度器:基于WordPress Cron系统管理备份计划 数据库备份模块:处理MySQL/MariaDB数据库的导出和优化 文件系统备份模块:处理文件和目录的增量备份 压缩加密模块:对备份数据进行压缩和加密 存储管理模块:支持本地、FTP、云存储等多种存储后端 监控通知模块:监控备份状态并发送通知 2.2 核心代码实现 2.2.1 备份调度器实现 class WP_Auto_Backup_Scheduler { private $backup_intervals; public function __construct() { $this->backup_intervals = array( 'daily' => 86400, 'twicedaily' => 43200, 'hourly' => 3600, 'weekly' => 604800, ); add_filter('cron_schedules', array($this, 'add_custom_schedules')); add_action('wp_auto_backup_event', array($this, 'execute_backup')); } public function add_custom_schedules($schedules) { foreach ($this->backup_intervals as $key => $interval) { if (!isset($schedules[$key])) { $schedules[$key] = array( 'interval' => $interval, 'display' => ucfirst($key) . ' Backup' ); } } return $schedules; } public function schedule_backup($interval = 'daily') { if (!wp_next_scheduled('wp_auto_backup_event')) { wp_schedule_event(time(), $interval, 'wp_auto_backup_event'); } } public function execute_backup() { $backup_manager = new WP_Backup_Manager(); $result = $backup_manager->perform_complete_backup(); if ($result['status'] === 'success') { $this->log_backup($result); $this->send_notification('备份成功', $result); } else { $this->log_error($result); $this->send_notification('备份失败', $result, 'error'); } } } 2.2.2 数据库备份模块 class WP_Database_Backup { private $db_connection; private $backup_path; public function __construct() { $this->backup_path = WP_CONTENT_DIR . '/backups/database/'; $this->ensure_directory_exists($this->backup_path); } public function backup_database($incremental = false) { global $wpdb; $tables = $wpdb->get_col("SHOW TABLES LIKE '" . $wpdb->prefix . "%'"); $backup_file = $this->backup_path . 'db_backup_' . date('Y-m-d_H-i-s') . '.sql'; $sql_dump = ""; // 获取表结构 foreach ($tables as $table) { $create_table = $wpdb->get_row("SHOW CREATE TABLE `$table`", ARRAY_N); $sql_dump .= "nn" . $create_table[1] . ";nn"; // 获取表数据 $rows = $wpdb->get_results("SELECT * FROM `$table`", ARRAY_A); if ($rows) { foreach ($rows as $row) { $values = array_map(array($wpdb, '_real_escape'), $row); $sql_dump .= "INSERT INTO `$table` VALUES('" . implode("', '", $values) . "');n"; } } } // 增量备份处理 if ($incremental) { $sql_dump = $this->extract_incremental_changes($sql_dump); } // 写入文件 if (file_put_contents($backup_file, $sql_dump)) { return array( 'status' => 'success', 'file' => $backup_file, 'size' => filesize($backup_file), 'tables' => count($tables) ); } return array('status' => 'error', 'message' => '无法写入备份文件'); } private function extract_incremental_changes($full_dump) { // 实现增量备份逻辑 // 比较上次备份与当前数据库的差异 // 只备份发生变化的数据 return $full_dump; // 简化示例 } } 2.2.3 文件系统备份模块 class WP_Filesystem_Backup { private $backup_path; private $excluded_patterns; public function __construct() { $this->backup_path = WP_CONTENT_DIR . '/backups/filesystem/'; $this->excluded_patterns = array( '/.git/', '/.svn/', '/.DS_Store/', '/backups/', '/cache/', '/logs/' ); $this->ensure_directory_exists($this->backup_path); } public function backup_filesystem($incremental = false) { $backup_file = $this->backup_path . 'fs_backup_' . date('Y-m-d_H-i-s') . '.zip'; $zip = new ZipArchive(); if ($zip->open($backup_file, ZipArchive::CREATE) !== TRUE) { return array('status' => 'error', 'message' => '无法创建ZIP文件'); } // 备份WordPress根目录 $this->add_directory_to_zip(ABSPATH, $zip, '', $incremental); // 备份wp-content目录 $this->add_directory_to_zip(WP_CONTENT_DIR, $zip, 'wp-content', $incremental); $zip->close(); return array( 'status' => 'success', 'file' => $backup_file, 'size' => filesize($backup_file), 'compression_ratio' => $this->calculate_compression_ratio($backup_file) ); } private function add_directory_to_zip($directory, $zip, $base_path = '', $incremental = false) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::LEAVES_ONLY ); $last_backup_time = $this->get_last_backup_time(); foreach ($files as $name => $file) { if (!$file->isDir()) { $file_path = $file->getRealPath(); $relative_path = substr($file_path, strlen($directory) + 1); // 检查是否在排除列表中 if ($this->is_excluded($file_path)) { continue; } // 增量备份检查 if ($incremental && filemtime($file_path) < $last_backup_time) { continue; } $zip_path = $base_path ? $base_path . '/' . $relative_path : $relative_path; $zip->addFile($file_path, $zip_path); } } } } 2.3 备份存储与加密策略 2.3.1 多存储后端支持 class WP_Backup_Storage { private $storage_engines = array(); public function __construct() { // 注册存储引擎 $this->register_storage_engine('local', new Local_Storage()); $this->register_storage_engine('ftp', new FTP_Storage()); $this->register_storage_engine('s3', new S3_Storage()); $this->register_storage_engine('google_drive', new Google_Drive_Storage()); } public function store_backup($backup_file, $engine_type, $options = array()) { if (!isset($this->storage_engines[$engine_type])) { return array('status' => 'error', 'message' => '不支持的存储引擎'); } $engine = $this->storage_engines[$engine_type]; // 加密备份文件 $encrypted_file = $this->encrypt_backup($backup_file); // 存储到指定引擎 $result = $engine->store($encrypted_file, $options); // 清理临时加密文件 unlink($encrypted_file); return $result; } private function encrypt_backup($file_path) { $encryption_key = defined('WP_BACKUP_ENCRYPTION_KEY') ? WP_BACKUP_ENCRYPTION_KEY : $this->generate_encryption_key(); $encrypted_file = $file_path . '.enc'; $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); $encrypted_data = openssl_encrypt( file_get_contents($file_path), 'aes-256-cbc', $encryption_key, 0, $iv ); file_put_contents($encrypted_file, $iv . $encrypted_data); return $encrypted_file; } } 2.3.2 云存储集成示例(AWS S3) class S3_Storage { private $s3_client; public function __construct() { $this->s3_client = new AwsS3S3Client([ 'version' => 'latest', 'region' => get_option('wp_backup_s3_region', 'us-east-1'), 'credentials' => [ 'key' => get_option('wp_backup_s3_key'), 'secret' => get_option('wp_backup_s3_secret'), ] ]); } public function store($file_path, $options = array()) { $bucket = $options['bucket'] ?? get_option('wp_backup_s3_bucket'); $key = 'backups/' . basename($file_path); try { $result = $this->s3_client->putObject([ 'Bucket' => $bucket, 'Key' => $key, 'SourceFile' => $file_path, 'StorageClass' => 'STANDARD_IA' // 低频访问存储,降低成本 ]); return array( 'status' => 'success', 'url' => $result['ObjectURL'], 'storage_class' => 'S3', 'expiration' => date('Y-m-d H:i:s', time() + 365*24*60*60) // 1年后过期 ); } catch (AwsS3ExceptionS3Exception $e) { return array( 'status' => 'error', 'message' => $e->getMessage() ); } } } 2.4 监控与通知系统 class WP_Backup_Monitor { public function check_backup_health() { $health_status = array( 'last_backup' => $this->get_last_backup_time(), 'backup_size' => $this->get_total_backup_size(), 'storage_status' => $this->check_storage_availability(), 'integrity_checks' => $this->verify_backup_integrity() ); return $health_status; } public function send_notification($type, $data, $priority = 'normal') { $notification_methods = get_option('wp_backup_notification_methods', array('email')); foreach ($notification_methods as $method) { switch ($method) { case 'email': $this->send_email_notification($type, $data, $priority); break; case 'slack': $this->send_slack_notification($type, $data, $priority); break; case 'webhook': $this->send_webhook_notification($type, $data, $priority); break; } } } private function send_email_notification($type, $data, $priority) { $to = get_option('admin_email'); $subject = $this->get_notification_subject($type, $priority); $message = $this->generate_notification_message($type, $data); wp_mail($to, $subject, $message, array('Content-Type: text/html; charset=UTF-8')); } } 第三部分:智能恢复系统设计与实现 3.1 恢复策略与流程设计 一个可靠的恢复系统应该支持多种恢复场景: 完整恢复:从完整备份恢复整个网站 部分恢复:仅恢复数据库或特定文件 时间点恢复:恢复到特定时间点的状态 迁移恢复:将备份恢复到不同的服务器或域名 3.2 一键恢复功能实现 class WP_OneClick_Restore { public function restore_from_backup($backup_id, $options = array()) { // 进入维护模式 $this->enable_maintenance_mode(); try { // 步骤1:验证备份文件完整性 if (!$this->verify_backup_integrity($backup_id)) { throw new Exception('备份文件完整性验证失败'); } // 步骤2:下载备份文件 $backup_files = $this->download_backup_files($backup_id); // 步骤3:恢复数据库 if (in_array('database', $options['components'])) { $this->restore_database($backup_files['database']); } // 步骤4:恢复文件系统 if (in_array('filesystem', $options['components'])) { $this->restore_filesystem($backup_files['filesystem']); } // 步骤5:更新配置(如域名变更) if (isset($options['new_domain'])) { $this->update_site_url($options['new_domain']); } // 步骤6:清理缓存 $this->clear_all_caches(); // 步骤7:退出维护模式 $this->disable_maintenance_mode(); return array( 'status' => 'success', 'message' => '恢复完成', 'restored_at' => current_time('mysql') ); } catch (Exception $e) { // 恢复失败,尝试回滚 $this->attempt_rollback(); $this->disable_maintenance_mode(); return array( 'status' => 'error', 'message' => '恢复失败: ' . $e->getMessage() ); } } private function restore_database($database_file) { global $wpdb; // 临时禁用外键检查 第三部分:智能恢复系统设计与实现(续) 3.2 一键恢复功能实现(续) private function restore_database($database_file) { global $wpdb; // 临时禁用外键检查 $wpdb->query('SET FOREIGN_KEY_CHECKS = 0'); // 读取SQL文件 $sql_content = file_get_contents($database_file); $queries = $this->split_sql_queries($sql_content); // 执行每个查询 foreach ($queries as $query) { if (trim($query) !== '') { $wpdb->query($query); } } // 重新启用外键检查 $wpdb->query('SET FOREIGN_KEY_CHECKS = 1'); // 更新数据库版本 update_option('db_version', get_option('db_version') + 1); } private function restore_filesystem($filesystem_backup) { $backup_dir = WP_CONTENT_DIR . '/temp_restore/'; $this->ensure_directory_exists($backup_dir); // 解压备份文件 $zip = new ZipArchive(); if ($zip->open($filesystem_backup) === TRUE) { $zip->extractTo($backup_dir); $zip->close(); } else { throw new Exception('无法解压备份文件'); } // 恢复WordPress核心文件 $this->restore_directory($backup_dir . 'wordpress/', ABSPATH); // 恢复wp-content目录 $this->restore_directory($backup_dir . 'wp-content/', WP_CONTENT_DIR); // 清理临时文件 $this->delete_directory($backup_dir); } private function restore_directory($source, $destination) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($files as $file) { $target = $destination . DIRECTORY_SEPARATOR . $files->getSubPathName(); if ($file->isDir()) { if (!is_dir($target)) { mkdir($target, 0755, true); } } else { copy($file, $target); chmod($target, 0644); } } } private function enable_maintenance_mode() { $maintenance_file = ABSPATH . '.maintenance'; $content = '<?php $upgrading = ' . time() . '; ?>'; file_put_contents($maintenance_file, $content); } private function disable_maintenance_mode() { $maintenance_file = ABSPATH . '.maintenance'; if (file_exists($maintenance_file)) { unlink($maintenance_file); } } } 3.3 增量恢复与选择性恢复 class WP_Selective_Restore { public function restore_specific_tables($tables, $backup_id) { global $wpdb; $backup_file = $this->get_backup_file($backup_id, 'database'); $sql_content = file_get_contents($backup_file); // 提取特定表的SQL $table_queries = $this->extract_table_queries($sql_content, $tables); // 备份当前表数据 $this->backup_current_tables($tables); try { // 清空目标表 foreach ($tables as $table) { $wpdb->query("TRUNCATE TABLE `$table`"); } // 恢复表数据 foreach ($table_queries as $query) { $wpdb->query($query); } return array('status' => 'success', 'restored_tables' => $tables); } catch (Exception $e) { // 恢复失败,回滚 $this->restore_from_backup($tables, 'pre_restore_backup'); throw $e; } } public function restore_media_by_date($start_date, $end_date) { // 恢复特定时间段的媒体文件 $backup_files = $this->get_backup_files_in_range($start_date, $end_date); $restored_media = array(); foreach ($backup_files as $backup) { $media_files = $this->extract_media_from_backup($backup, $start_date, $end_date); $this->restore_files($media_files, WP_CONTENT_DIR . '/uploads/'); $restored_media = array_merge($restored_media, $media_files); } // 更新数据库中的媒体记录 $this->update_media_records($restored_media); return array( 'status' => 'success', 'restored_count' => count($restored_media), 'files' => $restored_media ); } } 第四部分:通过代码二次开发实现常用互联网小工具 4.1 小工具架构设计 我们将创建一个模块化的小工具系统,支持以下功能: 社交分享工具 内容推荐引擎 用户反馈系统 数据统计面板 SEO优化工具 性能监控工具 class WP_Toolkit_Manager { private $tools = array(); public function __construct() { $this->register_core_tools(); add_action('init', array($this, 'init_tools')); } private function register_core_tools() { $this->register_tool('social_share', new Social_Share_Tool()); $this->register_tool('content_recommend', new Content_Recommend_Tool()); $this->register_tool('user_feedback', new User_Feedback_Tool()); $this->register_tool('analytics', new Analytics_Tool()); $this->register_tool('seo_optimizer', new SEO_Optimizer_Tool()); $this->register_tool('performance_monitor', new Performance_Monitor_Tool()); } public function register_tool($slug, $tool_instance) { $this->tools[$slug] = $tool_instance; } public function init_tools() { foreach ($this->tools as $tool) { if (method_exists($tool, 'init')) { $tool->init(); } } } public function get_tool($slug) { return isset($this->tools[$slug]) ? $this->tools[$slug] : null; } } 4.2 智能社交分享工具 class Social_Share_Tool { private $platforms = array( 'facebook' => array( 'name' => 'Facebook', 'icon' => 'fab fa-facebook-f', 'color' => '#1877F2', 'api_endpoint' => 'https://www.facebook.com/sharer/sharer.php' ), 'twitter' => array( 'name' => 'Twitter', 'icon' => 'fab fa-twitter', 'color' => '#1DA1F2', 'api_endpoint' => 'https://twitter.com/intent/tweet' ), 'linkedin' => array( 'name' => 'LinkedIn', 'icon' => 'fab fa-linkedin-in', 'color' => '#0A66C2', 'api_endpoint' => 'https://www.linkedin.com/sharing/share-offsite/' ), 'wechat' => array( 'name' => '微信', 'icon' => 'fab fa-weixin', 'color' => '#07C160', 'api_endpoint' => 'javascript:' ) ); public function init() { add_action('wp_enqueue_scripts', array($this, 'enqueue_assets')); add_filter('the_content', array($this, 'add_share_buttons'), 99); add_action('wp_ajax_track_share', array($this, 'track_share')); add_action('wp_ajax_nopriv_track_share', array($this, 'track_share')); } public function enqueue_assets() { wp_enqueue_style('social-share-tool', plugin_dir_url(__FILE__) . 'css/social-share.css'); wp_enqueue_script('social-share-tool', plugin_dir_url(__FILE__) . 'js/social-share.js', array('jquery'), '1.0', true); wp_localize_script('social-share-tool', 'social_share_data', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('social_share_nonce') )); } public function add_share_buttons($content) { if (is_single() && $this->should_display_buttons()) { $buttons_html = $this->generate_share_buttons(); $content .= '<div class="social-share-container">' . $buttons_html . '</div>'; } return $content; } private function generate_share_buttons() { global $post; $post_url = urlencode(get_permalink($post->ID)); $post_title = urlencode(get_the_title($post->ID)); $post_excerpt = urlencode(wp_trim_words(get_the_excerpt($post), 20)); $buttons = array(); foreach ($this->platforms as $slug => $platform) { $share_url = $this->generate_share_url($slug, $post_url, $post_title, $post_excerpt); $buttons[] = sprintf( '<a href="%s" class="social-share-btn share-%s" data-platform="%s" data-post-id="%d" title="分享到%s">' . '<i class="%s"></i><span class="share-text">%s</span>' . '</a>', esc_url($share_url), esc_attr($slug), esc_attr($slug), $post->ID, esc_attr($platform['name']), esc_attr($platform['icon']), esc_html($platform['name']) ); } // 添加微信二维码分享 $buttons[] = $this->generate_wechat_qrcode(); return '<div class="social-share-buttons">' . implode('', $buttons) . '</div>'; } private function generate_share_url($platform, $url, $title, $excerpt) { switch ($platform) { case 'facebook': return $this->platforms[$platform]['api_endpoint'] . '?u=' . $url; case 'twitter': return $this->platforms[$platform]['api_endpoint'] . '?text=' . $title . '&url=' . $url; case 'linkedin': return $this->platforms[$platform]['api_endpoint'] . '?url=' . $url; case 'wechat': return 'javascript:void(0);'; default: return '#'; } } private function generate_wechat_qrcode() { global $post; $qrcode_url = add_query_arg(array( 'action' => 'generate_wechat_qrcode', 'url' => urlencode(get_permalink($post->ID)), 'nonce' => wp_create_nonce('wechat_qrcode_nonce') ), admin_url('admin-ajax.php')); return sprintf( '<div class="wechat-share-container">' . '<a href="javascript:void(0);" class="social-share-btn share-wechat" title="微信分享">' . '<i class="fab fa-weixin"></i><span class="share-text">微信</span>' . '</a>' . '<div class="wechat-qrcode-popup">' . '<div class="qrcode-title">扫描二维码分享</div>' . '<img src="%s" alt="微信分享二维码" class="wechat-qrcode">' . '<div class="qrcode-tip">打开微信,扫描二维码分享给好友</div>' . '</div>' . '</div>', esc_url($qrcode_url) ); } public function track_share() { check_ajax_referer('social_share_nonce', 'nonce'); $platform = sanitize_text_field($_POST['platform']); $post_id = intval($_POST['post_id']); $user_ip = $this->get_user_ip(); // 记录分享数据 $share_data = array( 'post_id' => $post_id, 'platform' => $platform, 'user_ip' => $user_ip, 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'timestamp' => current_time('mysql') ); $this->save_share_analytics($share_data); // 更新文章分享计数 $this->update_post_share_count($post_id, $platform); wp_send_json_success(array('message' => '分享已记录')); } private function save_share_analytics($data) { global $wpdb; $table_name = $wpdb->prefix . 'social_share_analytics'; $wpdb->insert($table_name, $data); } private function update_post_share_count($post_id, $platform) { $current_count = get_post_meta($post_id, '_share_count_' . $platform, true); $current_count = $current_count ? intval($current_count) : 0; update_post_meta($post_id, '_share_count_' . $platform, $current_count + 1); // 更新总分享数 $total_count = get_post_meta($post_id, '_total_share_count', true); $total_count = $total_count ? intval($total_count) : 0; update_post_meta($post_id, '_total_share_count', $total_count + 1); } } 4.3 智能内容推荐引擎 class Content_Recommend_Tool { private $recommendation_strategies = array( 'related_by_tags', 'related_by_category', 'popular_posts', 'recently_viewed', 'user_based_collaborative' ); public function init() { add_action('wp_enqueue_scripts', array($this, 'enqueue_assets')); add_filter('the_content', array($this, 'add_recommendations'), 100); add_action('wp_ajax_get_recommendations', array($this, 'ajax_get_recommendations')); add_action('wp_ajax_nopriv_get_recommendations', array($this, 'ajax_get_recommendations')); add_action('wp_footer', array($this, 'track_user_behavior')); } public function get_recommendations($post_id = null, $limit = 6) { if (!$post_id) { global $post; $post_id = $post->ID; } $recommendations = array(); $strategy_weights = $this->get_strategy_weights(); foreach ($this->recommendation_strategies as $strategy) { if ($strategy_weights[$strategy] > 0) { $strategy_recommendations = call_user_func( array($this, 'get_' . $strategy . '_recommendations'), $post_id, ceil($limit * $strategy_weights[$strategy]) ); $recommendations = array_merge($recommendations, $strategy_recommendations); } } // 去重和排序 $recommendations = $this->deduplicate_and_sort($recommendations, $limit); return $recommendations; } private function get_related_by_tags_recommendations($post_id, $limit) { $tags = wp_get_post_tags($post_id, array('fields' => 'ids')); if (empty($tags)) { return array(); } $args = array( 'post_type' => 'post', 'post__not_in' => array($post_id), 'tag__in' => $tags, 'posts_per_page' => $limit, 'orderby' => 'relevance', 'meta_query' => array( array( 'key' => '_thumbnail_id', 'compare' => 'EXISTS' ) ) ); $query = new WP_Query($args); return $this->format_recommendations($query->posts, 'tag_based'); } private function get_popular_posts_recommendations($post_id, $limit) { $args = array( 'post_type' => 'post', 'post__not_in' => array($post_id), 'posts_per_page' => $limit, 'meta_key' => '_total_share_count', 'orderby' => 'meta_value_num', 'order' => 'DESC', 'date_query' => array( array( 'after' => '30 days ago' ) ) ); $query = new WP_Query($args); return $this->format_recommendations($query->posts, 'popular'); } private function get_user_based_collaborative_recommendations($post_id, $limit) { // 基于用户行为的协同过滤推荐 $current_user_id = get_current_user_id(); if (!$current_user_id) { return array(); } // 获取当前用户的阅读历史 $user_history = $this->get_user_read_history($current_user_id); if (empty($user_history)) { return array(); } // 找到有相似阅读历史的用户

发表评论

一步步实现,为网站添加文件上传与云存储管理

一步步实现:为网站添加文件上传与云存储管理,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么网站需要文件上传与云存储管理? 在当今数字化时代,网站的功能性需求日益复杂。无论是企业官网、个人博客,还是电子商务平台,文件上传与管理功能已成为不可或缺的基础需求。用户期望能够轻松上传图片、文档、视频等内容,而网站管理员则需要高效、安全地管理这些文件资源。 传统的WordPress媒体库虽然提供了基础的文件上传功能,但在面对大量文件、大文件上传、多用户协作、跨平台访问等复杂场景时,往往显得力不从心。云存储解决方案的出现,为这一问题提供了完美的答案。通过将文件存储在云端,网站不仅可以减轻服务器负担,还能实现更高的可用性、可扩展性和安全性。 本文将深入探讨如何通过WordPress代码二次开发,为网站添加强大的文件上传与云存储管理功能,并在此基础上实现一系列常用互联网小工具,从而大幅提升网站的功能性和用户体验。 第一部分:WordPress文件上传机制深度解析 1.1 WordPress默认上传系统的工作原理 WordPress内置了一个相对完整的文件上传系统。当用户通过媒体上传界面或相关功能上传文件时,系统会执行以下关键步骤: 文件验证:检查文件类型、大小是否符合系统设置 安全处理:对文件名进行清理,防止安全漏洞 存储处理:将文件保存到wp-content/uploads目录,并按年月组织子目录 数据库记录:在wp_posts表中创建attachment类型的记录 元数据生成:为图片文件生成缩略图,提取EXIF信息等 了解这一基础流程是进行二次开发的前提。我们可以通过分析wp_handle_upload()、media_handle_upload()等核心函数,掌握WordPress处理上传文件的完整机制。 1.2 现有上传系统的局限性 尽管WordPress默认系统能够满足基本需求,但在实际应用中存在明显不足: 存储空间受限:依赖服务器本地存储,空间有限且不易扩展 性能瓶颈:大文件上传和处理可能拖慢网站响应速度 备份困难:文件分散在服务器上,难以实现统一备份和恢复 访问限制:缺乏细粒度的访问控制和权限管理 多站点管理复杂:对于多站点网络,文件管理变得异常复杂 这些局限性正是我们需要引入云存储解决方案的根本原因。 第二部分:云存储集成方案选择与比较 2.1 主流云存储服务概览 目前市场上有多种云存储服务可供选择,每种都有其特点和适用场景: Amazon S3:功能全面,生态系统完善,适合企业级应用 Google Cloud Storage:与Google生态系统深度集成,性能优异 阿里云OSS:国内访问速度快,符合中国法规要求 腾讯云COS:性价比高,与腾讯生态整合良好 Backblaze B2:价格实惠,适合个人和小型企业 Wasabi:无出口费用,适合高频访问场景 2.2 选择云存储服务的关键考量因素 在选择云存储服务时,需要综合考虑以下因素: 成本结构:存储费用、请求费用、流量费用的综合评估 性能表现:上传下载速度、延迟、可用性保证 地理位置:服务器位置对访问速度的影响 集成难度:API的易用性和文档完整性 合规要求:数据主权、隐私保护等法规遵从性 生态系统:与其他服务的集成能力 2.3 WordPress云存储插件评估 在开始自定义开发前,了解现有插件解决方案是必要的: WP Offload Media:功能全面,支持多种云服务 Media Cloud:专注于云存储,提供高级功能 Stateless:与Google Cloud Platform深度集成 Storage for WordPress:轻量级解决方案,易于定制 虽然这些插件提供了现成的解决方案,但通过自定义开发,我们可以实现更贴合特定需求、更高效集成的文件管理系统。 第三部分:构建自定义文件上传与云存储管理系统 3.1 系统架构设计 我们的自定义系统将采用模块化设计,主要包括以下组件: 上传处理模块:负责接收、验证和处理上传请求 云存储适配器:抽象不同云服务的API,提供统一接口 文件管理模块:提供文件的增删改查操作 权限控制系统:管理用户对文件的访问权限 缓存与优化层:提高系统性能和响应速度 管理界面:为管理员和用户提供友好的操作界面 3.2 核心代码实现 3.2.1 创建云存储适配器抽象类 <?php /** * 云存储适配器抽象类 */ abstract class Cloud_Storage_Adapter { protected $config; protected $client; public function __construct($config) { $this->config = $config; $this->initialize_client(); } abstract protected function initialize_client(); abstract public function upload($local_path, $remote_path, $options = []); abstract public function download($remote_path, $local_path); abstract public function delete($remote_path); abstract public function list_files($prefix = '', $options = []); abstract public function get_url($remote_path, $expires = null); abstract public function file_exists($remote_path); } 3.2.2 实现S3适配器 <?php /** * Amazon S3适配器实现 */ class S3_Storage_Adapter extends Cloud_Storage_Adapter { protected function initialize_client() { $this->client = new AwsS3S3Client([ 'version' => 'latest', 'region' => $this->config['region'], 'credentials' => [ 'key' => $this->config['key'], 'secret' => $this->config['secret'], ], ]); } public function upload($local_path, $remote_path, $options = []) { $default_options = [ 'Bucket' => $this->config['bucket'], 'Key' => $remote_path, 'SourceFile' => $local_path, 'ACL' => 'private', ]; $options = array_merge($default_options, $options); try { $result = $this->client->putObject($options); return [ 'success' => true, 'url' => $result['ObjectURL'], 'etag' => $result['ETag'], ]; } catch (AwsS3ExceptionS3Exception $e) { return [ 'success' => false, 'error' => $e->getMessage(), ]; } } // 其他方法实现... } 3.2.3 创建文件上传处理器 <?php /** * 文件上传处理器 */ class File_Upload_Handler { private $adapter; private $allowed_types; private $max_size; public function __construct($adapter) { $this->adapter = $adapter; $this->allowed_types = get_option('allowed_upload_types', []); $this->max_size = get_option('max_upload_size', 10485760); // 默认10MB } public function handle_upload($file, $user_id, $options = []) { // 验证文件 $validation = $this->validate_file($file); if (!$validation['valid']) { return $validation; } // 生成唯一文件名和路径 $file_info = $this->generate_file_info($file, $user_id); // 临时保存文件 $temp_path = $this->save_temp_file($file); // 上传到云存储 $upload_result = $this->adapter->upload( $temp_path, $file_info['remote_path'], $options ); // 清理临时文件 unlink($temp_path); if ($upload_result['success']) { // 保存文件记录到数据库 $file_id = $this->save_file_record($file_info, $user_id); return [ 'success' => true, 'file_id' => $file_id, 'url' => $upload_result['url'], 'file_info' => $file_info, ]; } return [ 'success' => false, 'error' => $upload_result['error'], ]; } private function validate_file($file) { // 检查文件大小 if ($file['size'] > $this->max_size) { return [ 'valid' => false, 'error' => '文件大小超过限制', ]; } // 检查文件类型 $file_type = wp_check_filetype($file['name']); if (!in_array($file_type['type'], $this->allowed_types)) { return [ 'valid' => false, 'error' => '不支持的文件类型', ]; } // 安全检查 if (!wp_verify_nonce($_POST['upload_nonce'], 'file_upload')) { return [ 'valid' => false, 'error' => '安全验证失败', ]; } return ['valid' => true]; } private function generate_file_info($file, $user_id) { $original_name = sanitize_file_name($file['name']); $extension = pathinfo($original_name, PATHINFO_EXTENSION); $unique_name = wp_generate_uuid4() . '.' . $extension; // 按用户和日期组织目录结构 $date = date('Y/m'); $remote_path = "uploads/{$user_id}/{$date}/{$unique_name}"; return [ 'original_name' => $original_name, 'unique_name' => $unique_name, 'remote_path' => $remote_path, 'extension' => $extension, 'size' => $file['size'], ]; } // 其他辅助方法... } 3.3 数据库设计优化 为了高效管理文件元数据,我们需要创建自定义数据库表: CREATE TABLE wp_cloud_files ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT(20) UNSIGNED NOT NULL, original_name VARCHAR(255) NOT NULL, unique_name VARCHAR(255) NOT NULL, remote_path VARCHAR(500) NOT NULL, file_type VARCHAR(100) NOT NULL, file_size BIGINT(20) UNSIGNED NOT NULL, mime_type VARCHAR(100), upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, last_access DATETIME, access_count INT UNSIGNED DEFAULT 0, is_public TINYINT(1) DEFAULT 0, metadata TEXT, PRIMARY KEY (id), INDEX user_index (user_id), INDEX path_index (remote_path(255)), INDEX type_index (file_type), INDEX upload_time_index (upload_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 3.4 管理界面开发 创建用户友好的管理界面是系统成功的关键。我们可以使用WordPress的Admin API创建自定义管理页面: <?php /** * 文件管理界面 */ class File_Management_UI { public function __construct() { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']); } public function add_admin_menu() { add_menu_page( '云文件管理', '云文件', 'upload_files', 'cloud-file-manager', [$this, 'render_main_page'], 'dashicons-cloud', 30 ); add_submenu_page( 'cloud-file-manager', '上传文件', '上传', 'upload_files', 'cloud-file-upload', [$this, 'render_upload_page'] ); add_submenu_page( 'cloud-file-manager', '文件统计', '统计', 'manage_options', 'cloud-file-stats', [$this, 'render_stats_page'] ); } public function render_main_page() { ?> <div class="wrap"> <h1 class="wp-heading-inline">云文件管理</h1> <a href="<?php echo admin_url('admin.php?page=cloud-file-upload'); ?>" class="page-title-action">上传文件</a> <hr class="wp-header-end"> <div id="cloud-file-manager"> <!-- 文件列表将通过Vue.js动态加载 --> <div class="file-manager-container"> <div class="file-toolbar"> <div class="search-box"> <input type="search" id="file-search" placeholder="搜索文件..."> </div> <div class="filter-options"> <select id="file-type-filter"> <option value="">所有类型</option> <option value="image">图片</option> <option value="document">文档</option> <option value="video">视频</option> </select> </div> </div> <div class="file-list-container"> <!-- 文件列表将通过AJAX加载 --> </div> <div class="file-pagination"> <!-- 分页控件 --> </div> </div> </div> </div> <?php } public function enqueue_scripts($hook) { if (strpos($hook, 'cloud-file') === false) { return; } wp_enqueue_style( 'cloud-file-manager', plugins_url('css/file-manager.css', __FILE__), [], '1.0.0' ); wp_enqueue_script( 'cloud-file-manager', plugins_url('js/file-manager.js', __FILE__), ['jquery', 'vue'], '1.0.0', true ); wp_localize_script('cloud-file-manager', 'cloudFileManager', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('cloud_file_manager'), 'strings' => [ 'delete_confirm' => '确定要删除这个文件吗?', 'upload_success' => '文件上传成功', 'upload_failed' => '文件上传失败', ] ]); } } 第四部分:基于文件系统的实用小工具开发 4.1 图片水印添加工具 <?php /** * 图片水印工具 */ class Image_Watermark_Tool { private $adapter; public function __construct($adapter) { $this->adapter = $adapter; add_action('wp_ajax_add_watermark', [$this, 'ajax_add_watermark']); } public function add_watermark($image_path, $watermark_text, $options = []) { // 从云存储下载图片 $temp_path = $this->download_to_temp($image_path); // 获取图片信息 $image_info = getimagesize($temp_path); $image_type = $image_info[2]; // 根据图片类型创建图像资源 switch ($image_type) { case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($temp_path); break; case IMAGETYPE_PNG: $image = imagecreatefrompng($temp_path); break; case IMAGETYPE_GIF: $image = imagecreatefromgif($temp_path); break; default: return false; } // 设置水印颜色和字体 $text_color = imagecolorallocatealpha($image, 255, 255, 255, 60); $font_size = isset($options['font_size']) ? $options['font_size'] : 20; $font_path = isset($options['font_path']) ? $options['font_path'] : ''; // 计算水印位置 $text_box = imagettfbbox($font_size, 0, $font_path, $watermark_text); $text_width = $text_box[2] - $text_box[0]; $text_height = $text_box[7] - $text_box[1]; $x = imagesx($image) - $text_width - 10; $y = imagesy($image) - $text_height - 10; // 添加水印 imagettftext($image, $font_size, 0, $x, $y, $text_color, $font_path, $watermark_text); // 保存处理后的图片 $watermarked_path = $this->get_watermarked_path($temp_path); switch ($image_type) { case IMAGETYPE_JPEG: imagejpeg($image, $watermarked_path, 90); break; case IMAGETYPE_PNG: imagepng($image, $watermarked_path, 9); break; case IMAGETYPE_GIF: imagegif($image, $watermarked_path); break; } imagedestroy($image); // 上传处理后的图片 $new_remote_path = $this->get_watermarked_remote_path($image_path); $result = $this->adapter->upload($watermarked_path, $new_remote_path); // 清理临时文件 unlink($temp_path); unlink($watermarked_path); return $result['success'] ? $new_remote_path : false; } public function ajax_add_watermark() { check_ajax_referer('watermark_tool', 'nonce'); $file_id = intval($_POST['file_id']); $watermark_text = sanitize_text_field($_POST['watermark_text']); // 获取文件信息 $file_info = $this->get_file_info($file_id); if (!$file_info || !$this->is_image($file_info['mime_type'])) { 4.2 文件批量处理工具 <?php /** * 文件批量处理工具 */ class Batch_File_Processor { private $adapter; private $batch_size = 50; // 每批处理文件数量 public function __construct($adapter) { $this->adapter = $adapter; add_action('wp_ajax_batch_process_files', [$this, 'ajax_batch_process']); } public function process_batch($file_ids, $operation, $params = []) { $results = [ 'success' => [], 'failed' => [], 'total' => count($file_ids) ]; // 分批处理,避免内存溢出 $chunks = array_chunk($file_ids, $this->batch_size); foreach ($chunks as $chunk) { foreach ($chunk as $file_id) { try { $result = $this->process_single_file($file_id, $operation, $params); if ($result['success']) { $results['success'][] = [ 'id' => $file_id, 'message' => $result['message'] ]; } else { $results['failed'][] = [ 'id' => $file_id, 'error' => $result['error'] ]; } // 短暂暂停,减轻服务器压力 usleep(100000); // 0.1秒 } catch (Exception $e) { $results['failed'][] = [ 'id' => $file_id, 'error' => $e->getMessage() ]; } } } return $results; } private function process_single_file($file_id, $operation, $params) { global $wpdb; // 获取文件信息 $file = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}cloud_files WHERE id = %d", $file_id )); if (!$file) { return ['success' => false, 'error' => '文件不存在']; } switch ($operation) { case 'compress_images': return $this->compress_image($file, $params); case 'convert_format': return $this->convert_format($file, $params); case 'add_metadata': return $this->add_metadata($file, $params); case 'generate_thumbnails': return $this->generate_thumbnails($file, $params); case 'optimize_for_web': return $this->optimize_for_web($file, $params); default: return ['success' => false, 'error' => '不支持的操作']; } } private function compress_image($file, $params) { if (!$this->is_image($file->mime_type)) { return ['success' => false, 'error' => '不是图片文件']; } // 下载文件到临时目录 $temp_path = $this->download_to_temp($file->remote_path); // 根据图片类型进行压缩 $compressed_path = $this->compress_image_file($temp_path, $params); if (!$compressed_path) { unlink($temp_path); return ['success' => false, 'error' => '压缩失败']; } // 计算压缩率 $original_size = filesize($temp_path); $compressed_size = filesize($compressed_path); $compression_rate = round((1 - $compressed_size / $original_size) * 100, 2); // 上传压缩后的文件 $new_remote_path = $this->get_compressed_path($file->remote_path); $upload_result = $this->adapter->upload($compressed_path, $new_remote_path); // 清理临时文件 unlink($temp_path); unlink($compressed_path); if ($upload_result['success']) { // 更新数据库记录 $this->update_file_record($file->id, [ 'compressed_path' => $new_remote_path, 'original_size' => $original_size, 'compressed_size' => $compressed_size, 'compression_rate' => $compression_rate ]); return [ 'success' => true, 'message' => "压缩成功,压缩率:{$compression_rate}%" ]; } return ['success' => false, 'error' => '上传压缩文件失败']; } private function compress_image_file($image_path, $params) { $quality = isset($params['quality']) ? $params['quality'] : 80; $max_width = isset($params['max_width']) ? $params['max_width'] : 1920; $image_info = getimagesize($image_path); $image_type = $image_info[2]; // 创建图像资源 switch ($image_type) { case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($image_path); break; case IMAGETYPE_PNG: $image = imagecreatefrompng($image_path); // 保留透明度 imagealphablending($image, false); imagesavealpha($image, true); break; default: return false; } // 调整尺寸 $original_width = imagesx($image); $original_height = imagesy($image); if ($original_width > $max_width) { $new_width = $max_width; $new_height = intval($original_height * ($max_width / $original_width)); $resized_image = imagecreatetruecolor($new_width, $new_height); // 处理PNG透明度 if ($image_type == IMAGETYPE_PNG) { imagealphablending($resized_image, false); imagesavealpha($resized_image, true); $transparent = imagecolorallocatealpha($resized_image, 255, 255, 255, 127); imagefilledrectangle($resized_image, 0, 0, $new_width, $new_height, $transparent); } imagecopyresampled( $resized_image, $image, 0, 0, 0, 0, $new_width, $new_height, $original_width, $original_height ); imagedestroy($image); $image = $resized_image; } // 保存压缩后的图片 $compressed_path = $this->get_temp_file_path('compressed_'); switch ($image_type) { case IMAGETYPE_JPEG: imagejpeg($image, $compressed_path, $quality); break; case IMAGETYPE_PNG: // PNG质量参数是0-9,与JPEG相反 $png_quality = 9 - round(($quality / 100) * 9); imagepng($image, $compressed_path, $png_quality); break; } imagedestroy($image); return $compressed_path; } public function ajax_batch_process() { check_ajax_referer('batch_processor', 'nonce'); if (!current_user_can('upload_files')) { wp_send_json_error('权限不足'); } $file_ids = array_map('intval', $_POST['file_ids']); $operation = sanitize_text_field($_POST['operation']); $params = isset($_POST['params']) ? $_POST['params'] : []; // 异步处理 if (isset($_POST['async']) && $_POST['async']) { $this->start_async_batch_process($file_ids, $operation, $params); wp_send_json_success(['message' => '批量处理已开始']); } else { $results = $this->process_batch($file_ids, $operation, $params); wp_send_json_success($results); } } private function start_async_batch_process($file_ids, $operation, $params) { // 创建后台任务 $task_id = wp_generate_uuid4(); $task_data = [ 'file_ids' => $file_ids, 'operation' => $operation, 'params' => $params, 'status' => 'pending', 'progress' => 0, 'created_at' => current_time('mysql'), 'created_by' => get_current_user_id() ]; // 保存任务到数据库 $this->save_batch_task($task_id, $task_data); // 触发后台处理 wp_schedule_single_event(time() + 5, 'process_batch_task', [$task_id]); return $task_id; } } 4.3 智能文件分类器 <?php /** * 智能文件分类器 */ class Smart_File_Classifier { private $adapter; private $categories = [ 'images' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], 'documents' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'], 'videos' => ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'], 'audio' => ['mp3', 'wav', 'ogg', 'm4a'], 'archives' => ['zip', 'rar', '7z', 'tar', 'gz'], 'code' => ['php', 'js', 'css', 'html', 'py', 'java', 'cpp'] ]; public function __construct($adapter) { $this->adapter = $adapter; add_action('wp_ajax_classify_files', [$this, 'ajax_classify_files']); add_action('add_attachment', [$this, 'auto_classify_new_file']); } public function classify_file($file_path, $file_name) { $extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); // 根据扩展名分类 foreach ($this->categories as $category => $extensions) { if (in_array($extension, $extensions)) { return $category; } } // 使用MIME类型进一步分类 $mime_type = $this->get_mime_type($file_path); if ($mime_type) { return $this->classify_by_mime_type($mime_type); } return 'other'; } private function classify_by_mime_type($mime_type) { $mime_categories = [ 'image/' => 'images', 'application/pdf' => 'documents', 'application/msword' => 'documents', 'application/vnd.openxmlformats-officedocument' => 'documents', 'video/' => 'videos', 'audio/' => 'audio', 'application/zip' => 'archives', 'application/x-rar-compressed' => 'archives', 'text/' => 'documents' ]; foreach ($mime_categories as $prefix => $category) { if (strpos($mime_type, $prefix) === 0) { return $category; } } return 'other'; } public function auto_classify_new_file($attachment_id) { $file_path = get_attached_file($attachment_id); $file_name = basename($file_path); $category = $this->classify_file($file_path, $file_name); // 保存分类信息 update_post_meta($attachment_id, '_file_category', $category); // 如果是图片,提取更多信息 if ($category === 'images') { $this->extract_image_metadata($attachment_id, $file_path); } } private function extract_image_metadata($attachment_id, $file_path) { $metadata = []; // 获取EXIF数据 if (function_exists('exif_read_data') && in_array(strtolower(pathinfo($file_path, PATHINFO_EXTENSION)), ['jpg', 'jpeg'])) { $exif = @exif_read_data($file_path); if ($exif) { if (isset($exif['DateTimeOriginal'])) { $metadata['taken_date'] = $exif['DateTimeOriginal']; } if (isset($exif['GPSLatitude']) && isset($exif['GPSLongitude'])) { $metadata['gps'] = $this->convert_gps($exif['GPSLatitude'], $exif['GPSLongitude']); } if (isset($exif['Make'])) { $metadata['camera_make'] = $exif['Make']; } if (isset($exif['Model'])) { $metadata['camera_model'] = $exif['Model']; } } } // 获取图片尺寸 $image_size = getimagesize($file_path); if ($image_size) { $metadata['dimensions'] = [ 'width' => $image_size[0], 'height' => $image_size[1] ]; $metadata['mime_type'] = $image_size['mime']; } // 计算文件哈希 $metadata['file_hash'] = md5_file($file_path); // 保存元数据 update_post_meta($attachment_id, '_image_metadata', $metadata); } private function convert_gps($gps_lat, $gps_lon) { // 将GPS坐标转换为十进制 $lat_degrees = count($gps_lat) > 0 ? $this->gps_to_degrees($gps_lat) : 0; $lon_degrees = count($gps_lon) > 0 ? $this->gps_to_degrees($gps_lon) : 0; // 确定半球 $lat_direction = ($gps_lat['GPSLatitudeRef'] == 'S') ? -1 : 1; $lon_direction = ($gps_lon['GPSLongitudeRef'] == 'W') ? -1 : 1; return [ 'lat' => $lat_degrees * $lat_direction, 'lon' => $lon_degrees * $lon_direction ]; } private function gps_to_degrees($gps_coordinate) { $degrees = count($gps_coordinate) > 0 ? $this->gps_coordinate_to_number($gps_coordinate[0]) : 0; $minutes = count($gps_coordinate) > 1 ? $this->gps_coordinate_to_number($gps_coordinate[1]) : 0; $seconds = count($gps_coordinate) > 2 ? $this->gps_coordinate_to_number($gps_coordinate[2]) : 0; return $degrees + ($minutes / 60) + ($seconds / 3600); } private function gps_coordinate_to_number($coordinate_part) { $parts = explode('/', $coordinate_part); if (count($parts) <= 0) { return 0; } if (count($parts) == 1) { return $parts[0]; } return floatval($parts[0]) / floatval($parts[1]); } public function ajax_classify_files() { check_ajax_referer('file_classifier', 'nonce'); $file_ids = array_map('intval', $_POST['file_ids']); $results = []; foreach ($file_ids as $file_id) { $file = get_post($file_id); if (!$file || $file->post_type != 'attachment') { continue; } $file_path = get_attached_file($file_id); $category = $this->classify_file($file_path, $file->post_title); // 更新分类 update_post_meta($file_id, '_file_category', $category); $results[] = [ 'id' => $file_id, 'name' => $file->post_title, 'category' => $category, 'icon' => $this->get_category_icon($category) ]; } wp_send_json_success([ 'results' => $results, 'total' => count($results) ]); } private function get_category_icon($category) { $icons = [ 'images' => 'dashicons-format-image', 'documents' => 'dashicons-media-document', 'videos' => 'dashicons-format-video', 'audio' => 'dashicons-format-audio', 'archives' => 'dashicons-media-archive', 'code' => 'dashicons-editor-code', 'other' => 'dashicons-media-default' ]; return isset($icons[$category]) ? $icons[$category] : $icons['other']; } } 4.4 文件分享与协作工具 <?php /** * 文件分享与协作工具 */ class File_Sharing_Tool { private $adapter; private $share_expiry_days = 7; public function __construct($adapter) { $this->adapter = $adapter; add_action('wp_ajax_create_file_share', [$this, 'ajax_create_share']); add_action('wp_ajax_revoke_file_share', [$this, 'ajax_revoke_share']); add_action('wp', [$this, 'handle_shared_file_access']); } public function create_share_link($file_id, $options = []) { global $wpdb; // 验证文件存在且用户有权限 $file = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}cloud_files WHERE id = %d", $file_id )); if (!$file) {

发表评论

实战教程,在WordPress中集成天气与地图显示插件

实战教程:在WordPress中集成天气与地图显示插件 引言:为什么WordPress需要集成天气与地图功能? 在当今数字化时代,网站的功能丰富性直接关系到用户体验和网站价值。对于许多类型的WordPress网站来说,集成天气和地图功能可以显著提升实用性和用户粘性。旅游博客需要展示目的地天气,本地商家网站需要显示店铺位置,活动策划网站需要提供场地地图和天气信息——这些场景都说明了集成这些功能的必要性。 传统的解决方案是使用第三方小工具或iframe嵌入,但这些方法往往存在加载速度慢、样式不统一、功能受限等问题。通过WordPress代码二次开发实现这些功能,不仅可以获得更好的性能和控制权,还能确保与网站主题完美融合,提供一致的用户体验。 本教程将引导您完成在WordPress中集成天气与地图显示功能的完整过程,从API选择到代码实现,再到前端展示,为您提供一个完整的解决方案。 第一部分:准备工作与环境配置 1.1 选择合适的天气和地图API 在开始编码之前,我们需要选择合适的数据源。对于天气数据,有几个流行的API可供选择: OpenWeatherMap:提供免费层级的API调用,包含当前天气、预报和历史数据 WeatherAPI:简单易用,免费套餐包含300万次调用/月 AccuWeather:数据准确但免费层级限制较多 对于地图功能,主流选择包括: Google Maps API:功能强大但需要绑定信用卡(有免费额度) Mapbox:提供美观的地图样式和灵活的定制选项 Leaflet + OpenStreetMap:完全开源免费的解决方案 考虑到成本和易用性,本教程将使用: 天气数据:OpenWeatherMap API(免费版) 地图显示:Leaflet + OpenStreetMap(完全免费) 1.2 注册API密钥 OpenWeatherMap注册步骤: 访问 OpenWeatherMap官网 点击"Sign Up"创建账户 登录后进入API Keys页面 生成新的API密钥并保存 Leaflet无需API密钥,可以直接使用。 1.3 WordPress开发环境准备 确保您具备以下环境: 本地或线上的WordPress安装(建议5.0以上版本) 代码编辑器(VS Code、Sublime Text等) FTP客户端或文件管理器(用于上传代码) 基本的PHP、JavaScript和HTML/CSS知识 1.4 创建自定义插件目录 为了避免主题更新导致代码丢失,我们将创建一个独立插件: 在WordPress的wp-content/plugins/目录下创建新文件夹weather-map-integration 在该文件夹中创建主插件文件weather-map-integration.php 第二部分:创建基础插件结构 2.1 插件头部信息 打开weather-map-integration.php,添加以下插件声明: <?php /** * Plugin Name: Weather & Map Integration * Plugin URI: https://yourwebsite.com/ * Description: 在WordPress中集成天气与地图显示功能 * Version: 1.0.0 * Author: 您的名称 * Author URI: https://yourwebsite.com/ * License: GPL v2 or later * Text Domain: weather-map-integration */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } 2.2 定义插件常量 在插件头部信息后添加以下常量定义: // 定义插件路径和URL常量 define('WMI_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WMI_PLUGIN_URL', plugin_dir_url(__FILE__)); // 定义API密钥常量(在实际使用中应从设置页面获取) define('WMI_OPENWEATHER_API_KEY', 'your_openweather_api_key_here'); // 插件版本 define('WMI_VERSION', '1.0.0'); 2.3 创建插件主类 使用面向对象的方式组织插件代码: class Weather_Map_Integration { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 初始化钩子 add_action('init', array($this, 'init')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_init', array($this, 'register_settings')); add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); // 注册短代码 add_shortcode('weather_display', array($this, 'weather_shortcode')); add_shortcode('map_display', array($this, 'map_shortcode')); add_shortcode('weather_map_combo', array($this, 'weather_map_combo_shortcode')); } public function init() { // 初始化代码 load_plugin_textdomain('weather-map-integration', false, dirname(plugin_basename(__FILE__)) . '/languages'); } // 其他方法将在后续部分添加 } // 初始化插件 Weather_Map_Integration::get_instance(); 第三部分:创建管理设置页面 3.1 添加管理菜单 在插件类中添加以下方法: public function add_admin_menu() { add_menu_page( '天气与地图设置', '天气地图', 'manage_options', 'weather-map-settings', array($this, 'settings_page'), 'dashicons-location-alt', 80 ); add_submenu_page( 'weather-map-settings', 'API设置', 'API设置', 'manage_options', 'weather-map-api-settings', array($this, 'api_settings_page') ); add_submenu_page( 'weather-map-settings', '显示设置', '显示设置', 'manage_options', 'weather-map-display-settings', array($this, 'display_settings_page') ); } 3.2 注册设置选项 public function register_settings() { // API设置 register_setting('wmi_api_settings', 'wmi_openweather_api_key'); register_setting('wmi_api_settings', 'wmi_default_city'); register_setting('wmi_api_settings', 'wmi_default_country'); // 显示设置 register_setting('wmi_display_settings', 'wmi_temperature_unit'); register_setting('wmi_display_settings', 'wmi_map_height'); register_setting('wmi_display_settings', 'wmi_map_width'); register_setting('wmi_display_settings', 'wmi_default_zoom'); register_setting('wmi_display_settings', 'wmi_show_attribution'); // API设置部分 add_settings_section( 'wmi_api_section', 'API配置', array($this, 'api_section_callback'), 'weather-map-api-settings' ); add_settings_field( 'wmi_openweather_api_key', 'OpenWeatherMap API密钥', array($this, 'api_key_field_callback'), 'weather-map-api-settings', 'wmi_api_section' ); add_settings_field( 'wmi_default_city', '默认城市', array($this, 'default_city_field_callback'), 'weather-map-api-settings', 'wmi_api_section' ); // 显示设置部分 add_settings_section( 'wmi_display_section', '显示设置', array($this, 'display_section_callback'), 'weather-map-display-settings' ); add_settings_field( 'wmi_temperature_unit', '温度单位', array($this, 'temperature_unit_field_callback'), 'weather-map-display-settings', 'wmi_display_section' ); // 更多设置字段... } public function api_section_callback() { echo '<p>配置天气和地图API的相关设置。请确保您已注册相应的API服务。</p>'; } public function api_key_field_callback() { $api_key = get_option('wmi_openweather_api_key', ''); echo '<input type="text" id="wmi_openweather_api_key" name="wmi_openweather_api_key" value="' . esc_attr($api_key) . '" class="regular-text" />'; echo '<p class="description">请输入您的OpenWeatherMap API密钥。如果没有,请访问<a href="https://openweathermap.org/api" target="_blank">OpenWeatherMap官网</a>注册获取。</p>'; } // 其他字段回调函数... 3.3 创建设置页面模板 public function api_settings_page() { if (!current_user_can('manage_options')) { return; } ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <form action="options.php" method="post"> <?php settings_fields('wmi_api_settings'); do_settings_sections('weather-map-api-settings'); submit_button('保存设置'); ?> </form> </div> <?php } public function display_settings_page() { if (!current_user_can('manage_options')) { return; } ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <form action="options.php" method="post"> <?php settings_fields('wmi_display_settings'); do_settings_sections('weather-map-display-settings'); submit_button('保存设置'); ?> </form> </div> <?php } 第四部分:天气功能实现 4.1 创建天气数据获取类 在插件目录下创建includes/class-weather-data.php: <?php if (!defined('ABSPATH')) { exit; } class WMI_Weather_Data { private $api_key; private $base_url = 'https://api.openweathermap.org/data/2.5/'; public function __construct($api_key = '') { $this->api_key = $api_key ?: get_option('wmi_openweather_api_key', ''); } /** * 获取当前天气数据 */ public function get_current_weather($city, $country = '', $units = 'metric') { $location = $city; if (!empty($country)) { $location .= ',' . $country; } $transient_key = 'wmi_current_weather_' . md5($location . $units); $cached_data = get_transient($transient_key); if ($cached_data !== false) { return $cached_data; } $url = $this->base_url . 'weather'; $args = array( 'q' => $location, 'appid' => $this->api_key, 'units' => $units, 'lang' => $this->get_language_code() ); $response = wp_remote_get(add_query_arg($args, $url)); if (is_wp_error($response)) { return array('error' => $response->get_error_message()); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['cod']) && $data['cod'] != 200) { return array('error' => $data['message'] ?? '未知错误'); } // 缓存数据10分钟 set_transient($transient_key, $data, 10 * MINUTE_IN_SECONDS); return $data; } /** * 获取天气预报数据 */ public function get_forecast($city, $country = '', $units = 'metric', $days = 5) { $location = $city; if (!empty($country)) { $location .= ',' . $country; } $transient_key = 'wmi_forecast_' . md5($location . $units . $days); $cached_data = get_transient($transient_key); if ($cached_data !== false) { return $cached_data; } $url = $this->base_url . 'forecast'; $args = array( 'q' => $location, 'appid' => $this->api_key, 'units' => $units, 'cnt' => $days * 8, // 每3小时一个数据点 'lang' => $this->get_language_code() ); $response = wp_remote_get(add_query_arg($args, $url)); if (is_wp_error($response)) { return array('error' => $response->get_error_message()); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['cod']) && $data['cod'] != 200) { return array('error' => $data['message'] ?? '未知错误'); } // 缓存数据30分钟 set_transient($transient_key, $data, 30 * MINUTE_IN_SECONDS); return $data; } /** * 根据IP获取位置信息 */ public function get_location_by_ip() { $transient_key = 'wmi_location_ip_' . $_SERVER['REMOTE_ADDR']; $cached_data = get_transient($transient_key); if ($cached_data !== false) { return $cached_data; } // 使用ipapi.co服务(免费) $response = wp_remote_get('https://ipapi.co/json/'); if (is_wp_error($response)) { return array('city' => 'Beijing', 'country' => 'CN'); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); $location = array( 'city' => $data['city'] ?? 'Beijing', 'country' => $data['country'] ?? 'CN' ); // 缓存24小时 set_transient($transient_key, $location, 24 * HOUR_IN_SECONDS); return $location; } /** * 获取语言代码 */ private function get_language_code() { $locale = get_locale(); $lang_map = array( 'zh_CN' => 'zh_cn', 'zh_TW' => 'zh_tw', 'en_US' => 'en', 'en_GB' => 'en', 'es_ES' => 'es', 'fr_FR' => 'fr', 'de_DE' => 'de', 'ja' => 'ja', 'ko_KR' => 'kr' ); return $lang_map[$locale] ?? 'en'; } /** * 处理天气数据为前端可用格式 */ public function process_weather_data($weather_data) { if (isset($weather_data['error'])) { return $weather_data; } $processed = array( 'location' => $weather_data['name'] . ', ' . ($weather_data['sys']['country'] ?? ''), 'temperature' => round($weather_data['main']['temp']), 'feels_like' => round($weather_data['main']['feels_like']), 'description' => $weather_data['weather'][0]['description'], 'icon' => $weather_data['weather'][0]['icon'], 'humidity' => $weather_data['main']['humidity'], 'pressure' => $weather_data['main']['pressure'], 'wind_speed' => $weather_data['wind']['speed'], 'wind_deg' => $weather_data['wind']['deg'], 'sunrise' => date('H:i', $weather_data['sys']['sunrise']), 'sunset' => date('H:i', $weather_data['sys']['sunset']), 'timestamp' => time() ); return $processed; } } 4.2 创建天气显示短代码 在主插件类中添加天气短代码方法: public function weather_shortcode($atts) { $atts = shortcode_atts(array( 'city' => '', 'country' => '', 'units' => get_option('wmi_temperature_unit', 'metric'), 'show_forecast' => 'false', 'forecast_days' => 3, 'layout' => 'compact', // compact, detailed, card 'title' => '当前天气' ), $atts, 'weather_display'); // 如果没有指定城市,尝试根据IP获取 if (empty($atts['city'])) { $weather_data = new WMI_Weather_Data(); $location = $weather_data->get_location_by_ip(); $atts['city'] = $location['city']; $atts['country'] = $location['country']; } // 获取天气数据 $weather_api = new WMI_Weather_Data(); $current_weather = $weather_api->get_current_weather($atts['city'], $atts['country'], $atts['units']); if (isset($current_weather['error'])) { return '<div class="wmi-error">无法获取天气数据: ' . esc_html($current_weather['error']) . '</div>'; } $processed_weather = $weather_api->process_weather_data($current_weather); // 获取天气预报(如果需要) $forecast_data = array(); if ($atts['show_forecast'] === 'true') { $forecast = $weather_api->get_forecast($atts['city'], $atts['country'], $atts['units'], $atts['forecast_days']); if (!isset($forecast['error'])) { $forecast_data = $this->process_forecast_data($forecast); } } // 生成输出HTML ob_start(); ?> <div class="wmi-weather-container wmi-layout-<?php echo esc_attr($atts['layout']); ?>" data-city="<?php echo esc_attr($atts['city']); ?>" data-country="<?php echo esc_attr($atts['country']); ?>" data-units="<?php echo esc_attr($atts['units']); ?>"> <div class="wmi-weather-header"> <h3 class="wmi-weather-title"><?php echo esc_html($atts['title']); ?></h3> <div class="wmi-location"><?php echo esc_html($processed_weather['location']); ?></div> </div> <div class="wmi-current-weather"> <div class="wmi-weather-main"> <div class="wmi-temperature"> <span class="wmi-temp-value"><?php echo esc_html($processed_weather['temperature']); ?></span> <span class="wmi-temp-unit">°<?php echo $atts['units'] === 'metric' ? 'C' : 'F'; ?></span> </div> <div class="wmi-weather-icon"> <img src="https://openweathermap.org/img/wn/<?php echo esc_attr($processed_weather['icon']); ?>@2x.png" alt="<?php echo esc_attr($processed_weather['description']); ?>"> </div> </div> <div class="wmi-weather-details"> <div class="wmi-weather-desc"><?php echo esc_html($processed_weather['description']); ?></div> <div class="wmi-weather-meta"> <span class="wmi-feels-like">体感温度: <?php echo esc_html($processed_weather['feels_like']); ?>°</span> <span class="wmi-humidity">湿度: <?php echo esc_html($processed_weather['humidity']); ?>%</span> <span class="wmi-wind">风速: <?php echo esc_html($processed_weather['wind_speed']); ?> m/s</span> </div> </div> </div> <?php if (!empty($forecast_data)): ?> <div class="wmi-forecast"> <h4 class="wmi-forecast-title"><?php echo esc_html($atts['forecast_days']); ?>天预报</h4> <div class="wmi-forecast-days"> <?php foreach ($forecast_data as $day): ?> <div class="wmi-forecast-day"> <div class="wmi-forecast-date"><?php echo esc_html($day['date']); ?></div> <div class="wmi-forecast-icon"> <img src="https://openweathermap.org/img/wn/<?php echo esc_attr($day['icon']); ?>.png" alt="<?php echo esc_attr($day['description']); ?>"> </div> <div class="wmi-forecast-temp"> <span class="wmi-forecast-temp-max"><?php echo esc_html($day['temp_max']); ?>°</span> <span class="wmi-forecast-temp-min"><?php echo esc_html($day['temp_min']); ?>°</span> </div> </div> <?php endforeach; ?> </div> </div> <?php endif; ?> <div class="wmi-weather-footer"> <div class="wmi-sun-times"> <span class="wmi-sunrise">日出: <?php echo esc_html($processed_weather['sunrise']); ?></span> <span class="wmi-sunset">日落: <?php echo esc_html($processed_weather['sunset']); ?></span> </div> <div class="wmi-update-time"> 更新时间: <?php echo date('H:i', $processed_weather['timestamp']); ?> </div> </div> </div> <?php return ob_get_clean(); } private function process_forecast_data($forecast_data) { $daily_data = array(); $grouped_by_day = array(); // 按日期分组 foreach ($forecast_data['list'] as $item) { $date = date('Y-m-d', $item['dt']); if (!isset($grouped_by_day[$date])) { $grouped_by_day[$date] = array(); } $grouped_by_day[$date][] = $item; } // 处理每天的数据 $count = 0; foreach ($grouped_by_day as $date => $day_items) { if ($count >= 5) break; // 最多显示5天 $temps = array(); $icons = array(); $descriptions = array(); foreach ($day_items as $item) { $temps[] = $item['main']['temp']; $icons[] = $item['weather'][0]['icon']; $descriptions[] = $item['weather'][0]['description']; } // 获取最常见的图标和描述 $icon_counts = array_count_values($icons); arsort($icon_counts); $most_common_icon = key($icon_counts); $desc_counts = array_count_values($descriptions); arsort($desc_counts); $most_common_desc = key($desc_counts); $daily_data[] = array( 'date' => date('m/d', strtotime($date)), 'day' => date('D', strtotime($date)), 'temp_max' => round(max($temps)), 'temp_min' => round(min($temps)), 'icon' => $most_common_icon, 'description' => $most_common_desc ); $count++; } return $daily_data; } ## 第五部分:地图功能实现 ### 5.1 创建地图数据类 在插件目录下创建`includes/class-map-data.php`: <?phpif (!defined('ABSPATH')) { exit; } class WMI_Map_Data { /** * 获取位置坐标 */ public function get_coordinates($location) { $transient_key = 'wmi_coordinates_' . md5($location); $cached_data = get_transient($transient_key); if ($cached_data !== false) { return $cached_data; } // 使用Nominatim(OpenStreetMap的搜索服务) $url = 'https://nominatim.openstreetmap.org/search'; $args = array( 'q' => $location, 'format' => 'json', 'limit' => 1, 'addressdetails' => 1 ); $response = wp_remote_get(add_query_arg($args, $url), array( 'headers' => array( 'User-Agent' => 'WordPress Weather Map Integration/1.0' ) )); if (is_wp_error($response)) { return array('error' => $response->get_error_message()); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (empty($data)) { return array('error' => '未找到位置信息'); } $coordinates = array( 'lat' => $data[0]['lat'], 'lon' => $data[0]['lon'], 'display_name' => $data[0]['display_name'], 'address' => $data[0]['address'] ); // 缓存30天 set_transient($transient_key, $coordinates, 30 * DAY_IN_SECONDS); return $coordinates; } /** * 获取多个标记点 */ public function get_multiple_markers($locations) { $markers = array(); foreach ($locations as $location) { if (is_array($location) && isset($location['lat'], $location['lng'])) { $markers[] = array( 'lat' => $location['lat'], 'lng' => $location['lng'], 'title' => $location['title'] ?? '', 'description' => $location['description'] ?? '' ); } else { $coords = $this->get_coordinates($location); if (!isset($coords['error'])) { $markers[] = array( 'lat' => $coords['lat'], 'lng' => $coords['lon'], 'title' => is_array($location) ? ($location['title'] ?? '') : $location, 'description' => $coords['display_name'] ); } } } return $markers; } /** * 计算地图边界 */ public function calculate_bounds($markers) { if (empty($markers)) { return array( 'south' => 0, 'west' => 0, 'north' => 0, 'east' => 0 ); } $lats = array_column($markers, 'lat'); $lngs = array_column($markers, 'lng'); return array( 'south' => min($lats), 'west' => min($lngs), 'north' => max($lats), 'east' => max($lngs) ); } } ### 5.2 创建地图显示短代码 在主插件类中添加地图短代码方法: public function map_shortcode($atts) { $atts = shortcode_atts(array( 'location' => '', 'lat' => '', 'lng' => '', 'zoom' => get_option('wmi_default_zoom', 13), 'height' => get_option('wmi_map_height', '400px'), 'width' => get_option('wmi_map_width', '100%'), 'markers' => '', // JSON格式或逗号分隔 'show_search' => 'false', 'show_controls' => 'true', 'map_style' => 'streets', // streets, satellite, terrain 'title' => '位置地图' ), $atts, 'map_display'); // 生成唯一ID $map_id = 'wmi-map-' . uniqid(); // 处理位置数据 $locations = array(); if (!empty($atts['markers'])) { // 尝试解析JSON $markers_json = json_decode($atts['markers'], true); if (json_last_error() === JSON_ERROR_NONE) { $locations = $markers_json; } else { // 逗号分隔的字符串 $marker_list = explode(',', $atts['markers']); foreach ($marker_list as $marker) { $locations[] = trim($marker); } } } elseif (!empty($atts['location'])) { $locations[] = $atts['location']; } elseif (!empty($atts['lat']) && !empty($atts['lng'])) { $locations[] = array( 'lat' => $atts['lat'], 'lng' => $atts['lng'], 'title' => $atts['title'] ); } else { // 默认位置 $weather_data = new WMI_Weather_Data(); $default_location = $weather_data->get_location_by_ip(); $locations[] = $default_location['city'] . ', ' . $default_location['country']; } // 获取坐标 $map_api = new WMI_Map_Data(); $markers = $map_api->get_multiple_markers($locations); // 计算地图边界 $bounds = $map_api->calculate_bounds($markers); ob_start(); ?> <div class="wmi-map-container" id="<?php echo esc_attr($map_id); ?>-container"> <?php if (!empty($atts['title'])): ?> <h3 class="wmi-map-title"><?php echo esc_html($atts['title']); ?></h3> <?php endif; ?> <?php if ($atts['show_search'] === 'true'): ?> <div class="wmi-map-search"> <input type="text" id="<?php echo esc_attr($map_id); ?>-search" placeholder="搜索地点..." class="wmi-map-search-input"> <button type="button" class="wmi-map-search-button">搜索</button> </div> <?php endif; ?> <div class="wmi-map" id="<?php echo esc_attr($map_id); ?>" style="height: <?php echo esc_attr($atts['height']); ?>; width: <?php echo esc_attr($atts['width']); ?>;"> </div> <div class="wmi-map-info"> <?php if (!empty($markers) && count($markers) === 1): ?> <div class="wmi-location-info"> <strong><?php echo esc_html($markers[0]['title'] ?: '位置'); ?>:</strong> <span><?php echo esc_html($markers[0]['description']); ?></span> </div> <?php endif; ?> </div> </div> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { if (typeof L !== 'undefined') { initWmiMap('<?php echo esc_js($map_id); ?>', { markers: <?php echo json_encode($markers); ?>, bounds: <?php echo json_encode($bounds); ?>, zoom: <?php echo intval($atts['zoom']); ?>, showControls: <?php echo $atts['show_controls'] === 'true' ? 'true' : 'false'; ?>, mapStyle: '<?php echo esc_js($atts['map_style']); ?>' }); } }); </script> <?php return ob_get_clean(); } ### 5.3 创建组合短代码 public function weather_map_combo_shortcode($atts) { $atts = shortcode_atts(array( 'location' => '', 'city' => '', 'country' => '', 'layout' => 'side-by-side', // side-by-side, map-above, weather-above 'height' => '500px', 'show_forecast' => 'true', 'show_search' => 'true', 'title' => '天气与位置' ), $atts, 'weather_map_combo'); // 确定位置 if (!empty($atts['location'])) { $location_parts = explode(',', $atts['location']); $atts['city'] = trim($location_parts[0]); if (isset($location_parts[1])) { $atts['country'] = trim($location_parts[1]); } } ob_start(); ?> <div class="wmi-combo-container wmi-layout-<?php echo esc_attr($atts['layout']); ?>"> <h2 class="wmi-combo-title"><?php echo esc_html($atts['title']); ?></h2> <div class="wmi-combo-content"> <div class="wmi-combo-weather"> <?php echo $this->weather_shortcode(array( 'city' => $atts['city'], 'country' => $atts['country'], 'show_forecast' => $atts['show_forecast'], 'layout' => 'detailed', 'title' => '' )); ?> </div> <div class="wmi-combo-map"> <?php $location_str = !empty($atts['country']) ? $atts['city'] . ', ' . $atts['country'] : $atts['city']; echo $this->map_shortcode(array( 'location' => $location_str, 'height' => $atts['height'], 'show_search' => $atts['show_search'], 'title' => '' )); ?> </div> </div> </div> <?php return ob_get_clean(); } ## 第六部分:前端资源加载与样式 ### 6.1 注册和加载脚本样式 在主插件类中添加以下方法: public function enqueue_frontend_scripts() { // 加载Leaflet地图库 wp_enqueue_style('leaflet-css', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.css', array(), '1.7.1'); wp_enqueue_script('leaflet-js', 'https://unpkg.com/leaflet@1.7.1/dist/leaflet.js', array(), '1.7.1', true); // 加载插件样式 wp_enqueue_style('wmi-frontend-style', WMI_PLUGIN_URL . 'assets/css/frontend.css', array(), WMI_VERSION); // 加载插件脚本 wp_enqueue_script('wmi-frontend-script', WMI_PLUGIN_URL . 'assets/js/frontend.js', array('jquery', 'leaflet-js'), WMI_VERSION, true); // 本地化脚本 wp_localize_script('wmi-frontend-script', 'wmi_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('wmi_ajax_nonce'), 'default_location' => $this->get_default_location(), 'strings' => array( 'loading' => __('加载中...', 'weather-map-integration'), 'error' => __('发生错误', 'weather-map-integration'), 'search_placeholder' => __('输入地点

发表评论

开发指南,为网站创建自定义在线问卷调查工具

开发指南:为WordPress网站创建自定义在线问卷调查工具 引言:为什么需要自定义问卷调查工具 在当今数据驱动的互联网时代,问卷调查已成为网站收集用户反馈、进行市场研究、评估客户满意度的重要工具。虽然WordPress生态中有许多现成的问卷调查插件,如WPForms、Gravity Forms等,但这些通用解决方案往往无法完全满足特定业务需求。自定义问卷调查工具能够提供更精准的数据收集、更符合品牌形象的用户界面以及更灵活的集成能力。 通过WordPress代码二次开发实现自定义问卷调查工具,不仅可以节省长期使用付费插件的成本,还能确保工具完全按照您的业务流程和需求定制。本指南将详细介绍如何从零开始,为WordPress网站开发一个功能完善的自定义在线问卷调查工具。 第一章:开发前的准备工作 1.1 环境搭建与工具选择 在开始开发之前,需要确保具备以下环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel搭建本地WordPress环境 代码编辑器:Visual Studio Code、PHPStorm或Sublime Text 版本控制:Git用于代码版本管理 浏览器开发者工具:用于调试前端代码 1.2 需求分析与功能规划 在编写代码之前,明确问卷调查工具的核心需求: 问卷创建与管理:后台界面用于创建、编辑和删除问卷 问题类型支持:单选题、多选题、文本题、评分题等 响应收集与存储:安全地存储用户提交的问卷数据 数据分析与导出:后台查看统计结果并导出数据 前端展示与交互:用户友好的问卷填写界面 权限控制:限制问卷访问权限(公开/私有) 1.3 数据库设计 设计合理的数据库结构是开发成功的关键。我们需要创建以下数据表: surveys表:存储问卷基本信息 id (主键) title (问卷标题) description (问卷描述) status (状态:草稿/发布/归档) created_at (创建时间) updated_at (更新时间) questions表:存储问题信息 id (主键) survey_id (关联问卷ID) question_text (问题文本) question_type (问题类型:单选/多选/文本等) options (选项,JSON格式存储) required (是否必答) sort_order (排序) responses表:存储用户回答 id (主键) survey_id (问卷ID) question_id (问题ID) answer (用户答案) user_id (用户ID,匿名则为空) submitted_at (提交时间) session_id (会话ID,用于匿名用户跟踪) 第二章:创建基础插件结构 2.1 初始化插件文件 在WordPress的wp-content/plugins/目录下创建新文件夹custom-survey-tool,并创建以下基础文件: custom-survey-tool/ ├── custom-survey-tool.php # 主插件文件 ├── includes/ │ ├── class-database.php # 数据库处理类 │ ├── class-survey.php # 问卷核心类 │ ├── class-admin.php # 后台管理类 │ └── class-frontend.php # 前端展示类 ├── admin/ │ ├── css/ │ │ └── admin-style.css # 后台样式 │ └── js/ │ └── admin-script.js # 后台脚本 ├── public/ │ ├── css/ │ │ └── public-style.css # 前端样式 │ └── js/ │ └── public-script.js # 前端脚本 ├── templates/ # 模板文件 └── assets/ # 静态资源 2.2 主插件文件配置 编辑custom-survey-tool.php文件,添加插件基本信息: <?php /** * Plugin Name: 自定义问卷调查工具 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站创建自定义在线问卷调查工具 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: custom-survey-tool */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CST_VERSION', '1.0.0'); define('CST_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CST_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class_name) { $prefix = 'CST_'; $base_dir = CST_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class_name, $len) !== 0) { return; } $relative_class = substr($class_name, $len); $file = $base_dir . 'class-' . strtolower(str_replace('_', '-', $relative_class)) . '.php'; if (file_exists($file)) { require_once $file; } }); // 初始化插件 function cst_init() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { wp_die(__('本插件需要WordPress 5.0或更高版本', 'custom-survey-tool')); } // 实例化核心类 $database = new CST_Database(); $survey = new CST_Survey(); $admin = new CST_Admin(); $frontend = new CST_Frontend(); // 激活/停用钩子 register_activation_hook(__FILE__, array($database, 'create_tables')); register_deactivation_hook(__FILE__, array($database, 'drop_tables')); } add_action('plugins_loaded', 'cst_init'); 第三章:数据库与核心功能开发 3.1 数据库处理类 创建includes/class-database.php文件,处理数据库表的创建与维护: <?php class CST_Database { public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_prefix = $wpdb->prefix . 'cst_'; // 创建问卷表 $surveys_table = $table_prefix . 'surveys'; $sql1 = "CREATE TABLE IF NOT EXISTS $surveys_table ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, description text, status varchar(20) DEFAULT 'draft', created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 创建问题表 $questions_table = $table_prefix . 'questions'; $sql2 = "CREATE TABLE IF NOT EXISTS $questions_table ( id int(11) NOT NULL AUTO_INCREMENT, survey_id int(11) NOT NULL, question_text text NOT NULL, question_type varchar(50) NOT NULL, options text, required tinyint(1) DEFAULT 0, sort_order int(11) DEFAULT 0, PRIMARY KEY (id), KEY survey_id (survey_id) ) $charset_collate;"; // 创建回答表 $responses_table = $table_prefix . 'responses'; $sql3 = "CREATE TABLE IF NOT EXISTS $responses_table ( id int(11) NOT NULL AUTO_INCREMENT, survey_id int(11) NOT NULL, question_id int(11) NOT NULL, answer text NOT NULL, user_id int(11) DEFAULT NULL, session_id varchar(100) DEFAULT NULL, submitted_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY survey_id (survey_id), KEY question_id (question_id), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); dbDelta($sql3); // 添加默认数据(示例问卷) $this->add_sample_data(); } private function add_sample_data() { global $wpdb; $table_prefix = $wpdb->prefix . 'cst_'; // 检查是否已有数据 $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table_prefix}surveys"); if ($count == 0) { // 添加示例问卷 $wpdb->insert( $table_prefix . 'surveys', array( 'title' => '客户满意度调查', 'description' => '帮助我们改进服务的简短问卷', 'status' => 'published' ) ); $survey_id = $wpdb->insert_id; // 添加示例问题 $sample_questions = array( array( 'survey_id' => $survey_id, 'question_text' => '您如何评价我们的产品质量?', 'question_type' => 'rating', 'options' => json_encode(array('min' => 1, 'max' => 5, 'labels' => array('非常差', '差', '一般', '好', '非常好'))), 'required' => 1, 'sort_order' => 1 ), array( 'survey_id' => $survey_id, 'question_text' => '您是通过什么渠道了解到我们的?', 'question_type' => 'checkbox', 'options' => json_encode(array('搜索引擎', '社交媒体', '朋友推荐', '广告', '其他')), 'required' => 0, 'sort_order' => 2 ), array( 'survey_id' => $survey_id, 'question_text' => '您有什么改进建议?', 'question_type' => 'textarea', 'options' => json_encode(array('rows' => 4)), 'required' => 0, 'sort_order' => 3 ) ); foreach ($sample_questions as $question) { $wpdb->insert($table_prefix . 'questions', $question); } } } public function drop_tables() { global $wpdb; $tables = array( $wpdb->prefix . 'cst_surveys', $wpdb->prefix . 'cst_questions', $wpdb->prefix . 'cst_responses' ); foreach ($tables as $table) { $wpdb->query("DROP TABLE IF EXISTS $table"); } } } 3.2 问卷核心功能类 创建includes/class-survey.php文件,实现问卷的核心业务逻辑: <?php class CST_Survey { private $db; public function __construct() { global $wpdb; $this->db = $wpdb; $this->table_prefix = $wpdb->prefix . 'cst_'; } // 获取所有问卷 public function get_all_surveys($status = null) { $where = ''; if ($status) { $where = $this->db->prepare("WHERE status = %s", $status); } return $this->db->get_results( "SELECT * FROM {$this->table_prefix}surveys $where ORDER BY created_at DESC" ); } // 获取单个问卷 public function get_survey($id) { return $this->db->get_row( $this->db->prepare( "SELECT * FROM {$this->table_prefix}surveys WHERE id = %d", $id ) ); } // 获取问卷的所有问题 public function get_survey_questions($survey_id) { return $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->table_prefix}questions WHERE survey_id = %d ORDER BY sort_order ASC", $survey_id ) ); } // 创建新问卷 public function create_survey($data) { $defaults = array( 'title' => '新问卷', 'description' => '', 'status' => 'draft' ); $data = wp_parse_args($data, $defaults); $this->db->insert( $this->table_prefix . 'surveys', $data ); return $this->db->insert_id; } // 更新问卷 public function update_survey($id, $data) { return $this->db->update( $this->table_prefix . 'surveys', $data, array('id' => $id) ); } // 删除问卷 public function delete_survey($id) { // 先删除相关问题 $this->db->delete( $this->table_prefix . 'questions', array('survey_id' => $id) ); // 再删除问卷 return $this->db->delete( $this->table_prefix . 'surveys', array('id' => $id) ); } // 添加问题到问卷 public function add_question($survey_id, $data) { $defaults = array( 'survey_id' => $survey_id, 'question_text' => '新问题', 'question_type' => 'text', 'options' => '', 'required' => 0, 'sort_order' => 0 ); $data = wp_parse_args($data, $defaults); $this->db->insert( $this->table_prefix . 'questions', $data ); return $this->db->insert_id; } // 提交问卷回答 public function submit_response($survey_id, $responses, $user_id = null) { $session_id = $this->generate_session_id(); foreach ($responses as $question_id => $answer) { // 处理多选答案 if (is_array($answer)) { $answer = json_encode($answer); } $this->db->insert( $this->table_prefix . 'responses', array( 'survey_id' => $survey_id, 'question_id' => $question_id, 'answer' => $answer, 'user_id' => $user_id, 'session_id' => $session_id ) ); } return true; } // 获取问卷统计 public function get_survey_stats($survey_id) { $questions = $this->get_survey_questions($survey_id); $stats = array(); foreach ($questions as $question) { $answers = $this->db->get_results( $this->db->prepare( "SELECT answer FROM {$this->table_prefix}responses WHERE survey_id = %d AND question_id = %d", $survey_id, $question->id ) ); $question_stats = array( 'question' => $question->question_text, 'type' => $question->question_type, 'total_responses' => count($answers), 'answers' => array() ); // 根据问题类型统计答案 if ($question->question_type === 'radio' || $question->question_type === 'checkbox') { $options = json_decode($question->options, true); $counts = array(); foreach ($options as $option) { $counts[$option] = 0; } foreach ($answers as $answer) { if ($question->question_type === 'checkbox') { $user_answers = json_decode($answer->answer, true); if (is_array($user_answers)) { foreach ($user_answers as $user_answer) { if (isset($counts[$user_answer])) { $counts[$user_answer]++; } } } } else { if (isset($counts[$answer->answer])) { $counts[$answer->answer]++; } } } $question_stats['answers'] = $counts; } elseif ($question->question_type === 'rating') { $ratings = array(); foreach ($answers as $answer) { $ratings[] = intval($answer->answer); } if (!empty($ratings)) { $question_stats['answers'] = array( 'average' => round(array_sum($ratings) / count($ratings), 2), 'min' => min($ratings), 'max' => max($ratings), 'distribution' => array_count_values($ratings) ); } } $stats[] = $question_stats; } return $stats; } private function generate_session_id() { return md5(uniqid(rand(), true) . $_SERVER['REMOTE_ADDR']); } } 第四章:后台管理界面开发 4.1 管理类与菜单创建 创建includes/class-admin.php文件,实现后台管理功能: <?php class CST_Admin { private $survey; public function __construct() { $this->survey = new CST_Survey(); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 加载脚本和样式 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); // 处理表单提交 add_action('admin_post_cst_save_survey', array($this, 'save_survey')); add_action('admin_post_cst_delete_survey', array($this, 'delete_survey')); } 第四章:后台管理界面开发(续) 4.1 管理类与菜单创建(续) public function add_admin_menu() { // 主菜单 add_menu_page( '问卷调查工具', '问卷调查', 'manage_options', 'custom-survey-tool', array($this, 'surveys_list_page'), 'dashicons-feedback', 30 ); // 子菜单 add_submenu_page( 'custom-survey-tool', '所有问卷', '所有问卷', 'manage_options', 'custom-survey-tool', array($this, 'surveys_list_page') ); add_submenu_page( 'custom-survey-tool', '添加新问卷', '添加新问卷', 'manage_options', 'cst-add-survey', array($this, 'add_survey_page') ); add_submenu_page( 'custom-survey-tool', '问卷统计', '问卷统计', 'manage_options', 'cst-survey-stats', array($this, 'survey_stats_page') ); add_submenu_page( 'custom-survey-tool', '插件设置', '设置', 'manage_options', 'cst-settings', array($this, 'settings_page') ); } public function enqueue_admin_scripts($hook) { // 只在插件页面加载资源 if (strpos($hook, 'custom-survey-tool') === false && strpos($hook, 'cst-') === false) { return; } wp_enqueue_style( 'cst-admin-style', CST_PLUGIN_URL . 'admin/css/admin-style.css', array(), CST_VERSION ); wp_enqueue_script( 'cst-admin-script', CST_PLUGIN_URL . 'admin/js/admin-script.js', array('jquery', 'jquery-ui-sortable'), CST_VERSION, true ); // 本地化脚本 wp_localize_script('cst-admin-script', 'cst_admin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('cst_admin_nonce'), 'confirm_delete' => __('确定要删除这个问卷吗?此操作不可撤销。', 'custom-survey-tool') )); } 4.2 问卷列表页面 public function surveys_list_page() { $action = isset($_GET['action']) ? sanitize_text_field($_GET['action']) : 'list'; $survey_id = isset($_GET['survey_id']) ? intval($_GET['survey_id']) : 0; switch ($action) { case 'edit': $this->edit_survey_page($survey_id); break; case 'view': $this->view_responses_page($survey_id); break; default: $this->display_surveys_list(); } } private function display_surveys_list() { $surveys = $this->survey->get_all_surveys(); ?> <div class="wrap cst-admin"> <h1 class="wp-heading-inline">问卷调查</h1> <a href="<?php echo admin_url('admin.php?page=cst-add-survey'); ?>" class="page-title-action"> 添加新问卷 </a> <?php if (isset($_GET['message'])): ?> <div class="notice notice-<?php echo sanitize_text_field($_GET['type'] ?? 'success'); ?> is-dismissible"> <p><?php echo esc_html($_GET['message']); ?></p> </div> <?php endif; ?> <div class="tablenav top"> <div class="alignleft actions"> <select name="status_filter" id="status_filter"> <option value="">所有状态</option> <option value="draft">草稿</option> <option value="published">已发布</option> <option value="archived">已归档</option> </select> <button class="button action" id="filter_action">筛选</button> </div> <br class="clear"> </div> <table class="wp-list-table widefat fixed striped surveys"> <thead> <tr> <th scope="col" class="column-id">ID</th> <th scope="col" class="column-title">问卷标题</th> <th scope="col" class="column-status">状态</th> <th scope="col" class="column-questions">问题数</th> <th scope="col" class="column-responses">回答数</th> <th scope="col" class="column-date">创建时间</th> <th scope="col" class="column-actions">操作</th> </tr> </thead> <tbody> <?php if (empty($surveys)): ?> <tr> <td colspan="7" class="no-items">暂无问卷,请点击"添加新问卷"创建第一个问卷。</td> </tr> <?php else: ?> <?php foreach ($surveys as $survey): $questions_count = $this->survey->get_questions_count($survey->id); $responses_count = $this->survey->get_responses_count($survey->id); ?> <tr> <td class="column-id"><?php echo $survey->id; ?></td> <td class="column-title"> <strong> <a href="<?php echo admin_url('admin.php?page=custom-survey-tool&action=edit&survey_id=' . $survey->id); ?>"> <?php echo esc_html($survey->title); ?> </a> </strong> <div class="row-actions"> <span class="edit"> <a href="<?php echo admin_url('admin.php?page=custom-survey-tool&action=edit&survey_id=' . $survey->id); ?>">编辑</a> | </span> <span class="view"> <a href="<?php echo admin_url('admin.php?page=cst-survey-stats&survey_id=' . $survey->id); ?>">统计</a> | </span> <span class="duplicate"> <a href="#" class="duplicate-survey" data-id="<?php echo $survey->id; ?>">复制</a> | </span> <span class="trash"> <a href="<?php echo wp_nonce_url(admin_url('admin-post.php?action=cst_delete_survey&survey_id=' . $survey->id), 'cst_delete_survey'); ?>" class="submitdelete" onclick="return confirm('确定要删除这个问卷吗?')">删除</a> </span> </div> </td> <td class="column-status"> <span class="status-badge status-<?php echo $survey->status; ?>"> <?php $status_labels = array( 'draft' => '草稿', 'published' => '已发布', 'archived' => '已归档' ); echo $status_labels[$survey->status] ?? $survey->status; ?> </span> </td> <td class="column-questions"><?php echo $questions_count; ?></td> <td class="column-responses"><?php echo $responses_count; ?></td> <td class="column-date"><?php echo date('Y-m-d H:i', strtotime($survey->created_at)); ?></td> <td class="column-actions"> <a href="<?php echo admin_url('admin.php?page=custom-survey-tool&action=edit&survey_id=' . $survey->id); ?>" class="button button-small">编辑</a> <a href="<?php echo admin_url('admin.php?page=cst-survey-stats&survey_id=' . $survey->id); ?>" class="button button-small">查看统计</a> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> <?php } 4.3 问卷编辑页面 private function edit_survey_page($survey_id) { $survey = $this->survey->get_survey($survey_id); $questions = $this->survey->get_survey_questions($survey_id); if (!$survey) { wp_die('问卷不存在'); } ?> <div class="wrap cst-admin"> <h1>编辑问卷: <?php echo esc_html($survey->title); ?></h1> <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" id="survey-form"> <input type="hidden" name="action" value="cst_save_survey"> <input type="hidden" name="survey_id" value="<?php echo $survey_id; ?>"> <?php wp_nonce_field('cst_save_survey', 'cst_nonce'); ?> <div id="poststuff"> <div id="post-body" class="metabox-holder columns-2"> <!-- 主内容区 --> <div id="post-body-content"> <div class="postbox"> <h2 class="hndle">问卷基本信息</h2> <div class="inside"> <table class="form-table"> <tr> <th><label for="survey_title">问卷标题</label></th> <td> <input type="text" id="survey_title" name="survey_title" value="<?php echo esc_attr($survey->title); ?>" class="regular-text" required> </td> </tr> <tr> <th><label for="survey_description">问卷描述</label></th> <td> <textarea id="survey_description" name="survey_description" rows="3" class="large-text"><?php echo esc_textarea($survey->description); ?></textarea> <p class="description">显示在问卷开头的描述文字</p> </td> </tr> <tr> <th><label for="survey_status">状态</label></th> <td> <select id="survey_status" name="survey_status"> <option value="draft" <?php selected($survey->status, 'draft'); ?>>草稿</option> <option value="published" <?php selected($survey->status, 'published'); ?>>已发布</option> <option value="archived" <?php selected($survey->status, 'archived'); ?>>已归档</option> </select> </td> </tr> </table> </div> </div> <!-- 问题管理区域 --> <div class="postbox"> <h2 class="hndle">问卷问题</h2> <div class="inside"> <div id="questions-container" class="sortable-questions"> <?php if (empty($questions)): ?> <p class="no-questions">暂无问题,点击下方"添加问题"按钮开始添加。</p> <?php else: ?> <?php foreach ($questions as $index => $question): ?> <?php $this->render_question_editor($question, $index); ?> <?php endforeach; ?> <?php endif; ?> </div> <div class="add-question-section"> <button type="button" id="add-question" class="button button-primary"> <span class="dashicons dashicons-plus"></span> 添加问题 </button> <div class="question-type-selector" style="display: none;"> <select id="new-question-type"> <option value="text">单行文本</option> <option value="textarea">多行文本</option> <option value="radio">单选题</option> <option value="checkbox">多选题</option> <option value="select">下拉选择</option> <option value="rating">评分题</option> <option value="date">日期选择</option> <option value="email">邮箱地址</option> </select> <button type="button" id="confirm-add-question" class="button">确认添加</button> <button type="button" id="cancel-add-question" class="button button-link">取消</button> </div> </div> </div> </div> </div> <!-- 侧边栏 --> <div id="postbox-container-1" class="postbox-container"> <div class="postbox"> <h2 class="hndle">发布</h2> <div class="inside"> <div class="submitbox"> <div id="major-publishing-actions"> <div id="publishing-action"> <span class="spinner"></span> <input type="submit" name="save_survey" value="保存问卷" class="button button-primary button-large"> </div> <div class="clear"></div> </div> </div> </div> </div> <div class="postbox"> <h2 class="hndle">问卷短码</h2> <div class="inside"> <p>将以下短码插入到文章或页面中显示此问卷:</p> <input type="text" value="[custom_survey id='<?php echo $survey_id; ?>']" class="large-text code" readonly onclick="this.select();"> <p class="description">复制此短码并粘贴到任何文章或页面中</p> </div> </div> <div class="postbox"> <h2 class="hndle">问卷设置</h2> <div class="inside"> <table class="form-table"> <tr> <th><label for="allow_anonymous">允许匿名提交</label></th> <td> <input type="checkbox" id="allow_anonymous" name="allow_anonymous" value="1" checked> </td> </tr> <tr> <th><label for="limit_one_response">限制每人提交一次</label></th> <td> <input type="checkbox" id="limit_one_response" name="limit_one_response" value="1"> </td> </tr> <tr> <th><label for="show_progress">显示进度条</label></th> <td> <input type="checkbox" id="show_progress" name="show_progress" value="1" checked> </td> </tr> </table> </div> </div> </div> </div> </div> </form> </div> <?php } private function render_question_editor($question, $index) { $options = json_decode($question->options, true); ?> <div class="question-editor" data-index="<?php echo $index; ?>" data-id="<?php echo $question->id; ?>"> <div class="question-header"> <span class="question-number">问题 <?php echo $index + 1; ?></span> <span class="question-type-badge"><?php echo $this->get_question_type_label($question->question_type); ?></span> <button type="button" class="delete-question" title="删除问题"> <span class="dashicons dashicons-trash"></span> </button> <button type="button" class="toggle-question" title="展开/收起"> <span class="dashicons dashicons-arrow-down"></span> </button> </div> <div class="question-body"> <div class="question-main"> <input type="hidden" name="questions[<?php echo $index; ?>][id]" value="<?php echo $question->id; ?>"> <input type="hidden" name="questions[<?php echo $index; ?>][type]" value="<?php echo $question->question_type; ?>"> <div class="form-field"> <label>问题内容</label> <input type="text" name="questions[<?php echo $index; ?>][text]" value="<?php echo esc_attr($question->question_text); ?>" class="regular-text question-text" required> </div> <div class="form-field"> <label> <input type="checkbox" name="questions[<?php echo $index; ?>][required]" value="1" <?php checked($question->required, 1); ?>> 必填问题 </label> </div> <!-- 选项配置区域(根据问题类型显示) --> <div class="question-options" data-type="<?php echo $question->question_type; ?>"> <?php $this->render_question_options($question->question_type, $options, $index); ?> </div> </div> </div> </div> <?php } private function render_question_options($type, $options, $index) { switch ($type) { case 'radio': case 'checkbox': case 'select': ?> <div class="form-field"> <label>选项(每行一个)</label> <textarea name="questions[<?php echo $index; ?>][options]" rows="5" class="large-text"><?php if (is_array($options)) { echo implode("n", $options); } ?></textarea> <p class="description">每个选项单独一行</p> </div> <?php break; case 'rating': $min = $options['min'] ?? 1; $max = $options['max'] ?? 5; $labels = $options['labels'] ?? array(); ?>

发表评论