文章目录[隐藏]
网络传媒柔性用户画像与内容推荐WordPress插件教程
引言:个性化推荐的时代需求
在信息爆炸的网络传媒时代,用户每天面对海量内容,如何精准推荐符合用户兴趣的内容成为提升用户体验和留存率的关键。传统的内容推荐往往基于简单规则或热门度,缺乏对用户个性化需求的深度理解。本文将介绍如何开发一个基于柔性用户画像的内容推荐WordPress插件,通过动态学习用户行为,实现智能内容匹配。
柔性用户画像与刚性用户画像不同,它不依赖用户填写的固定标签,而是通过分析用户的浏览行为、互动模式等动态数据,构建可随时间演变的用户兴趣模型。这种方法的优势在于能够捕捉用户兴趣的变化,提供更精准的推荐。
插件架构设计
核心模块划分
我们的插件将包含以下核心模块:
- 用户行为追踪模块:记录用户的浏览、点击、停留时间等行为
- 用户画像构建模块:分析行为数据,构建动态用户兴趣模型
- 内容分析模块:分析网站内容特征,建立内容标签体系
- 推荐算法模块:匹配用户画像与内容特征,生成推荐列表
- 前端展示模块:在网站适当位置展示推荐内容
数据库设计
我们需要创建几个自定义数据表来存储用户行为和画像数据:
/**
* 创建插件所需的数据表
* 这段代码应该放在插件激活钩子中执行
*/
function fup_create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_prefix = $wpdb->prefix . 'fup_';
// 用户行为记录表
$behavior_table = $table_prefix . 'user_behavior';
$behavior_sql = "CREATE TABLE IF NOT EXISTS $behavior_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) DEFAULT 0,
session_id varchar(100) NOT NULL,
post_id bigint(20) NOT NULL,
behavior_type varchar(50) NOT NULL,
behavior_value float DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY post_id (post_id),
KEY behavior_type (behavior_type)
) $charset_collate;";
// 用户画像表
$profile_table = $table_prefix . 'user_profile';
$profile_sql = "CREATE TABLE IF NOT EXISTS $profile_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_id bigint(20) DEFAULT 0,
session_id varchar(100) NOT NULL,
interest_tags text,
interest_weights text,
last_updated datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY user_session (user_id, session_id)
) $charset_collate;";
// 内容标签表
$content_tags_table = $table_prefix . 'content_tags';
$content_tags_sql = "CREATE TABLE IF NOT EXISTS $content_tags_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
post_id bigint(20) NOT NULL,
tags text,
keywords text,
category_weights text,
updated_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY post_id (post_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($behavior_sql);
dbDelta($profile_sql);
dbDelta($content_tags_sql);
}
register_activation_hook(__FILE__, 'fup_create_tables');
用户行为追踪实现
行为数据收集
用户行为追踪是构建用户画像的基础。我们需要在用户浏览内容时收集相关数据:
/**
* 追踪用户行为
* @param int $post_id 文章ID
* @param string $behavior_type 行为类型:view, click, like, share, time_spent
* @param float $value 行为值(如停留时间)
*/
function fup_track_user_behavior($post_id, $behavior_type = 'view', $value = 1) {
global $wpdb;
// 获取当前用户信息
$user_id = get_current_user_id();
$session_id = fup_get_session_id();
// 防止重复记录快速刷新
if ($behavior_type === 'view') {
$last_view = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}fup_user_behavior
WHERE (user_id = %d OR session_id = %s)
AND post_id = %d
AND behavior_type = 'view'
AND created_at > DATE_SUB(NOW(), INTERVAL 10 SECOND)",
$user_id, $session_id, $post_id
));
if ($last_view) {
return;
}
}
// 插入行为记录
$wpdb->insert(
"{$wpdb->prefix}fup_user_behavior",
array(
'user_id' => $user_id,
'session_id' => $session_id,
'post_id' => $post_id,
'behavior_type' => $behavior_type,
'behavior_value' => $value
),
array('%d', '%s', '%d', '%s', '%f')
);
// 实时更新用户画像(简化版,实际应使用队列处理)
if (in_array($behavior_type, array('view', 'click', 'like'))) {
fup_update_user_profile($user_id, $session_id, $post_id, $behavior_type, $value);
}
}
/**
* 获取或创建会话ID
*/
function fup_get_session_id() {
if (isset($_COOKIE['fup_session_id']) && $_COOKIE['fup_session_id']) {
return $_COOKIE['fup_session_id'];
}
$session_id = wp_generate_uuid4();
setcookie('fup_session_id', $session_id, time() + 3600 * 24 * 30, '/');
return $session_id;
}
// 在文章页面添加行为追踪
add_action('wp_head', function() {
if (is_single()) {
global $post;
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 发送浏览行为
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'action=fup_track&post_id=<?php echo $post->ID; ?>&behavior_type=view'
});
// 追踪停留时间
let startTime = Date.now();
window.addEventListener('beforeunload', function() {
let timeSpent = (Date.now() - startTime) / 1000; // 转换为秒
navigator.sendBeacon('<?php echo admin_url('admin-ajax.php'); ?>',
'action=fup_track&post_id=<?php echo $post->ID; ?>&behavior_type=time_spent&value=' + timeSpent);
});
});
</script>
<?php
}
});
// 处理AJAX行为追踪请求
add_action('wp_ajax_fup_track', 'fup_handle_track_request');
add_action('wp_ajax_nopriv_fup_track', 'fup_handle_track_request');
function fup_handle_track_request() {
$post_id = intval($_POST['post_id']);
$behavior_type = sanitize_text_field($_POST['behavior_type']);
$value = isset($_POST['value']) ? floatval($_POST['value']) : 1;
if ($post_id && $behavior_type) {
fup_track_user_behavior($post_id, $behavior_type, $value);
}
wp_die();
}
柔性用户画像构建算法
兴趣标签提取与权重计算
用户画像的核心是根据用户行为推断其兴趣标签及权重:
/**
* 更新用户画像
* @param int $user_id 用户ID(0表示未登录用户)
* @param string $session_id 会话ID
* @param int $post_id 文章ID
* @param string $behavior_type 行为类型
* @param float $value 行为值
*/
function fup_update_user_profile($user_id, $session_id, $post_id, $behavior_type, $value = 1) {
global $wpdb;
// 获取文章标签和分类
$post_tags = wp_get_post_tags($post_id, array('fields' => 'names'));
$post_categories = wp_get_post_categories($post_id, array('fields' => 'names'));
// 合并标签和分类作为兴趣关键词
$interest_keywords = array_merge($post_tags, $post_categories);
if (empty($interest_keywords)) {
return;
}
// 获取现有用户画像
$profile = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}fup_user_profile
WHERE (user_id = %d OR session_id = %s)
ORDER BY last_updated DESC LIMIT 1",
$user_id, $session_id
));
// 行为权重系数
$behavior_weights = array(
'view' => 0.1,
'click' => 0.3,
'like' => 0.5,
'share' => 0.8,
'time_spent' => 0.05 // 每60秒增加0.05权重
);
$weight = isset($behavior_weights[$behavior_type]) ? $behavior_weights[$behavior_type] : 0.1;
if ($behavior_type === 'time_spent') {
$weight = $weight * min($value / 60, 10); // 最大权重限制
}
// 初始化或更新兴趣权重
if ($profile) {
$interest_weights = maybe_unserialize($profile->interest_weights);
$interest_weights = is_array($interest_weights) ? $interest_weights : array();
// 衰减旧兴趣(柔性特征:兴趣会随时间衰减)
$decay_factor = 0.95; // 每次更新时旧兴趣衰减5%
foreach ($interest_weights as $keyword => $old_weight) {
$interest_weights[$keyword] = $old_weight * $decay_factor;
}
} else {
$interest_weights = array();
}
// 增加新兴趣权重
foreach ($interest_keywords as $keyword) {
if (isset($interest_weights[$keyword])) {
$interest_weights[$keyword] += $weight;
} else {
$interest_weights[$keyword] = $weight;
}
// 限制单个兴趣的最大权重
$interest_weights[$keyword] = min($interest_weights[$keyword], 10);
}
// 移除权重过低的关键词(小于0.1)
foreach ($interest_weights as $keyword => $weight) {
if ($weight < 0.1) {
unset($interest_weights[$keyword]);
}
}
// 归一化处理,使所有权重之和为1
$total_weight = array_sum($interest_weights);
if ($total_weight > 0) {
foreach ($interest_weights as $keyword => $weight) {
$interest_weights[$keyword] = $weight / $total_weight;
}
}
// 保存更新后的用户画像
$data = array(
'user_id' => $user_id,
'session_id' => $session_id,
'interest_tags' => serialize(array_keys($interest_weights)),
'interest_weights' => serialize($interest_weights),
'last_updated' => current_time('mysql')
);
if ($profile) {
$wpdb->update(
"{$wpdb->prefix}fup_user_profile",
$data,
array('id' => $profile->id),
array('%d', '%s', '%s', '%s', '%s'),
array('%d')
);
} else {
$wpdb->insert(
"{$wpdb->prefix}fup_user_profile",
$data,
array('%d', '%s', '%s', '%s', '%s')
);
}
}
智能推荐算法实现
基于内容相似度的推荐
/**
* 获取个性化推荐内容
* @param int $user_id 用户ID
* @param string $session_id 会话ID
* @param int $limit 推荐数量
* @return array 推荐文章ID数组
*/
function fup_get_personalized_recommendations($user_id = 0, $session_id = '', $limit = 5) {
global $wpdb;
// 获取用户兴趣画像
$profile = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}fup_user_profile
WHERE (user_id = %d OR session_id = %s)
ORDER BY last_updated DESC LIMIT 1",
$user_id, $session_id
));
// 如果没有用户画像,返回热门内容
if (!$profile || empty($profile->interest_weights)) {
return fup_get_popular_posts($limit);
}
$interest_weights = maybe_unserialize($profile->interest_weights);
if (!is_array($interest_weights) || empty($interest_weights)) {
return fup_get_popular_posts($limit);
}
// 获取用户已浏览过的文章ID
$viewed_posts = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT post_id FROM {$wpdb->prefix}fup_user_behavior
WHERE (user_id = %d OR session_id = %s)
AND behavior_type IN ('view', 'click')",
$user_id, $session_id
));
// 获取所有文章的基本信息
$all_posts = get_posts(array(
'post_type' => 'post',
'post_status' => 'publish',
'numberposts' => 100, // 限制计算范围以提高性能
'exclude' => $viewed_posts,
'fields' => 'ids'
));
if (empty($all_posts)) {
return array();
}
// 计算每篇文章与用户兴趣的匹配度
$post_scores = array();
foreach ($all_posts as $post_id) {
$score = fup_calculate_post_match_score($post_id, $interest_weights);
$post_scores[$post_id] = $score;
}
// 按匹配度排序
arsort($post_scores);
// 返回前N个推荐
return array_slice(array_keys($post_scores), 0, $limit, true);
}
/**
* 计算文章与用户兴趣的匹配度
*/
function fup_calculate_post_match_score($post_id, $interest_weights) {
// 获取文章标签和分类
$post_tags = wp_get_post_tags($post_id, array('fields' => 'names'));
$post_categories = wp_get_post_categories($post_id, array('fields' => 'names'));
$post_keywords = array_merge($post_tags, $post_categories);
if (empty($post_keywords)) {
return 0;
}
// 计算匹配度
$match_score = 0;
foreach ($post_keywords as $keyword) {
if (isset($interest_weights[$keyword])) {
$match_score += $interest_weights[$keyword];
}
}
// 考虑文章新鲜度(最近发布的文章有加分)
$post_date = get_the_date('Y-m-d', $post_id);
$days_old = (time() - strtotime($post_date)) / (60 * 60 * 24);
$freshness_factor = exp(-$days_old / 30); // 30天衰减周期
return $match_score * (0.7 + 0.3 * $freshness_factor); // 70%兴趣匹配 + 30%新鲜度
}
/**
* 获取热门文章(备用推荐)
*/
function fup_get_popular_posts($limit = 5) {
global $wpdb;
$popular_posts = $wpdb->get_results($wpdb->prepare(
"SELECT post_id, COUNT(*) as view_count
FROM {$wpdb->prefix}fup_user_behavior
WHERE behavior_type = 'view'
AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY post_id
ORDER BY view_count DESC
LIMIT %d",
$limit * 2 // 多取一些以便过滤
));
$post_ids = array();
foreach ($popular_posts as $post) {
if (get_post_status($post->post_id) === 'publish') {
$post_ids[] = $post->post_id;
}
if (count($post_ids) >= $limit) {
break;
}
}
return $post_ids;
}
前端展示与插件集成
创建推荐内容小工具
/**
* 注册推荐小工具
*/
class FUP_Recommendations_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'fup_recommendations_widget',
__('个性化推荐', 'fup'),
array('description' => __('根据用户兴趣推荐相关内容', 'fup'))
);
}
public function widget($args, $instance) {
$title = apply_filters('widget_title',
empty($instance['title']) ? __('为您推荐', 'fup') : $instance['title']);
$limit = empty($instance['limit']) ? 5 : absint($instance['limit']);
echo $args['before_widget'];
if ($title) {
echo $args['before_title'] . $title . $args['after_title'];
}
// 获取推荐内容
$session_id = fup_get_session_id();
$recommendations = fup_get_personalized_recommendations($user_id, $session_id, $limit);
if (!empty($recommendations)) {
echo '<ul class="fup-recommendations-list">';
foreach ($recommendations as $post_id) {
$post = get_post($post_id);
if (!$post) continue;
echo '<li class="fup-recommendation-item">';
echo '<a href="' . get_permalink($post_id) . '" class="fup-recommendation-link">';
// 显示缩略图
if (has_post_thumbnail($post_id)) {
echo get_the_post_thumbnail($post_id, 'thumbnail',
array('class' => 'fup-recommendation-thumbnail'));
}
echo '<span class="fup-recommendation-title">' .
esc_html(get_the_title($post_id)) . '</span>';
// 显示匹配度标签
$profile = fup_get_user_profile($user_id, $session_id);
if ($profile && !empty($profile->interest_weights)) {
$interest_weights = maybe_unserialize($profile->interest_weights);
$post_tags = wp_get_post_tags($post_id, array('fields' => 'names'));
$common_tags = array_intersect(array_keys($interest_weights), $post_tags);
if (!empty($common_tags)) {
echo '<span class="fup-match-tags">';
echo '<small>' . __('可能因为:', 'fup') . ' ';
echo implode(', ', array_slice($common_tags, 0, 2));
echo '</small>';
echo '</span>';
}
}
echo '</a>';
echo '</li>';
}
echo '</ul>';
} else {
echo '<p class="fup-no-recommendations">' .
__('暂无个性化推荐,请继续浏览以生成您的兴趣画像。', 'fup') . '</p>';
}
echo $args['after_widget'];
}
public function form($instance) {
$title = isset($instance['title']) ? esc_attr($instance['title']) : '';
$limit = isset($instance['limit']) ? absint($instance['limit']) : 5;
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">
<?php _e('标题:', 'fup'); ?>
</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_id('title'); ?>" type="text"
value="<?php echo $title; ?>" />
</p>
<p>
<label for="<?php echo $this->get_field_id('limit'); ?>">
<?php _e('显示数量:', 'fup'); ?>
</label>
<input class="tiny-text" id="<?php echo $this->get_field_id('limit'); ?>"
name="<?php echo $this->get_field_id('limit'); ?>" type="number"
value="<?php echo $limit; ?>" min="1" max="10" />
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = $old_instance;
$instance['title'] = sanitize_text_field($new_instance['title']);
$instance['limit'] = absint($new_instance['limit']);
return $instance;
}
}
// 注册小工具
add_action('widgets_init', function() {
register_widget('FUP_Recommendations_Widget');
});
/**
* 获取用户画像辅助函数
*/
function fup_get_user_profile($user_id, $session_id) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}fup_user_profile
WHERE (user_id = %d OR session_id = %s)
ORDER BY last_updated DESC LIMIT 1",
$user_id, $session_id
));
}
短代码支持
/**
* 添加推荐短代码
* 用法:[fup_recommendations limit="5" title="为您推荐"]
*/
function fup_recommendations_shortcode($atts) {
$atts = shortcode_atts(array(
'limit' => 5,
'title' => __('为您推荐', 'fup'),
'layout' => 'list' // list, grid, compact
), $atts, 'fup_recommendations');
$user_id = get_current_user_id();
$session_id = fup_get_session_id();
$recommendations = fup_get_personalized_recommendations($user_id, $session_id, $atts['limit']);
if (empty($recommendations)) {
return '<div class="fup-recommendations-empty">' .
__('暂无个性化推荐', 'fup') . '</div>';
}
ob_start();
?>
<div class="fup-recommendations-shortcode fup-layout-<?php echo esc_attr($atts['layout']); ?>">
<?php if ($atts['title']) : ?>
<h3 class="fup-recommendations-title"><?php echo esc_html($atts['title']); ?></h3>
<?php endif; ?>
<div class="fup-recommendations-container">
<?php foreach ($recommendations as $post_id) :
$post = get_post($post_id);
if (!$post) continue;
?>
<div class="fup-recommendation-card">
<a href="<?php echo get_permalink($post_id); ?>" class="fup-card-link">
<?php if (has_post_thumbnail($post_id)) : ?>
<div class="fup-card-image">
<?php echo get_the_post_thumbnail($post_id, 'medium'); ?>
</div>
<?php endif; ?>
<div class="fup-card-content">
<h4 class="fup-card-title"><?php echo esc_html(get_the_title($post_id)); ?></h4>
<div class="fup-card-excerpt">
<?php echo wp_trim_words(get_the_excerpt($post_id), 20); ?>
</div>
<div class="fup-card-meta">
<span class="fup-card-date">
<?php echo get_the_date('', $post_id); ?>
</span>
<span class="fup-card-readtime">
<?php echo fup_estimate_read_time($post_id); ?>分钟阅读
</span>
</div>
</div>
</a>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
return ob_get_clean();
}
add_shortcode('fup_recommendations', 'fup_recommendations_shortcode');
/**
* 估算阅读时间
*/
function fup_estimate_read_time($post_id) {
$content = get_post_field('post_content', $post_id);
$word_count = str_word_count(strip_tags($content));
$reading_time = ceil($word_count / 200); // 按每分钟200字计算
return max(1, $reading_time);
}
后台管理界面
插件设置页面
/**
* 添加插件设置页面
*/
class FUP_Admin_Settings {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
public function add_admin_menu() {
add_options_page(
__('柔性用户画像设置', 'fup'),
__('用户画像推荐', 'fup'),
'manage_options',
'fup-settings',
array($this, 'render_settings_page')
);
// 数据分析页面
add_submenu_page(
'fup-settings',
__('用户行为分析', 'fup'),
__('行为分析', 'fup'),
'manage_options',
'fup-analytics',
array($this, 'render_analytics_page')
);
}
public function register_settings() {
register_setting('fup_settings_group', 'fup_settings');
add_settings_section(
'fup_general_section',
__('常规设置', 'fup'),
array($this, 'render_general_section'),
'fup-settings'
);
add_settings_field(
'fup_enable_tracking',
__('启用用户追踪', 'fup'),
array($this, 'render_enable_tracking_field'),
'fup-settings',
'fup_general_section'
);
add_settings_field(
'fup_recommendation_limit',
__('推荐数量限制', 'fup'),
array($this, 'render_recommendation_limit_field'),
'fup-settings',
'fup_general_section'
);
add_settings_field(
'fup_interest_decay_rate',
__('兴趣衰减率', 'fup'),
array($this, 'render_decay_rate_field'),
'fup-settings',
'fup_general_section'
);
add_settings_field(
'fup_exclude_categories',
__('排除分类', 'fup'),
array($this, 'render_exclude_categories_field'),
'fup-settings',
'fup_general_section'
);
}
public function render_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('fup_settings_group');
do_settings_sections('fup-settings');
submit_button(__('保存设置', 'fup'));
?>
</form>
<div class="fup-settings-info">
<h3><?php _e('插件状态', 'fup'); ?></h3>
<?php
global $wpdb;
// 统计信息
$total_users = $wpdb->get_var(
"SELECT COUNT(DISTINCT user_id) FROM {$wpdb->prefix}fup_user_profile WHERE user_id > 0"
);
$total_sessions = $wpdb->get_var(
"SELECT COUNT(DISTINCT session_id) FROM {$wpdb->prefix}fup_user_profile"
);
$total_behaviors = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->prefix}fup_user_behavior"
);
echo '<p>' . sprintf(__('已记录用户: %d', 'fup'), $total_users) . '</p>';
echo '<p>' . sprintf(__('会话数量: %d', 'fup'), $total_sessions) . '</p>';
echo '<p>' . sprintf(__('行为记录: %d', 'fup'), $total_behaviors) . '</p>';
?>
<h3><?php _e('数据维护', 'fup'); ?></h3>
<form method="post" action="">
<?php wp_nonce_field('fup_maintenance', 'fup_maintenance_nonce'); ?>
<p>
<button type="submit" name="fup_cleanup_old_data" class="button button-secondary">
<?php _e('清理30天前的旧数据', 'fup'); ?>
</button>
<span class="description">
<?php _e('删除30天前的用户行为记录,保留用户画像', 'fup'); ?>
</span>
</p>
</form>
</div>
</div>
<?php
// 处理数据清理请求
if (isset($_POST['fup_cleanup_old_data']) &&
wp_verify_nonce($_POST['fup_maintenance_nonce'], 'fup_maintenance')) {
$this->cleanup_old_data();
}
}
public function render_analytics_page() {
if (!current_user_can('manage_options')) {
return;
}
global $wpdb;
?>
<div class="wrap">
<h1><?php _e('用户行为分析', 'fup'); ?></h1>
<div class="fup-analytics-container">
<div class="fup-analytics-charts">
<h3><?php _e('热门兴趣标签', 'fup'); ?></h3>
<?php
// 获取所有用户兴趣标签的统计
$profiles = $wpdb->get_results(
"SELECT interest_weights FROM {$wpdb->prefix}fup_user_profile
WHERE interest_weights IS NOT NULL
AND last_updated > DATE_SUB(NOW(), INTERVAL 7 DAY)"
);
$tag_popularity = array();
foreach ($profiles as $profile) {
$weights = maybe_unserialize($profile->interest_weights);
if (is_array($weights)) {
foreach ($weights as $tag => $weight) {
if (!isset($tag_popularity[$tag])) {
$tag_popularity[$tag] = 0;
}
$tag_popularity[$tag] += $weight;
}
}
}
arsort($tag_popularity);
$top_tags = array_slice($tag_popularity, 0, 10, true);
if (!empty($top_tags)) {
echo '<ul class="fup-tag-cloud">';
$max_weight = max($top_tags);
foreach ($top_tags as $tag => $weight) {
$font_size = 12 + ($weight / $max_weight) * 18;
echo '<li style="font-size: ' . $font_size . 'px;">';
echo esc_html($tag) . ' <small>(' . round($weight, 2) . ')</small>';
echo '</li>';
}
echo '</ul>';
}
?>
</div>
<div class="fup-analytics-stats">
<h3><?php _e('行为统计', 'fup'); ?></h3>
<?php
$behavior_stats = $wpdb->get_results(
"SELECT behavior_type, COUNT(*) as count,
AVG(behavior_value) as avg_value
FROM {$wpdb->prefix}fup_user_behavior
WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY behavior_type"
);
if ($behavior_stats) {
echo '<table class="widefat">';
echo '<thead><tr>
<th>' . __('行为类型', 'fup') . '</th>
<th>' . __('次数', 'fup') . '</th>
<th>' . __('平均值', 'fup') . '</th>
</tr></thead>';
echo '<tbody>';
foreach ($behavior_stats as $stat) {
echo '<tr>';
echo '<td>' . esc_html($stat->behavior_type) . '</td>';
echo '<td>' . intval($stat->count) . '</td>';
echo '<td>' . round($stat->avg_value, 2) . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
?>
</div>
</div>
</div>
<?php
}
public function render_general_section() {
echo '<p>' . __('配置柔性用户画像与推荐系统的基本参数。', 'fup') . '</p>';
}
public function render_enable_tracking_field() {
$options = get_option('fup_settings');
$enabled = isset($options['enable_tracking']) ? $options['enable_tracking'] : 1;
?>
<label>
<input type="checkbox" name="fup_settings[enable_tracking]" value="1"
<?php checked(1, $enabled); ?> />
<?php _e('启用用户行为追踪', 'fup'); ?>
</label>
<p class="description">
<?php _e('禁用后将停止收集用户行为数据,但已存在的推荐功能仍可工作。', 'fup'); ?>
</p>
<?php
}
public function render_recommendation_limit_field() {
$options = get_option('fup_settings');
$limit = isset($options['recommendation_limit']) ? $options['recommendation_limit'] : 5;
?>
<input type="number" name="fup_settings[recommendation_limit]"
value="<?php echo esc_attr($limit); ?>" min="1" max="20" />
<p class="description">
<?php _e('每次推荐显示的最大文章数量。', 'fup'); ?>
</p>
<?php
}
public function render_decay_rate_field() {
$options = get_option('fup_settings');
$decay_rate = isset($options['interest_decay_rate']) ? $options['interest_decay_rate'] : 0.95;
?>
<input type="number" name="fup_settings[interest_decay_rate]"
step="0.01" min="0.8" max="1.0"
value="<?php echo esc_attr($decay_rate); ?>" />
<p class="description">
<?php _e('用户兴趣的衰减速率,值越小衰减越快。建议值:0.95', 'fup'); ?>
</p>
<?php
}
public function render_exclude_categories_field() {
$options = get_option('fup_settings');
$excluded = isset($options['exclude_categories']) ? $options['exclude_categories'] : array();
$categories = get_categories(array('hide_empty' => false));
foreach ($categories as $category) {
$checked = in_array($category->term_id, $excluded) ? 'checked' : '';
echo '<label style="display: block; margin: 5px 0;">';
