PHP项目热门排行榜实现

wen PHP项目 2

本文目录导读:

PHP项目热门排行榜实现

  1. 数据库设计
  2. 核心热度计算类
  3. 控制器实现
  4. 路由配置
  5. 前端集成示例
  6. 定时任务配置
  7. Cron脚本
  8. 高级功能扩展
  9. 防刷机制
  10. 性能优化建议

我来为您提供一个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;
    }
}

性能优化建议

  1. 数据库优化

    • 创建复合索引:(type, hot_score)
    • 使用分区表按月分表
    • 定期清理过期日志
  2. 缓存策略

    • 排行榜结果缓存5分钟
    • 热门度分数缓存30分钟
    • 使用Redis实现高性能缓存
  3. 异步处理

    • 使用消息队列处理操作记录
    • 异步更新热度分数
    • 批量处理日志写入
  4. 配置调整

    • 根据业务调整权重参数
    • 动态调整时间衰减因子
    • 区分不同类型内容的权重

这个完整的排行榜实现包含了数据库设计、核心算法、缓存机制、防刷策略等关键功能,可以根据实际需求进行调整和扩展。

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