const { User, Student, Score, AttendanceModel, ClassModel, Course, ConfigModel } = require('./models'); /** * 格式化当前日期 */ const getCurrentDateInfo = () => { const now = new Date(); const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${days[now.getDay()]}`; }; /** * 辅助函数:解析学年前缀 * 例如 "2023-2024学年 第二学期" -> "2023-2024学年" */ const getSchoolYearPrefix = (semester) => { if (!semester) return null; const match = semester.match(/^(.+?学年)/); return match ? match[1] : null; }; /** * 构建学生画像上下文 (学生视角) */ async function buildStudentContext(username, schoolId) { const student = await Student.findOne({ $or: [{ studentNo: username }, { name: username }], schoolId }); if (!student) return "无法找到该学生的详细档案。"; // 获取当前学期配置 const config = await ConfigModel.findOne({ key: 'main' }); const currentSemester = config ? config.semester : null; // 逻辑:尝试拉取本学年的所有数据 (第一学期 + 第二学期) const schoolYearPrefix = getSchoolYearPrefix(currentSemester); const scoreQuery = { studentNo: student.studentNo, schoolId }; if (schoolYearPrefix) { // 匹配该学年开头的所有学期 scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) }; } else if (currentSemester) { scoreQuery.semester = currentSemester; } // 获取成绩 (稍微放宽限制以容纳整年数据) const recentScores = await Score.find(scoreQuery).sort({ semester: -1, _id: -1 }).limit(100); // 获取考勤概况 const attendanceStats = await AttendanceModel.aggregate([ { $match: { studentId: student._id.toString() } }, { $group: { _id: "$status", count: { $sum: 1 } } } ]); const absentCount = attendanceStats.find(a => a._id === 'Absent')?.count || 0; const leaveCount = attendanceStats.find(a => a._id === 'Leave')?.count || 0; let prompt = ` ### 当前用户身份:学生 (个人视图) - **姓名**: ${student.name} - **班级**: ${student.className} - **学号**: ${student.studentNo} - **积分(小红花)**: ${student.flowerBalance} 🌺 - **当前学期**: ${currentSemester || '全部'} ### 个人学习数据 `; if (recentScores.length > 0) { // 格式化输出,带上学期标识 const scoreList = recentScores.map(s => { const semShort = s.semester ? s.semester.replace(schoolYearPrefix || '', '').trim() : ''; return `${s.courseName}: ${s.score} (${semShort} ${s.examName||s.type})`; }).join('\n'); prompt += `#### 成绩记录 (本学年):\n${scoreList}\n`; } else { prompt += `- **近期成绩**: 暂无记录\n`; } if (absentCount > 0 || leaveCount > 0) { prompt += `- **考勤**: 缺勤 ${absentCount} 次,请假 ${leaveCount} 次。\n`; } else { prompt += `- **考勤**: 全勤。\n`; } return prompt; } /** * 构建教师画像上下文 (严格权限版 + 全量数据) */ async function buildTeacherContext(username, schoolId) { const user = await User.findOne({ username, schoolId }); if (!user) return "无法找到该教师档案。"; const homeroomClass = user.homeroomClass; // 1. 查找任教课程信息 (确定科任权限) const courses = await Course.find({ schoolId, $or: [{ teacherId: user._id }, { teacherName: user.trueName || user.username }] }); // 2. 确定有权限的班级列表 const authorizedClasses = new Set(); const teachingSubjectsMap = {}; // Map> // 班主任权限:拥有该班级所有数据权限 if (homeroomClass) { authorizedClasses.add(homeroomClass); } // 科任权限:拥有特定班级的特定科目权限 courses.forEach(c => { if (c.className) { authorizedClasses.add(c.className); if (!teachingSubjectsMap[c.className]) teachingSubjectsMap[c.className] = new Set(); teachingSubjectsMap[c.className].add(c.courseName); } }); const classList = Array.from(authorizedClasses); if (classList.length === 0) { return `### 当前用户身份:教师 (${user.trueName})\n目前系统显示您未绑定任何班级。请告知用户去“班级管理”或“课程安排”进行绑定。`; } // 3. 全量拉取相关班级的学生 const students = await Student.find({ schoolId, className: { $in: classList }, status: 'Enrolled' }).sort({ seatNo: 1, studentNo: 1 }); // 按座号排序 if (students.length === 0) { return `### 当前用户身份:教师\n管理班级: [${classList.join(', ')}]\n但系统未在这些班级找到学生档案。`; } const studentNos = students.map(s => s.studentNo); // 4. 拉取这些学生的成绩 (限制为本学年:包含第一学期和第二学期) const config = await ConfigModel.findOne({ key: 'main' }); const currentSemester = config ? config.semester : null; const schoolYearPrefix = getSchoolYearPrefix(currentSemester); const scoreQuery = { schoolId, studentNo: { $in: studentNos }, status: 'Normal' }; // 关键修复:使用正则匹配整个学年 (例如 "2024-2025学年") if (schoolYearPrefix) { scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) }; } else if (currentSemester) { // 如果无法解析年份,回退到当前学期 scoreQuery.semester = currentSemester; } const allScores = await Score.find(scoreQuery); // 5. 构建 Prompt let prompt = ` ### 当前用户身份:教师 (${user.trueName || username}) ### 权限范围 (严格遵守) 你只能回答下列班级的数据。如果用户询问其他班级(例如用户只教一年级,却问四年级),请礼貌拒绝,说明权限不足。 管理班级: [${classList.join(', ')}] 数据范围: ${schoolYearPrefix ? schoolYearPrefix + " (全学年)" : (currentSemester || '所有历史')} ### 详细班级数据 `; for (const cls of classList) { const isClassHomeroom = cls === homeroomClass; const subjects = teachingSubjectsMap[cls] ? Array.from(teachingSubjectsMap[cls]) : []; const roleText = isClassHomeroom ? "班主任 (全科权限)" : `任课老师 (科目: ${subjects.join(', ')})`; prompt += `\n#### 🏫 ${cls} [${roleText}]\n`; const clsStudents = students.filter(s => s.className === cls); if (clsStudents.length === 0) { prompt += "(暂无学生)\n"; continue; } prompt += `学生总数: ${clsStudents.length}人\n名单及成绩 (格式: [学期]科目:分数):\n`; for (const s of clsStudents) { let sScores = allScores.filter(sc => sc.studentNo === s.studentNo); // 如果不是班主任,仅展示自己教的科目成绩 if (!isClassHomeroom && subjects.length > 0) { sScores = sScores.filter(sc => subjects.includes(sc.courseName)); } // 格式: 张三(01号): [一]语文:90, [二]语文:85... const scoreStr = sScores.length > 0 ? sScores.map(sc => { // 简化学期显示,例如 "第一学期" -> "一", "第二学期" -> "二" let semLabel = ""; if (sc.semester && schoolYearPrefix) { if (sc.semester.includes("第一")) semLabel = "[上]"; else if (sc.semester.includes("第二")) semLabel = "[下]"; } return `${semLabel}${sc.courseName}:${sc.score}`; }).join(', ') : (isClassHomeroom ? "暂无本学年成绩" : "无本科目成绩"); prompt += `- ${s.name} (${s.seatNo ? s.seatNo+'号' : '无座号'}): ${scoreStr}\n`; } } return prompt; } /** * 构建管理员/校长画像上下文 */ async function buildAdminContext(role, schoolId) { return `### 当前用户身份:${role === 'PRINCIPAL' ? '校长' : '超级管理员'}\n你拥有最高权限,但请注意保护隐私。`; } /** * 主入口 */ async function buildUserContext(username, role, schoolId) { try { const dateStr = getCurrentDateInfo(); let roleContext = ""; if (role === 'STUDENT') { roleContext = await buildStudentContext(username, schoolId); } else if (role === 'TEACHER') { roleContext = await buildTeacherContext(username, schoolId); } else if (role === 'ADMIN' || role === 'PRINCIPAL') { roleContext = await buildAdminContext(role, schoolId); } return ` --- 【系统注入上下文】 当前时间: ${dateStr} 以下是当前用户权限范围内的 **全量真实数据**。 ${roleContext} 【AI 行为准则】 1. **角色设定**: 你是学校的AI智能助手。你的职责是协助查询校内信息,同时也乐于回答通用的百科/日常问题。 2. **数据查询规则**: - 当用户询问 **学校内部数据** (如: 学生成绩、考勤、班级名单、老师信息) 时,**必须且只能** 使用上述提供的【系统注入上下文】。 - 如果用户询问的 **校内数据** 不在上下文中 (例如: 用户只教一年级,却问三年级的数据),请明确告知“无权限查看该数据”。 3. **通用问答规则**: - 当用户询问 **非校内数据** (如: 天气、历史、写代码、翻译、闲聊) 时,请忽略权限限制,利用你的通用知识库或联网搜索功能正常回答。不要因为上下文中没有天气数据就拒绝回答。 4. **联网搜索**: 如果用户开启了联网搜索,积极搜索最新信息回答通用问题。 --- `; } catch (e) { console.error("Context build failed:", e); return ""; } } module.exports = { buildUserContext };