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

如何在不修改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精简返回字段,提升接口响应效率

注意事项

  1. 聚合中from的值对应MongoDB集合名(默认是模型名小写复数,比如Admin对应admins,Kelas对应kelas),若你自定义过集合名,需修改对应值
  2. 绝对不要返回用户的password字段,始终通过select或$project控制返回内容
  3. 必须加入错误捕获逻辑,避免接口因异常崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:42:08