PHP项目怎么实现用户自定义评分?

wen java案例 6

本文目录导读:

PHP项目怎么实现用户自定义评分?

  1. 核心流程
  2. 第一步:数据库设计
  3. 第二步:后端PHP逻辑 (API接口)
  4. 第三步:前端实现 (HTML + JavaScript)
  5. 进阶优化与安全考虑
  6. 完整示例的数据库查询 (获取页面数据)

这是一个很好的问题!在PHP项目中实现用户自定义评分(比如用户可以对文章、商品进行1-5星评分),通常涉及前端展示、后端逻辑、数据库存储三个部分。

下面是一个从基础到进阶的完整实现方案,包含代码示例和安全考虑。

核心流程

  1. 用户前端:点击星星,发送Ajax请求。
  2. 后端接收:验证用户登录、防止重复评分、存储数据。
  3. 数据库更新:更新评分表,并同步更新主表的平均分/评分人数。
  4. 前端反馈:显示更新后的分数和用户评分状态。

第一步:数据库设计

你需要两张主要的表(或者一张表加缓存字段)。

评分记录表 (ratings) 存储每个用户对每个目标的评分。

CREATE TABLE `ratings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `target_id` int(11) NOT NULL COMMENT '被评分对象ID ( article_id, product_id)',
  `target_type` varchar(50) NOT NULL DEFAULT 'article' COMMENT '评分对象类型',
  `score` tinyint(1) NOT NULL COMMENT '评分值 1-5',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_rating` (`user_id`, `target_id`, `target_type`), -- 关键:防止同一用户多次评分
  KEY `target_score` (`target_id`, `target_type`, `score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

被评分对象表(articles 在主表中增加冗余字段,避免每次查询都去ratings表做SUM/COUNT,提高性能。

ALTER TABLE articles 
  ADD COLUMN `avg_score` decimal(2,1) NOT NULL DEFAULT '0.0' COMMENT '平均分',
  ADD COLUMN `score_count` int(11) NOT NULL DEFAULT '0' COMMENT '评分人数';

第二步:后端PHP逻辑 (API接口)

这里假设你有一个用户认证系统,$_SESSION['user_id'] 代表当前登录用户。

文件:rate.php

<?php
session_start();
header('Content-Type: application/json');
// 1. 权限检查
if (!isset($_SESSION['user_id'])) {
    echo json_encode(['success' => false, 'message' => '请先登录']);
    exit;
}
$userId = (int)$_SESSION['user_id'];
$targetId = (int)$_POST['target_id'];
$targetType = $_POST['target_type'] ?? 'article'; // 白名单校验
$score = (int)$_POST['score'];
// 2. 输入校验
if ($score < 1 || $score > 5) {
    echo json_encode(['success' => false, 'message' => '评分值必须在1-5之间']);
    exit;
}
$allowedTypes = ['article', 'product', 'video']; // 白名单
if (!in_array($targetType, $allowedTypes)) {
    echo json_encode(['success' => false, 'message' => '无效的评分类型']);
    exit;
}
// 3. 数据库操作 (PDO示例)
try {
    $pdo = new PDO('mysql:host=localhost;dbname=yourdb;charset=utf8mb4', 'username', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->beginTransaction();
    // 3.1 插入或更新评分 (使用 ON DUPLICATE KEY UPDATE 实现“修改评分”)
    $sql = "INSERT INTO ratings (user_id, target_id, target_type, score) 
            VALUES (:user_id, :target_id, :target_type, :score)
            ON DUPLICATE KEY UPDATE score = VALUES(score)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        ':user_id' => $userId,
        ':target_id' => $targetId,
        ':target_type' => $targetType,
        ':score' => $score
    ]);
    // 3.2 更新主表的平均分和评分人数
    // 计算此对象的平均分
    $avgSql = "SELECT AVG(score) as avg_score, COUNT(*) as score_count 
               FROM ratings 
               WHERE target_id = :target_id AND target_type = :target_type";
    $avgStmt = $pdo->prepare($avgSql);
    $avgStmt->execute([':target_id' => $targetId, ':target_type' => $targetType]);
    $result = $avgStmt->fetch(PDO::FETCH_ASSOC);
    $avgScore = round($result['avg_score'], 1);
    $count = (int)$result['score_count'];
    // 动态更新主表 (根据目标类型选择不同表)
    $updateSql = "UPDATE articles 
                  SET avg_score = :avg_score, score_count = :score_count 
                  WHERE id = :target_id";
    $updateStmt = $pdo->prepare($updateSql);
    $updateStmt->execute([
        ':avg_score' => $avgScore,
        ':score_count' => $count,
        ':target_id' => $targetId
    ]);
    $pdo->commit();
    echo json_encode([
        'success' => true,
        'new_avg' => $avgScore,
        'new_count' => $count,
        'your_score' => $score
    ]);
} catch (PDOException $e) {
    $pdo->rollBack();
    // 实际项目应记录日志,不暴露给用户
    echo json_encode(['success' => false, 'message' => '评分失败,请稍后重试']);
}

第三步:前端实现 (HTML + JavaScript)

使用CSS星星和Ajax请求。

HTML结构:

<div class="rating-widget" data-target-id="123" data-target-type="article">
    <!-- 显示当前平均分 -->
    <div class="rating-average">
        当前评分:<span class="avg-score">0.0</span> (<span class="score-count">0</span>人)
    </div>
    <!-- 星星区域 -->
    <div class="stars">
        <span class="star" data-value="1">☆</span>
        <span class="star" data-value="2">☆</span>
        <span class="star" data-value="3">☆</span>
        <span class="star" data-value="4">☆</span>
        <span class="star" data-value="5">☆</span>
    </div>
    <p class="rating-message" style="color:green;"></p>
</div>

CSS样式 (简单示例):

.stars {
    cursor: pointer;
    font-size: 30px;
    color: #ddd;
}
.stars .star.active, 
.stars .star:hover,
.stars .star:hover ~ .star {
    color: #ffd700; /* 金色 */
}

JavaScript (原生或jQuery示例):

// 假设使用 jQuery
$(document).ready(function() {
    $('.star').on('click', function() {
        var $this = $(this);
        var value = $this.data('value');
        var $widget = $this.closest('.rating-widget');
        var targetId = $widget.data('target-id');
        var targetType = $widget.data('target-type');
        var $message = $widget.find('.rating-message');
        // 发送Ajax
        $.post('rate.php', {
            target_id: targetId,
            target_type: targetType,
            score: value
        }, function(response) {
            if (response.success) {
                // 更新显示
                $widget.find('.avg-score').text(response.new_avg);
                $widget.find('.score-count').text(response.new_count);
                $message.text('评分成功!').fadeIn().delay(2000).fadeOut();
                // 高亮用户选择的星星
                $widget.find('.star').each(function() {
                    if ($(this).data('value') <= response.your_score) {
                        $(this).addClass('active');
                    } else {
                        $(this).removeClass('active');
                    }
                });
            } else {
                $message.text(response.message).css('color', 'red').fadeIn().delay(3000).fadeOut();
            }
        }, 'json');
    });
});

进阶优化与安全考虑

  1. 防刷评分

    • IP限制:对同一IP在短时间内限制评分次数。
    • Token验证:使用CSRF token防止跨站请求伪造。
  2. 性能优化

    • 缓存平均分:如果访问量大,不要每次都计算AVG(),可以考虑:
      • 使用Redis缓存target_id对应的平均分和人数。
      • 或使用MySQL的触发器/事件在写入ratings表时自动更新主表(上面示例已实现)。
    • 读写分离:写操作走主库,读操作走从库。
  3. 用户体验

    • 禁用重复点击:Ajax请求发送期间,禁用星星点击或显示加载状态。
    • 回滚机制:如果Ajax失败,显示错误并允许重新点击。
    • 显示用户当前评分:页面加载时,查询当前用户是否已经评分,并高亮显示。
  4. 数据库事务:上面代码使用了事务,确保ratings表和主表的avg_score一致。

  5. 防止SQL注入:严格使用参数绑定或预处理语句(上面示例已使用PDO)。

完整示例的数据库查询 (获取页面数据)

当你渲染文章列表或详情页时,需要获取评分数据:

SELECT id, title, avg_score, score_count FROM articles WHERE id = :id;

这个方案实现了:

  • 可复用:通过target_type字段支持多种对象(文章、商品、评论)。
  • 防重复:使用唯一索引。
  • 高性能:主表保存平均分和人数,避免每次查询都计算。
  • 安全:输入校验、白名单、参数绑定、事务。

你可以根据项目规模选择使用简单的文件缓存或Redis来进一步优化。

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