本文目录导读:

在PHP项目中实现热门内容排序,通常需要结合数据采集(计数)、缓存、排序算法和时效性权重,下面是一个从简单到复杂的完整方案。
核心难点
热门排序不是简单的“总浏览量排序”,因为:
- 霸榜:一个去年的爆款文章会永远排在第一。
- 数据量增长慢即使当前很火,也需要很长时间才能积累足够数据。
大部分方案会引入时间衰减(Time Decay),让近期数据权重更高。
基于浏览量/点赞量的简单排序(适合小型项目)
这是最简单的实现,但不推荐用于新闻或信息流类应用。
SQL 示例:
-- 按总浏览量排序 SELECT * FROM articles ORDER BY views DESC LIMIT 20; -- 按总点赞数排序 SELECT * FROM articles ORDER BY likes DESC LIMIT 20;
问题:如上所述,无法反映“近期热度”。
基于“时间窗口”的热度排序(推荐常用)
只计算最近一段时间内的活跃度,近7天”或“近24小时”。
实现方式:
-
设计数据表:记录每一次行为。
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 ); -
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(有序集合)
步骤:
-
当用户有操作时(如阅读、点赞),PHP 后台立即更新 Redis。
$redis->zIncrBy('hot:articles:score', 1, $articleId); // 增加分数 -
定义获取 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踩”的公平性 |
最后提醒:防刷与限流
热度排序极易被刷:
- 去重:用户IP、用户ID 对同一篇文章的重复操作,一定时间内只计一次。
- 设置阈值:单IP / 单用户 每天的操作上限。
- 使用验证码:频繁操作时弹出。
示例降频逻辑(PHP + Redis):
$key = 'action:'.$userId.':'.$articleId;
if ($redis->exists($key)) {
// 今天已经操作过,忽略
return false;
}
$redis->setex($key, 86400, 1);
// 继续执行点赞等逻辑