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

如何将React星级评分提交至MongoDB并按评分降序展示院校

院校评分提交存储+按评分降序展示实现方案

后端(Node.js + MongoDB/Mongoose)实现

现有Schema定义无需修改,核心是补全接口逻辑、替换无效的空条件查询:

1. 评分提交/更新接口

做幂等校验:同一用户对同一院校仅保留一条评分记录,重复提交直接更新原有分数,避免重复数据导致平均分计算错误。userId必须从服务端登录态读取,禁止前端传值伪造身份。

// 评分提交/更新接口
app.post('/api/rating/update', async (req, res) => {
  try {
    const { rating, collegeId } = req.body;
    const userId = req.user._id; // 从登录态获取当前用户ID

    // 参数合法性校验
    if (!Number.isInteger(rating) || rating < 1 || rating > 5 || !mongoose.isValidObjectId(collegeId)) {
      return res.status(400).json({ msg: '评分参数不合法' });
    }

    // 存在对应记录则更新,不存在则新建
    const record = await Rating.findOneAndUpdate(
      { userId, collegeId },
      { $set: { rating } },
      { upsert: true, new: true }
    );

    res.json({ code: 0, data: record });
  } catch (err) {
    res.status(500).json({ msg: '评分提交失败', error: err.message });
  }
});

2. 按平均评分降序的院校列表接口

通过MongoDB聚合管道直接计算每个院校的平均评分、评分人数,关联院校表信息后直接返回排序完成的结果,不需要前端拿到数据再排序:

app.get('/api/college/rank-list', async (req, res) => {
  try {
    const list = await Rating.aggregate([
      // 按院校ID分组计算统计值
      {
        $group: {
          _id: '$collegeId',
          avgRating: { $avg: '$rating' },
          totalRatings: { $sum: 1 }
        }
      },
      // 关联colleges集合取院校详情
      {
        $lookup: {
          from: 'colleges',
          localField: '_id',
          foreignField: '_id',
          as: 'collegeInfo'
        }
      },
      // 拆解关联返回的院校数组为对象
      { $unwind: '$collegeInfo' },
      // 排序规则:优先按平均分降序,同分按评分人数降序
      { $sort: { avgRating: -1, totalRatings: -1 } }
    ]);
    res.json({ code: 0, data: list });
  } catch (err) {
    res.status(500).json({ msg: '获取院校列表失败', error: err.message });
  }
});

如果需要查询单个用户对指定院校的已有评分,替换之前空条件的findOne({}),加上明确查询条件即可:

const userRating = await Rating.findOne({
  userId: req.user._id,
  collegeId: req.query.collegeId
}).populate('userId').populate('collegeId');

前端(React)代码修复

现有代码存在3个核心问题:子组件StarRating无法获取父组件的评分修改方法、星星高亮逻辑写死为固定值、提交评分时未传院校ID参数、变量名冲突导致无法获取选中的评分值。修复后完整代码如下:

const StarRating = (props) => {
  const { ratingValue, onChange } = props;
  return (
    <div>
      {Array(5).fill(0).map((_, idx) => {
        const currentStarVal = idx + 1;
        return (
          <label key={idx} style={{ cursor: 'pointer', marginRight: 4 }}>
            <input
              type="radio"
              name="rating"
              style={{ display: 'none' }}
              onChange={() => onChange(currentStarVal)}
              value={currentStarVal}
              checked={currentStarVal === ratingValue}
            />
            <FaStar 
              color={currentStarVal <= ratingValue ? "#01af93" : "#bbb"} 
              size={22}
            />
          </label>
        );
      })}
    </div>
  );
};

const Report = (props) => {
  const { advices } = useSelector((state) => state.advice);
  // 从路由/父组件props获取当前浏览的院校ID
  const currentCollegeId = props.match.params.collegeId || props.collegeId;
  const [rate, setRate] = useState(0);
  const [loading, setLoading] = useState(false);

  // 补全useEffect依赖,避免无限重渲染
  useEffect(() => {
    if (!advices) dispatch(fetchAdvices(history));
  }, [advices, dispatch, history]);

  // 拉取当前用户对该院校的已有评分
  useEffect(() => {
    async function fetchUserRating() {
      try {
        const res = await api.get(paths.FETCH_RATING, {
          params: { collegeId: currentCollegeId }
        });
        if (res.data?.rating) setRate(res.data.rating);
      } catch (error) {
        console.log(error);
      }
    }
    if (currentCollegeId) fetchUserRating();
  }, [currentCollegeId]);

  // 提交评分
  const submitRating = async () => {
    if (!rate || rate < 1 || rate > 5) {
      alert('请先选择星级评分');
      return;
    }
    setLoading(true);
    try {
      await api.post(paths.UPDATE_RATING, {
        rating: rate,
        collegeId: currentCollegeId
      });
      alert('评分提交成功');
    } catch (err) {
      console.log(err);
      alert('评分提交失败,请重试');
    } finally {
      setLoading(false);
    }
  };
   
  return (
    <Container> 
      <Grid>
        <Grid>
          <Card>
            <CardContent>
              <div>
                <StarRating onChange={(val) => setRate(val)} ratingValue={rate} />
                <button
                  onClick={submitRating}
                  disabled={loading}
                  style={{ marginTop: 12 }}
                >
                  {loading ? '提交中...' : '提交评分'}
                </button>
              </div>
            </CardContent>
          </Card>
        </Grid>
      </Grid>
    </Container>
  );
};

优化建议

  • 给rating集合的userId、collegeId字段建联合唯一索引,从数据库层面杜绝同一用户给同一院校重复评分的问题
  • 如果院校数据量大,聚合查询耗时较长,可以加定时任务定期计算院校平均分冗余到colleges集合中,进一步提升列表查询速度
  • 评分接口加频率限制,避免恶意刷分

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 22:01:09