You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基础匿名用户评分系统技术咨询:数据库与存储实现问题

嘿,作为刚接触Web开发的新手,你遇到的这些问题都是非常典型的入门坑,我来帮你一步步理清楚:

1. 数据库设计:当前方案的问题与更优选择

你当前设计的单表多列(每个项目对应一列)思路虽然直观,但有几个明显的弊端:

  • 扩展性极差:如果后续新增/删除评分项目,必须修改数据库表结构,这在生产环境中麻烦且风险高。
  • 数据冗余:大部分用户不会给所有68个项目评分,空值会浪费存储空间。
  • 统计效率低:计算单个项目的平均分需要跨所有行求和再取平均,SQL语句会非常冗长。

更优的方案是采用关系型数据库的规范化设计,用两张表存储数据:

表1:用户表(users)

字段名类型说明
idINT(11) AUTO_INCREMENT主键,自增用户ID
u_idVARCHAR(64)存储前端localStorage里的用户标识(或IP+UA组合)
created_atDATETIME用户创建时间

表2:评分明细表(ratings)

字段名类型说明
idINT(11) AUTO_INCREMENT主键
user_idINT(11)关联users.id,外键约束
item_indexINT(11)评分项目的索引(0-67)
scoreTINYINT(1)评分值(1=差,2=一般,3=好,0=未评分)

这个设计的优势:

  • 新增/删除项目完全不需要改表,直接操作评分数据即可。
  • 统计单个项目的平均分只需一行SQL:SELECT AVG(score) FROM ratings WHERE item_index = X
  • 数据存储更紧凑,没有冗余空值。
2. 修正PHP代码:实现“新增/更新”逻辑

你当前的代码有几个核心问题:

  1. 前端传的ratedArray是数组,直接用real_escape_string会把它转成字符串(比如Array),无法正确拆分存储。
  2. INSERT语句的列和值数量不匹配,语法错误。
  3. 更新用户评分的逻辑缺失。

下面是修正后的完整PHP代码,基于上面的规范化表结构:

<?php
// 连接数据库
$conn = new mysqli('localhost', 'root', '', 'db');
// 检查连接错误
if ($conn->connect_error) {
    die(json_encode(['error' => '数据库连接失败']));
}

if (isset($_POST['save'])) {
    // 获取前端参数,处理空值
    $uID = isset($_POST['uID']) ? $conn->real_escape_string($_POST['uID']) : '';
    $ratedArray = isset($_POST['ratedArray']) ? $_POST['ratedArray'] : [];
    
    // 校验评分数据格式
    if (!is_array($ratedArray)) {
        die(json_encode(['error' => '评分数据格式错误']));
    }

    $userId = 0;
    // 处理用户:无uID则新增,有则检查是否存在
    if (empty($uID)) {
        // 新增用户,这里用自增ID作为返回给前端的uID(也可以用uniqid生成唯一字符串)
        $conn->query("INSERT INTO users (created_at) VALUES (NOW())");
        $userId = $conn->insert_id;
        $returnUID = (string)$userId;
        // 如果想用自定义唯一标识,替换上面两行:
        // $returnUID = uniqid('user_', true);
        // $conn->query("INSERT INTO users (u_id, created_at) VALUES ('$returnUID', NOW())");
        // $userId = $conn->insert_id;
    } else {
        // 检查用户是否存在
        $sql = $conn->query("SELECT id FROM users WHERE u_id = '$uID'");
        if ($sql->num_rows > 0) {
            $userData = $sql->fetch_assoc();
            $userId = $userData['id'];
            $returnUID = $uID;
            // 删除旧评分(避免新旧数据冲突)
            $conn->query("DELETE FROM ratings WHERE user_id = $userId");
        } else {
            // 若uID不存在,视为新增用户
            $conn->query("INSERT INTO users (u_id, created_at) VALUES ('$uID', NOW())");
            $userId = $conn->insert_id;
            $returnUID = $uID;
        }
    }

    // 批量插入新评分
    foreach ($ratedArray as $itemIndex => $score) {
        // 过滤无效评分(只允许0-3的整数)
        $score = (int)$score;
        $itemIndex = (int)$itemIndex;
        if ($score < 0 || $score > 3 || $itemIndex < 0 || $itemIndex >= 68) {
            continue;
        }
        // 插入单条评分
        $conn->query("INSERT INTO ratings (user_id, item_index, score) VALUES ($userId, $itemIndex, $score)");
    }

    // 返回用户ID给前端,更新localStorage
    exit(json_encode(['id' => $returnUID]));
}
?>

前端注意事项:

你当前的data: { save: 1, uID: uID, ratedArray: ratedArray }写法是对的,jQuery会自动把数组转成ratedArray[]=1&ratedArray[]=2的格式,PHP能直接识别为数组。

3. 用IP替代userID的可靠性分析

用IP作为用户标识有一定便利性,但缺点远大于优点:

优点:

  • 无需依赖localStorage,用户清空缓存后不会丢失身份。
  • 实现简单,不用生成和存储用户ID。

缺点:

  • 准确性极低:同一IP下可能有多个用户(比如公司、家庭WiFi),会导致评分互相覆盖;移动用户的IP会频繁变化,同一个用户会被当成多个不同用户,重复评分。
  • 隐私风险:存储用户IP可能违反隐私法规(比如欧盟GDPR),需要额外的合规处理(比如匿名化存储)。
  • 防刷困难:恶意用户可以用代理IP批量刷分,难以识别。

替代建议:

  1. 优先保留localStorage存储用户ID的方案,同时后端记录用户IP和浏览器UA($_SERVER['HTTP_USER_AGENT']),作为辅助校验,防止同一IP生成大量用户ID刷分。
  2. 若要简化用户识别,可以用IP+浏览器UA的哈希值作为用户标识,比单纯用IP准确性高一些,但仍无法解决共享IP的问题。
  3. 增加防刷机制:限制同一IP在1小时内最多生成3个用户ID,或者同一用户ID的评分间隔不低于5分钟。

内容的提问来源于stack exchange,提问作者M P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:59:07