PHP项目评分与Rank

wen PHP项目 2

本文目录导读:

PHP项目评分与Rank

  1. 基础评分系统实现
  2. Rank排名算法实现
  3. 带权重的多维度评分
  4. 性能优化建议
  5. 前端展示示例
  6. API接口示例

我将为你详细介绍PHP项目中实现评分与排名功能的几种常见方案。

基础评分系统实现

数据库表设计

-- 评分表
CREATE TABLE ratings (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    project_id INT NOT NULL,
    score DECIMAL(3,1) NOT NULL,  -- 1.0-5.0
    comment TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY unique_rating (user_id, project_id),
    INDEX idx_project_score (project_id, score)
);
-- 项目表(添加评分统计字段)
CREATE TABLE projects (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    avg_score DECIMAL(3,1) DEFAULT 0.0,
    rating_count INT DEFAULT 0,
    -- 其他项目字段...
);

PHP评分操作类

<?php
class RatingSystem {
    private $db;
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    /**
     * 提交评分
     */
    public function submitRating(int $userId, int $projectId, float $score, string $comment = ''): bool {
        try {
            $this->db->beginTransaction();
            // 检查是否已评分
            $existing = $this->getUserRating($userId, $projectId);
            if ($existing) {
                // 更新评分
                $stmt = $this->db->prepare("
                    UPDATE ratings 
                    SET score = :score, comment = :comment 
                    WHERE user_id = :user_id AND project_id = :project_id
                ");
            } else {
                // 新增评分
                $stmt = $this->db->prepare("
                    INSERT INTO ratings (user_id, project_id, score, comment) 
                    VALUES (:user_id, :project_id, :score, :comment)
                ");
            }
            $stmt->execute([
                'user_id' => $userId,
                'project_id' => $projectId,
                'score' => $score,
                'comment' => $comment
            ]);
            // 更新项目评分统计
            $this->updateProjectStats($projectId);
            $this->db->commit();
            return true;
        } catch (Exception $e) {
            $this->db->rollBack();
            error_log("Rating submission failed: " . $e->getMessage());
            return false;
        }
    }
    /**
     * 获取用户评分
     */
    public function getUserRating(int $userId, int $projectId): ?array {
        $stmt = $this->db->prepare("
            SELECT * FROM ratings 
            WHERE user_id = :user_id AND project_id = :project_id
        ");
        $stmt->execute(['user_id' => $userId, 'project_id' => $projectId]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }
    /**
     * 更新项目评分统计
     */
    private function updateProjectStats(int $projectId): void {
        $this->db->prepare("
            UPDATE projects p
            SET 
                avg_score = (
                    SELECT ROUND(AVG(score), 1) 
                    FROM ratings 
                    WHERE project_id = :project_id
                ),
                rating_count = (
                    SELECT COUNT(*) 
                    FROM ratings 
                    WHERE project_id = :project_id
                )
            WHERE p.id = :project_id
        ")->execute(['project_id' => $projectId]);
    }
}

Rank排名算法实现

简单排名(基于平均分)

<?php
class RankingSystem {
    private $db;
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    /**
     * 获取项目排名列表(基于平均分)
     */
    public function getRankingByAverage(int $limit = 10, int $offset = 0): array {
        $stmt = $this->db->prepare("
            SELECT 
                p.*,
                p.avg_score,
                p.rating_count,
                @rank := @rank + 1 as rank
            FROM projects p
            CROSS JOIN (SELECT @rank := 0) r
            WHERE p.rating_count > 0
            ORDER BY p.avg_score DESC, p.rating_count DESC
            LIMIT :limit OFFSET :offset
        ");
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

贝叶斯平均排名(推荐算法)

<?php
class BayesianRanking {
    // 贝叶斯平均公式: (avg_all * m + sum_score) / (m + n)
    // m = 最小评分次数阈值
    // n = 该项目评分次数
    public function getBayesianRanking(int $m = 10, int $limit = 10): array {
        global $db;
        // 计算所有项目的平均评分
        $stmt = $db->query("SELECT AVG(avg_score) as avg_all FROM projects WHERE rating_count > 0");
        $avgAll = $stmt->fetchColumn();
        $stmt = $db->prepare("
            SELECT 
                p.*,
                (($avgAll * :m) + (p.avg_score * p.rating_count)) / (:m + p.rating_count) as bayesian_score
            FROM projects p
            WHERE p.rating_count > 0
            ORDER BY bayesian_score DESC
            LIMIT :limit
        ");
        $stmt->bindValue(':m', $m, PDO::PARAM_INT);
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->execute();
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 添加排名
        $rank = 0;
        foreach ($results as &$row) {
            $row['rank'] = ++$rank;
        }
        return $results;
    }
}

威尔逊区间评分(更高级)

<?php
class WilsonScoreRanking {
    /**
     * 计算威尔逊下限(用于排序)
     * 适合处理评分数据不确定性
     */
    public function getWilsonScoreRanking(int $limit = 10): array {
        global $db;
        // 置信度z值 (95% CI -> 1.96)
        $z = 1.96;
        $stmt = $db->prepare("
            SELECT 
                p.*,
                (
                    (p.avg_score / 5.0 + $z * $z / (2 * p.rating_count) 
                     - $z * SQRT((p.avg_score / 5.0 * (1 - p.avg_score / 5.0) + $z * $z / (4 * p.rating_count)) / p.rating_count)
                    ) / (1 + $z * $z / p.rating_count)
                ) as wilson_score
            FROM projects p
            WHERE p.rating_count > 0
            ORDER BY wilson_score DESC
            LIMIT :limit
        ");
        $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

带权重的多维度评分

<?php
class MultiDimensionRating {
    private $weights = [
        'performance' => 0.3,
        'usability' => 0.4,
        'support' => 0.2,
        'cost' => 0.1
    ];
    public function calculateOverallScore(array $dimensionScores): float {
        $total = 0;
        foreach ($this->weights as $dimension => $weight) {
            if (isset($dimensionScores[$dimension])) {
                $total += $dimensionScores[$dimension] * $weight;
            }
        }
        return round($total, 1);
    }
}

性能优化建议

缓存层实现

<?php
class CachedRanking {
    private $cache;
    private $rankingSystem;
    public function __construct($cache, $rankingSystem) {
        $this->cache = $cache;
        $this->rankingSystem = $rankingSystem;
    }
    public function getRanking(int $limit = 10): array {
        $cacheKey = "ranking:top:{$limit}";
        // 尝试从缓存获取
        $cached = $this->cache->get($cacheKey);
        if ($cached !== null) {
            return json_decode($cached, true);
        }
        // 从数据库获取
        $ranking = $this->rankingSystem->getBayesianRanking($limit);
        // 缓存5分钟
        $this->cache->set($cacheKey, json_encode($ranking), 300);
        return $ranking;
    }
}

索引优化

-- 关键索引
ALTER TABLE ratings ADD INDEX idx_user_project (user_id, project_id);
ALTER TABLE ratings ADD INDEX idx_project_score (project_id, score);
ALTER TABLE projects ADD INDEX idx_avg_score (avg_score DESC, rating_count DESC);

前端展示示例

<!-- 星级评分组件 -->
<div class="star-rating" data-project-id="1">
    <div class="stars">
        <?php for ($i = 1; $i <= 5; $i++): ?>
            <span class="star" data-value="<?= $i ?>">★</span>
        <?php endfor; ?>
    </div>
    <div class="rating-info">
        <span class="average">4.5</span>
        <span class="count">(128 评分)</span>
    </div>
</div>
<script>
document.querySelectorAll('.star').forEach(star => {
    star.addEventListener('click', function() {
        const projectId = this.closest('.star-rating').dataset.projectId;
        const score = this.dataset.value;
        // AJAX提交评分
        fetch('/api/submit-rating', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({project_id: projectId, score: score})
        });
    });
});
</script>

API接口示例

<?php
// api/ratings.php
header('Content-Type: application/json');
$ratingSystem = new RatingSystem($db);
$action = $_GET['action'] ?? '';
$userId = $_SESSION['user_id'] ?? 0;
switch ($action) {
    case 'submit':
        $projectId = (int)$_POST['project_id'];
        $score = (float)$_POST['score'];
        $result = $ratingSystem->submitRating($userId, $projectId, $score);
        echo json_encode(['success' => $result]);
        break;
    case 'ranking':
        $limit = (int)($_GET['limit'] ?? 10);
        $ranking = (new BayesianRanking($db))->getBayesianRanking(10, $limit);
        echo json_encode($ranking);
        break;
}
## 最佳实践总结
1. **公平性**:使用贝叶斯平均防止刷分
2. **实时性**:异步更新评分统计
3. **安全性**:防止重复评分和刷分
4. **可扩展性**:支持多维度评分
5. **性能**:合理使用缓存和索引
根据你的具体需求选择合适的算法方案,对于小型项目,简单平均分就足够了;对于大型平台,建议使用贝叶斯或威尔逊区间算法。

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