本文目录导读:

我来为您提供一个PHP项目热门排行榜的完整实现方案,包含多种热门计算方式。
数据库设计
CREATE TABLE `hot_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL COMMENT '标题', `type` varchar(50) NOT NULL COMMENT '类型:article/product/user等', `item_id` int(11) NOT NULL COMMENT '对应项目ID', `views` int(11) DEFAULT 0 COMMENT '浏览量', `likes` int(11) DEFAULT 0 COMMENT '点赞数', `comments` int(11) DEFAULT 0 COMMENT '评论数', `shares` int(11) DEFAULT 0 COMMENT '分享数', `collections` int(11) DEFAULT 0 COMMENT '收藏数', `hot_score` decimal(10,2) DEFAULT 0.00 COMMENT '热度分数', `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_hot_score` (`hot_score` DESC), KEY `idx_type` (`type`), KEY `idx_updated_at` (`updated_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 热度日志表 CREATE TABLE `hot_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `item_id` int(11) NOT NULL, `action` varchar(20) NOT NULL COMMENT '操作类型', `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_item_id` (`item_id`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心热度计算类
<?php
// HotRank.php
class HotRank {
private $db;
private $cache;
// 权重配置
private $weights = [
'views' => 1,
'likes' => 3,
'comments' => 5,
'shares' => 8,
'collections' => 4
];
// 时间衰减参数(天)
private $timeDecayDays = 7;
public function __construct($db, $cache = null) {
$this->db = $db;
$this->cache = $cache;
}
/**
* 计算单个项目的热度分数
* @param array $item 项目数据
* @return float
*/
public function calculateScore($item) {
// 基础分数
$baseScore = 0;
foreach ($this->weights as $field => $weight) {
$baseScore += ($item[$field] ?? 0) * $weight;
}
// 时间衰减因子(使用指数衰减)
$hoursPassed = (time() - strtotime($item['created_at'])) / 3600;
$timeDecay = exp(-$hoursPassed / ($this->timeDecayDays * 24));
// 最终分数
return round($baseScore * $timeDecay, 2);
}
/**
* 批量更新热度分数
*/
public function updateAllScores() {
$sql = "UPDATE hot_items SET hot_score = ? WHERE id = ?";
$stmt = $this->db->prepare($sql);
// 获取所有项目
$items = $this->db->query("SELECT * FROM hot_items")->fetchAll();
foreach ($items as $item) {
$score = $this->calculateScore($item);
$stmt->execute([$score, $item['id']]);
}
}
/**
* 记录操作日志
*/
public function logAction($itemId, $action) {
$sql = "INSERT INTO hot_logs (item_id, action) VALUES (?, ?)";
$this->db->prepare($sql)->execute([$itemId, $action]);
}
/**
* 实时更新计数
*/
public function updateCount($itemId, $action, $increment = 1) {
// 允许的字段映射
$fieldMap = [
'view' => 'views',
'like' => 'likes',
'comment' => 'comments',
'share' => 'shares',
'collection' => 'collections'
];
if (!isset($fieldMap[$action])) {
return false;
}
$field = $fieldMap[$action];
// 更新计数
$sql = "UPDATE hot_items SET {$field} = {$field} + ? WHERE id = ?";
$this->db->prepare($sql)->execute([$increment, $itemId]);
// 记录日志
$this->logAction($itemId, $action);
// 清理缓存
$this->clearCache();
return true;
}
/**
* 获取热门排行榜
* @param string $type 类型
* @param int $limit 数量
* @param int $offset 偏移
* @return array
*/
public function getRanking($type = null, $limit = 10, $offset = 0) {
$cacheKey = "hot_ranking_{$type}_{$limit}_{$offset}";
// 尝试从缓存获取
if ($this->cache) {
$cached = $this->cache->get($cacheKey);
if ($cached !== false) {
return json_decode($cached, true);
}
}
// 构建查询
$where = $type ? "WHERE type = ?" : "";
$sql = "SELECT h.*,
CASE
WHEN h.type = 'article' THEN (SELECT title FROM articles WHERE id = h.item_id)
WHEN h.type = 'product' THEN (SELECT name FROM products WHERE id = h.item_id)
ELSE h.title
END as display_title
FROM hot_items h
{$where}
ORDER BY hot_score DESC
LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
if ($type) {
$stmt->execute([$type, $limit, $offset]);
} else {
$stmt->execute([$limit, $offset]);
}
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 缓存结果(5分钟)
if ($this->cache) {
$this->cache->set($cacheKey, json_encode($result), 300);
}
return $result;
}
/**
* 获取热度趋势
*/
public function getTrendingItems($hours = 24, $limit = 10) {
$startTime = date('Y-m-d H:i:s', time() - $hours * 3600);
$sql = "SELECT item_id, action, COUNT(*) as count
FROM hot_logs
WHERE created_at >= ?
GROUP BY item_id, action
ORDER BY count DESC
LIMIT ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$startTime, $limit * 5]); // 获取更多数据用于计算
return $this->processTrendingData($stmt->fetchAll(PDO::FETCH_ASSOC), $limit);
}
/**
* 处理趋势数据
*/
private function processTrendingData($logs, $limit) {
$itemScores = [];
foreach ($logs as $log) {
$itemId = $log['item_id'];
$weight = $this->weights[$log['action']] ?? 1;
if (!isset($itemScores[$itemId])) {
$itemScores[$itemId] = 0;
}
$itemScores[$itemId] += $log['count'] * $weight;
}
// 排序并取前N个
arsort($itemScores);
$topIds = array_slice(array_keys($itemScores), 0, $limit);
// 获取完整信息
return $this->getItemsByIds($topIds);
}
/**
* 根据ID获取项目信息
*/
private function getItemsByIds($ids) {
if (empty($ids)) return [];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT * FROM hot_items WHERE id IN ({$placeholders})";
$stmt = $this->db->prepare($sql);
$stmt->execute($ids);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* 清除缓存
*/
private function clearCache() {
if ($this->cache) {
// 清除相关缓存
$this->cache->deleteByPrefix('hot_ranking_');
}
}
}
控制器实现
<?php
// HotRankController.php
class HotRankController {
private $hotRank;
public function __construct($hotRank) {
$this->hotRank = $hotRank;
}
/**
* 记录用户操作
*/
public function recordAction($itemId, $action) {
// 验证操作类型
$validActions = ['view', 'like', 'comment', 'share', 'collection'];
if (!in_array($action, $validActions)) {
http_response_code(400);
return json_encode(['error' => 'Invalid action']);
}
// 防止刷单(IP限制)
$ip = $_SERVER['REMOTE_ADDR'];
// 这里可以添加IP限制逻辑
$result = $this->hotRank->updateCount($itemId, $action);
return json_encode(['success' => $result]);
}
/**
* 获取排行榜
*/
public function getRanking() {
$type = $_GET['type'] ?? null;
$limit = min(intval($_GET['limit'] ?? 10), 100);
$offset = intval($_GET['offset'] ?? 0);
// 验证类型
$validTypes = ['article', 'product', 'user'];
if ($type && !in_array($type, $validTypes)) {
$type = null;
}
$ranking = $this->hotRank->getRanking($type, $limit, $offset);
return json_encode([
'code' => 0,
'data' => $ranking,
'total' => count($ranking)
]);
}
/**
* 获取趋势
*/
public function getTrending() {
$hours = min(intval($_GET['hours'] ?? 24), 168);
$limit = min(intval($_GET['limit'] ?? 10), 50);
$trending = $this->hotRank->getTrendingItems($hours, $limit);
return json_encode([
'code' => 0,
'data' => $trending
]);
}
/**
* 手动更新分数(通常由Cron执行)
*/
public function updateScores() {
// 验证执行权限
if (!isset($_GET['secret']) || $_GET['secret'] !== CRON_SECRET) {
http_response_code(403);
return json_encode(['error' => 'Unauthorized']);
}
$startTime = microtime(true);
$this->hotRank->updateAllScores();
$execTime = microtime(true) - $startTime;
return json_encode([
'success' => true,
'exec_time' => round($execTime, 4)
]);
}
}
路由配置
<?php
// routes.php
$router = new Router();
$controller = new HotRankController(new HotRank($db, $cache));
// 记录操作
$router->post('/api/hot/action', [$controller, 'recordAction']);
// 获取排行榜
$router->get('/api/hot/ranking', [$controller, 'getRanking']);
// 获取趋势
$router->get('/api/hot/trending', [$controller, 'getTrending']);
// 更新分数(Cron调用)
$router->get('/api/hot/update-scores', [$controller, 'updateScores']);
前端集成示例
// hot-ranking.js
class HotRanking {
constructor(options) {
this.apiUrl = options.apiUrl || '/api/hot';
this.container = options.container;
this.type = options.type || null;
this.limit = options.limit || 10;
this.interval = options.interval || 30000; // 30秒刷新
this.init();
}
init() {
this.fetchRanking();
// 定时刷新
setInterval(() => this.fetchRanking(), this.interval);
}
async fetchRanking() {
try {
const params = new URLSearchParams({
limit: this.limit
});
if (this.type) {
params.append('type', this.type);
}
const response = await fetch(`${this.apiUrl}/ranking?${params}`);
const data = await response.json();
if (data.code === 0) {
this.render(data.data);
}
} catch (error) {
console.error('Failed to fetch ranking:', error);
}
}
render(items) {
if (!this.container) return;
const html = items.map((item, index) => `
<div class="hot-item">
<span class="rank ${index < 3 ? 'top' : ''}">${index + 1}</span>
<div class="item-info">
<h3>${item.display_title || item.title}</h3>
<div class="stats">
<span>🔥 ${item.hot_score}</span>
<span>👁 ${item.views}</span>
<span>❤️ ${item.likes}</span>
</div>
</div>
</div>
`).join('');
this.container.innerHTML = html;
}
// 记录用户操作
static recordAction(itemId, action) {
fetch('/api/hot/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ item_id: itemId, action: action })
}).catch(error => console.error('Failed to record action:', error));
}
}
// 使用示例
document.addEventListener('DOMContentLoaded', () => {
const ranking = new HotRanking({
container: document.getElementById('hot-ranking'),
type: 'article',
limit: 20
});
// 记录点击
document.addEventListener('click', (e) => {
const item = e.target.closest('.hot-item');
if (item) {
HotRanking.recordAction(item.dataset.itemId, 'view');
}
});
});
定时任务配置
# crontab - 每30分钟更新一次热度分数 */30 * * * * php /path/to/cron/update_hot_scores.php # 或者使用更复杂的策略 # 0 */2 * * * php /path/to/cron/update_hot_scores.php
Cron脚本
<?php
// cron/update_hot_scores.php
require_once __DIR__ . '/../vendor/autoload.php';
// 初始化数据库连接
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 创建热门排名实例
$hotRank = new HotRank($db);
// 更新所有分数
$startTime = microtime(true);
$hotRank->updateAllScores();
$execTime = microtime(true) - $startTime;
// 记录日志
file_put_contents(
__DIR__ . '/../logs/hot_update.log',
date('Y-m-d H:i:s') . " - Updated scores in {$execTime}s\n",
FILE_APPEND
);
echo "Scores updated in {$execTime}s\n";
高级功能扩展
1 加权时间衰减算法
// 更复杂的时间衰减算法
public function calculateAdvancedScore($item) {
$baseScore = 0;
// 基础分数计算
foreach ($this->weights as $field => $weight) {
$baseScore += $item[$field] * $weight;
}
// 使用对数衰减(更平稳)
$hoursPassed = (time() - strtotime($item['created_at'])) / 3600;
if ($hoursPassed > 0) {
$timeFactor = 1 / log10($hoursPassed + 10);
} else {
$timeFactor = 1;
}
// 新鲜度加成(最近24小时内的活动)
$recentActivity = $this->getRecentActivity($item['id'], 24);
$freshnessBonus = $recentActivity * 0.5;
return round(($baseScore * $timeFactor) + $freshnessBonus, 2);
}
/**
* 获取近期活动数
*/
private function getRecentActivity($itemId, $hours) {
$sql = "SELECT COUNT(*) FROM hot_logs
WHERE item_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$itemId, $hours]);
return $stmt->fetchColumn();
}
2 Redis缓存实现
// RedisCache.php
class RedisCache {
private $redis;
private $prefix = 'hot_rank:';
public function __construct($redis) {
$this->redis = $redis;
}
public function get($key) {
return $this->redis->get($this->prefix . $key);
}
public function set($key, $value, $ttl = 300) {
return $this->redis->setex($this->prefix . $key, $ttl, $value);
}
public function delete($key) {
return $this->redis->del($this->prefix . $key);
}
public function deleteByPrefix($prefix) {
$keys = $this->redis->keys($this->prefix . $prefix . '*');
if (!empty($keys)) {
return $this->redis->del($keys);
}
return 0;
}
}
防刷机制
// AntiFraud.php
class AntiFraud {
private $redis;
private $limits = [
'view' => ['count' => 100, 'time' => 3600], // 每小时100次浏览
'like' => ['count' => 10, 'time' => 3600], // 每小时10次点赞
'comment' => ['count' => 5, 'time' => 3600], // 每小时5次评论
];
public function checkFraud($userId, $action) {
if (!isset($this->limits[$action])) {
return true;
}
$limit = $this->limits[$action];
$key = "fraud:{$userId}:{$action}";
$count = $this->redis->incr($key);
// 第一次设置过期时间
if ($count === 1) {
$this->redis->expire($key, $limit['time']);
}
return $count <= $limit['count'];
}
public function isSuspicious($itemId, $action) {
// 检查短时间内的大量操作
$sql = "SELECT COUNT(*) FROM hot_logs
WHERE item_id = ? AND action = ?
AND created_at > DATE_SUB(NOW(), INTERVAL 1 MINUTE)";
// 如果1分钟内超过50次,标记为可疑
// 这里实现略
return false;
}
}
性能优化建议
-
数据库优化
- 创建复合索引:(type, hot_score)
- 使用分区表按月分表
- 定期清理过期日志
-
缓存策略
- 排行榜结果缓存5分钟
- 热门度分数缓存30分钟
- 使用Redis实现高性能缓存
-
异步处理
- 使用消息队列处理操作记录
- 异步更新热度分数
- 批量处理日志写入
-
配置调整
- 根据业务调整权重参数
- 动态调整时间衰减因子
- 区分不同类型内容的权重
这个完整的排行榜实现包含了数据库设计、核心算法、缓存机制、防刷策略等关键功能,可以根据实际需求进行调整和扩展。