如何将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
相关产品推荐
相关产品推荐

