如何在不修改Mongoose模型的前提下实现管理员查看班级学生分数
解决方案:实现Admin查看本班学生所有分数
根据你的模型结构,我们可以通过分步链式查询或Mongoose聚合管道来实现需求,下面提供两种可行方案:
方案1:分步查询(适合新手理解)
这种方式逻辑清晰、易调试,分三次查询完成关联:
const express = require('express'); const router = express.Router(); const Kelas = require('../models/Kelas'); const Quiz = require('../models/Quiz'); const Nilai = require('../models/Nilai'); // 假设已通过中间件获取当前登录Admin的ID,存储在req.admin._id router.get('/admin/scores', async (req, res) => { try { const adminId = req.admin._id; // 1. 获取当前Admin创建的所有班级ID const adminClasses = await Kelas.find({ admin: adminId }).select('_id'); const classIds = adminClasses.map(cls => cls._id); if (classIds.length === 0) { return res.status(200).json({ success: true, data: [], message: '暂无班级数据' }); } // 2. 获取这些班级对应的所有测验ID const quizzes = await Quiz.find({ kelas: { $in: classIds } }).select('_id'); const quizIds = quizzes.map(q => q._id); if (quizIds.length === 0) { return res.status(200).json({ success: true, data: [], message: '暂无测验数据' }); } // 3. 获取所有相关分数,同时关联用户、测验和班级信息 const studentScores = await Nilai.find({ quiz: { $in: quizIds } }) .populate({ path: 'user', select: 'nama email' // 仅返回必要字段,避免暴露密码 }) .populate({ path: 'quiz', select: 'nama', populate: { path: 'kelas', select: 'nama' // 关联显示班级名称 } }) .sort({ createdAt: -1 }); // 按分数创建时间倒序排列 res.status(200).json({ success: true, data: studentScores }); } catch (error) { res.status(500).json({ success: false, message: '获取分数失败', error: error.message }); } }); module.exports = router;
方案1说明
- 先筛选当前Admin的所有班级,再定位对应班级的测验,最后关联测验的分数记录
- 使用
populate自动关联跨模型数据,精简返回字段保障安全 - 加入空数据判断,避免后续查询无意义执行
方案2:聚合管道(高效单查询)
如果需要更高的查询效率,可使用Mongoose聚合管道一次性完成所有关联与筛选:
const express = require('express'); const router = express.Router(); const Nilai = require('../models/Nilai'); router.get('/admin/scores', async (req, res) => { try { const adminId = req.admin._id; const studentScores = await Nilai.aggregate([ // 关联测验表(quizzes) { $lookup: { from: 'quizzes', localField: 'quiz', foreignField: '_id', as: 'quiz' } }, { $unwind: '$quiz' }, // 将关联后的数组转为单个对象 // 关联班级表(kelas) { $lookup: { from: 'kelas', localField: 'quiz.kelas', foreignField: '_id', as: 'kelas' } }, { $unwind: '$kelas' }, // 仅保留当前Admin创建的班级的分数 { $match: { 'kelas.admin': adminId } }, // 关联用户表(users) { $lookup: { from: 'users', localField: 'user', foreignField: '_id', as: 'user' } }, { $unwind: '$user' }, // 可选:验证学生确实选修了该班级(通过KelasTaken表) { $lookup: { from: 'kelastakens', let: { userId: '$user._id', classId: '$kelas._id' }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ['$user', '$$userId'] }, { $eq: ['$kelas', '$$classId'] } ] } } } ], as: 'kelasTaken' } }, // 过滤掉未选修该班级的学生分数 { $match: { 'kelasTaken.0': { $exists: true } } }, // 整理返回字段,剔除冗余数据 { $project: { _id: 1, skor: 1, createdAt: 1, 'user.nama': 1, 'user.email': 1, 'quiz.nama': 1, 'kelas.nama': 1 } }, // 按分数创建时间倒序排列 { $sort: { createdAt: -1 } } ]); res.status(200).json({ success: true, data: studentScores }); } catch (error) { res.status(500).json({ success: false, message: '获取分数失败', error: error.message }); } }); module.exports = router;
方案2说明
- 使用
$lookup替代多次find查询,完成跨模型关联 - 通过
$match精准筛选当前Admin的班级数据 - 可选的KelasTaken验证,确保分数来自真正选修该班级的学生
- 用
$project精简返回字段,提升接口响应效率
注意事项
- 聚合中
from的值对应MongoDB集合名(默认是模型名小写复数,比如Admin对应admins,Kelas对应kelas),若你自定义过集合名,需修改对应值 - 绝对不要返回用户的
password字段,始终通过select或$project控制返回内容 - 必须加入错误捕获逻辑,避免接口因异常崩溃
内容的提问来源于stack exchange,提问作者Ghufron Faqih
相关产品推荐
相关产品推荐

