PHP项目怎样实现热门内容排序?

wen PHP项目 56

本文目录导读:

PHP项目怎样实现热门内容排序?

  1. 核心难点
  2. 方案一:基于浏览量/点赞量的简单排序(适合小型项目)
  3. 方案二:基于“时间窗口”的热度排序(推荐常用)
  4. 方案四:结合 Redis 的实时排行榜(高性能)
  5. 综合建议(项目落地指南)
  6. 最后提醒:防刷与限流

在PHP项目中实现热门内容排序,通常需要结合数据采集(计数)缓存排序算法时效性权重,下面是一个从简单到复杂的完整方案。

核心难点

热门排序不是简单的“总浏览量排序”,因为:

  1. 霸榜:一个去年的爆款文章会永远排在第一。
  2. 数据量增长慢即使当前很火,也需要很长时间才能积累足够数据。

大部分方案会引入时间衰减(Time Decay),让近期数据权重更高。


基于浏览量/点赞量的简单排序(适合小型项目)

这是最简单的实现,但不推荐用于新闻或信息流类应用。

SQL 示例:

-- 按总浏览量排序
SELECT * FROM articles ORDER BY views DESC LIMIT 20;
-- 按总点赞数排序
SELECT * FROM articles ORDER BY likes DESC LIMIT 20;

问题:如上所述,无法反映“近期热度”。


基于“时间窗口”的热度排序(推荐常用)

只计算最近一段时间内的活跃度,近7天”或“近24小时”。

实现方式:

  1. 设计数据表:记录每一次行为。

    CREATE TABLE article_actions (
     id INT AUTO_INCREMENT PRIMARY KEY,
     article_id INT,
     action_type ENUM('view', 'like', 'comment'),
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
  2. PHP 查询逻辑

    <?php
    // 获取最近7天热度最高的文章
    $window = date('Y-m-d H:i:s', strtotime('-7 days'));

$sql = "SELECT article_id, COUNT(*) as hot_score FROM article_actions WHERE created_at >= :window GROUP BY article_id ORDER BY hot_score DESC LIMIT 20";

$stmt = $pdo->prepare($sql); $stmt->execute(['window' => $window]); $hotArticles = $stmt->fetchAll(); ?>


**优点**:能真实反映当前趋势。  
**缺点**:如果数据量极大(每天数亿条),直接查询日志表会很慢,需要缓存。
---
### 方案三:Hacker News / Reddit 算法(专业级、时效性强)
这类算法能同时兼顾**热度**和**时效性**,让“刚刚发布且迅速获得好评”的内容立刻冲上榜首。
#### 1. 简化版 Hacker News 算法(PHP 实现)
公式:`Score = (点赞数 - 反对数) / (时间差(小时) + 2)^G`
```php
<?php
function calculateHotScore($likes, $dislikes, $createdAt) {
    $gravity = 1.8; // 重力因子,越大新内容越容易上来
    $score = $likes - $dislikes;
    $hoursAgo = (time() - strtotime($createdAt)) / 3600;
    // 防止除零
    $hoursAgo = max($hoursAgo, 0.5); 
    return $score / pow(($hoursAgo + 2), $gravity);
}

用法:每次展示列表时,遍历所有候选文章计算评分,然后排序。
缺点:计算量稍大,需要缓存结果。

Reddit 的“热门”算法(前端排序)

Reddit 使用威尔逊区间(Wilson Score)下界来排序,考虑了样本量小带来的置信度问题。

PHP 库示例(需额外安装 stats 扩展或用纯 PHP 实现):

<?php
function wilsonScore($ups, $downs, $confidence = 0.95) {
    $n = $ups + $downs;
    if ($n == 0) return 0;
    $z = 1.96; // 对应 95% 置信区间
    $phat = $ups / $n;
    $num = ($phat + $z*$z/(2*$n) - $z * sqrt(($phat*(1-$phat)+$z*$z/(4*$n))/$n));
    $den = 1 + $z*$z/$n;
    return $num / $den;
}
// 排序时直接对每篇文章计算该值
?>

应用场景:评论排序、需要平衡小样本数据的场景。


结合 Redis 的实时排行榜(高性能)

对于高并发场景(如新闻门户、短视频),不要每次从数据库实时计算。

推荐架构:Redis Sorted Set(有序集合)

步骤:

  1. 当用户有操作时(如阅读、点赞),PHP 后台立即更新 Redis。

    $redis->zIncrBy('hot:articles:score', 1, $articleId); // 增加分数
  2. 定义获取 Top N 的接口

    $topIds = $redis->zRevRange('hot:articles:score', 0, 19, true);
    // 返回 [article_id => score] 的数组

实现时效性更复杂

  • 可以每天创建一个新的 key,如 hot:articles:2025-04-08
  • 查询时合并多个 key。
  • 或使用定时任务(Cron)将所有“过久”的分数乘以一个衰减因子(如 0.9)。

示例:定时衰减脚本(PHP CLI):

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'hot:articles:score';
$articles = $redis->zRange($key, 0, -1, true);
foreach ($articles as $id => $score) {
    $newScore = $score * 0.95; // 每天衰减5%
    $redis->zAdd($key, $newScore, $id);
}

综合建议(项目落地指南)

项目规模 推荐方案 备注
个人博客 / 小型CMS 方案一 + 时间窗口 加一个 updated_at 索引,简单可靠
中型社区 / 新闻站 方案二 + Redis缓存 使用Cron每5分钟更新一次缓存列表
高并发产品(如视频、问答) 方案四 + 时间衰减 全部经由Redis,完全避免数据库查询排序
需要精确排序(如评论) 方案三(Wilson Score) 解决“1赞0踩 vs 100赞1踩”的公平性

最后提醒:防刷与限流

热度排序极易被刷:

  1. 去重:用户IP、用户ID 对同一篇文章的重复操作,一定时间内只计一次。
  2. 设置阈值:单IP / 单用户 每天的操作上限。
  3. 使用验证码:频繁操作时弹出。

示例降频逻辑(PHP + Redis):

$key = 'action:'.$userId.':'.$articleId;
if ($redis->exists($key)) {
    // 今天已经操作过,忽略
    return false;
}
$redis->setex($key, 86400, 1);
// 继续执行点赞等逻辑

抱歉,评论功能暂时关闭!