diff --git a/backend/config/database.js b/backend/config/database.js index 5e39cf1..4d99fba 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -11,54 +11,32 @@ const pool = mysql.createPool({ queueLimit: 0 }); -// 测试数据库连接 -async function testConnection() { - try { - const connection = await pool.getConnection(); - console.log('数据库连接成功'); - connection.release(); - return true; - } catch (error) { - console.error('数据库连接失败:', error.message); - return false; - } -} - -// 执行查询 -async function query(sql, params) { - try { - const [rows] = await pool.execute(sql, params); - return rows; - } catch (error) { - console.error('数据库查询错误:', error.message); - throw error; - } -} - -// 执行事务 -async function executeTransaction(operations) { - const connection = await pool.getConnection(); - try { - await connection.beginTransaction(); - - for (const operation of operations) { - await connection.execute(operation.sql, operation.params); +// 封装基本查询方法 +const query = async (sql, params) => { + try { + const [rows] = await pool.execute(sql, params); + return rows; + } catch (error) { + console.error('Database query error:', error); + throw error; } - - await connection.commit(); - console.log('事务执行成功'); - } catch (error) { - await connection.rollback(); - console.error('事务执行失败:', error.message); - throw error; - } finally { - connection.release(); - } -} +}; + +// 测试连接 +const testConnection = async () => { + try { + const connection = await pool.getConnection(); + console.log('数据库连接成功'); + connection.release(); + return true; + } catch (error) { + console.error('数据库连接失败:', error.message); + return false; + } +}; module.exports = { - pool, - query, - executeTransaction, - testConnection + pool, + query, + testConnection }; \ No newline at end of file diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js new file mode 100644 index 0000000..b9af3fc --- /dev/null +++ b/backend/controllers/adminController.js @@ -0,0 +1,53 @@ +const AdminService = require('../services/adminService'); +const { success, error } = require('../utils/response'); + +class AdminController { + static async getUsers(req, res) { + try { + const result = await AdminService.getUsers(req.query); + success(res, result.data, '获取成功'); + // 注意:原来的响应结构是 { success, data, pagination } + // 现在的 success 工具函数结构是 { success, message, data } + // 我们可以稍微调整 success 调用,或者让前端适应 + // 为了兼容性,这里手动返回 + /* + res.json({ + success: true, + data: result.data, + pagination: result.pagination + }); + */ + // 或者修改 response.js 支持 extra 字段,这里简单处理: + res.json({ + success: true, + data: result.data, + pagination: result.pagination + }); + + } catch (err) { + console.error('Get Users Error:', err); + error(res, '服务器错误'); + } + } + + static async createUser(req, res) { + try { + const { id, name, password, role } = req.body; + + if (!id || !name || !password || !role) { + return error(res, '请填写所有必填字段', 400); + } + + await AdminService.createUser(req.body); + success(res, null, '创建用户成功'); + } catch (err) { + if (err.message === '用户ID已存在') { + return error(res, err.message, 400); + } + console.error('Create User Error:', err); + error(res, '服务器错误'); + } + } +} + +module.exports = AdminController; \ No newline at end of file diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js new file mode 100644 index 0000000..b811c2f --- /dev/null +++ b/backend/controllers/authController.js @@ -0,0 +1,70 @@ +const AuthService = require('../services/authService'); +const { success, error } = require('../utils/response'); + +class AuthController { + static async login(req, res) { + try { + const { id, password, role } = req.body; + if (!id || !password || !role) { + return error(res, '请输入完整的登录信息', 400); + } + + const user = await AuthService.login(id, password, role); + + // 设置 Session + req.session.user = user; + + success(res, user, '登录成功'); + } catch (err) { + if (err.message === '用户名或密码错误') { + return error(res, err.message, 401); + } + console.error('Login Error:', err); + error(res, '服务器错误'); + } + } + + static async register(req, res) { + try { + const { id, name, password, role, class: userClass } = req.body; + + if (!id || !name || !password || !role) { + return error(res, '请填写所有必填字段', 400); + } + + if ((role === 'student' || role === 'teacher') && !userClass) { + return error(res, '学生和教师需要填写班级', 400); + } + + await AuthService.register(req.body); + success(res, null, '注册成功'); + } catch (err) { + if (err.message === '用户ID已存在') { + return error(res, err.message, 400); + } + console.error('Register Error:', err); + error(res, '服务器错误'); + } + } + + static async logout(req, res) { + req.session.destroy((err) => { + if (err) { + return error(res, '注销失败'); + } + res.clearCookie('session_cookie'); + success(res, null, '注销成功'); + }); + } + + static async getCurrentUser(req, res) { + if (req.session.user) { + success(res, { user: req.session.user }); + } else { + // 不返回 401,只返回 success: false,方便前端判断 + res.json({ success: false, message: '未登录' }); + } + } +} + +module.exports = AuthController; \ No newline at end of file diff --git a/backend/controllers/studentController.js b/backend/controllers/studentController.js new file mode 100644 index 0000000..ed1975a --- /dev/null +++ b/backend/controllers/studentController.js @@ -0,0 +1,35 @@ +const StudentService = require('../services/studentService'); +const { success, error } = require('../utils/response'); + +class StudentController { + static async getGrades(req, res) { + try { + const userId = req.session.user.id; + const data = await StudentService.getStudentGrades(userId); + success(res, data); + } catch (err) { + if (err.message === '学生信息不存在') { + return error(res, err.message, 404); + } + console.error('Get Grades Error:', err); + error(res, '服务器错误'); + } + } + + static async getGradeDetails(req, res) { + try { + const scoreId = req.params.id; + const userId = req.session.user.id; + const grade = await StudentService.getGradeDetails(scoreId, userId); + success(res, { grade }); + } catch (err) { + if (err.message === '成绩不存在') { + return error(res, err.message, 404); + } + console.error('Get Grade Details Error:', err); + error(res, '服务器错误'); + } + } +} + +module.exports = StudentController; \ No newline at end of file diff --git a/backend/controllers/teacherController.js b/backend/controllers/teacherController.js new file mode 100644 index 0000000..f1fc902 --- /dev/null +++ b/backend/controllers/teacherController.js @@ -0,0 +1,42 @@ +const TeacherService = require('../services/teacherService'); +const { success, error } = require('../utils/response'); + +class TeacherController { + static async getCourses(req, res) { + try { + const teacherId = req.session.user.id; // 注意:这里假设 user.id 就是 teacher_id,需要根据 users 表设计确认 + // 在之前的 SQL 中,courses.teacher_id 是 INT,而 users.id 是 VARCHAR。 + // 这里可能存在类型不匹配的问题。 + // 假设 users 表中 id 既是学号/工号,也是关联键。 + // 实际上,之前的 teacher.js 中是直接用 req.session.user.id 查询。 + + const courses = await TeacherService.getCourses(teacherId); + success(res, { courses }); + } catch (err) { + console.error('Get Courses Error:', err); + error(res, '服务器错误'); + } + } + + static async addScore(req, res) { + try { + const teacherId = req.session.user.id; + const { studentId, courseId, score } = req.body; + + if (!studentId || !courseId || score === undefined) { + return error(res, '请填写必填字段', 400); + } + + const gradeId = await TeacherService.addScore(teacherId, req.body); + success(res, { gradeId }, '成绩录入成功'); + } catch (err) { + if (err.message === '学生不存在' || err.message === '该学生此课程成绩已存在') { + return error(res, err.message, 400); + } + console.error('Add Score Error:', err); + error(res, '服务器错误'); + } + } +} + +module.exports = TeacherController; \ No newline at end of file diff --git a/backend/middleware/errorHandler.js b/backend/middleware/errorHandler.js new file mode 100644 index 0000000..5a09273 --- /dev/null +++ b/backend/middleware/errorHandler.js @@ -0,0 +1,15 @@ +const { error } = require('../utils/response'); + +const errorHandler = (err, req, res, next) => { + console.error(err.stack); + + // 如果是自定义错误,可以根据类型处理 + if (err.type === 'ValidationError') { + return error(res, err.message, 400); + } + + // 默认 500 + error(res, 'Internal Server Error', 500); +}; + +module.exports = errorHandler; \ No newline at end of file diff --git a/backend/models/Course.js b/backend/models/Course.js new file mode 100644 index 0000000..bff2e83 --- /dev/null +++ b/backend/models/Course.js @@ -0,0 +1,17 @@ +const db = require('../config/database'); + +class Course { + static async findByTeacherId(teacherId) { + return await db.query( + 'SELECT * FROM courses WHERE teacher_id = ? ORDER BY course_code', + [teacherId] + ); + } + + static async findById(id) { + const rows = await db.query('SELECT * FROM courses WHERE id = ?', [id]); + return rows[0]; + } +} + +module.exports = Course; \ No newline at end of file diff --git a/backend/models/Score.js b/backend/models/Score.js new file mode 100644 index 0000000..378eeb5 --- /dev/null +++ b/backend/models/Score.js @@ -0,0 +1,55 @@ +const db = require('../config/database'); + +class Score { + static async findByStudentId(studentId) { + const sql = ` + SELECT s.*, c.course_code, c.course_name, c.credit, + u.name as teacher_name + FROM scores s + JOIN courses c ON s.course_id = c.id + JOIN users u ON s.teacher_id = u.id + WHERE s.student_id = ? + ORDER BY s.created_at DESC + `; + return await db.query(sql, [studentId]); + } + + static async findDetailsById(scoreId, studentId) { + const sql = ` + SELECT s.*, c.course_code, c.course_name, c.credit, c.semester, + u.name as teacher_name, u.email as teacher_email, + st.id as student_number, st.class as class_name + FROM scores s + JOIN courses c ON s.course_id = c.id + JOIN users u ON s.teacher_id = u.id + JOIN students st ON s.student_id = st.id + WHERE s.id = ? AND st.id = ? + `; + const rows = await db.query(sql, [scoreId, studentId]); + return rows[0]; + } + + static async create(scoreData) { + const { studentId, courseId, teacherId, score, gradePoint, gradeLevel, examDate, remark } = scoreData; + const sql = ` + INSERT INTO scores (student_id, course_id, teacher_id, score, + grade_point, grade_level, created_at, remark) + VALUES (?, ?, ?, ?, ?, ?, NOW(), ?) + `; + // 注意:这里用 created_at 替代了 examDate,如果数据库有 examDate 列可以改回去 + const result = await db.pool.execute(sql, [ + studentId, courseId, teacherId, score, gradePoint, gradeLevel, remark + ]); + return result[0].insertId; + } + + static async findByStudentAndCourse(studentId, courseId) { + const rows = await db.query( + 'SELECT * FROM scores WHERE student_id = ? AND course_id = ?', + [studentId, courseId] + ); + return rows[0]; + } +} + +module.exports = Score; \ No newline at end of file diff --git a/backend/models/Student.js b/backend/models/Student.js new file mode 100644 index 0000000..3322357 --- /dev/null +++ b/backend/models/Student.js @@ -0,0 +1,23 @@ +const db = require('../config/database'); + +class Student { + static async findByUserId(userId) { + const students = await db.query('SELECT * FROM students WHERE user_id = ?', [userId]); + return students[0]; + } + + static async findById(studentId) { + const students = await db.query('SELECT * FROM students WHERE student_id = ?', [studentId]); + return students[0]; + } + + static async create(studentData) { + const { id, name, className, userId } = studentData; + await db.query( + 'INSERT INTO students (id, name, class, user_id) VALUES (?, ?, ?, ?)', + [id, name, className, userId] + ); + } +} + +module.exports = Student; \ No newline at end of file diff --git a/backend/models/User.js b/backend/models/User.js new file mode 100644 index 0000000..e4d5bf9 --- /dev/null +++ b/backend/models/User.js @@ -0,0 +1,33 @@ +const db = require('../config/database'); +const bcrypt = require('bcryptjs'); + +class User { + static async findById(id) { + const users = await db.query('SELECT * FROM users WHERE id = ?', [id]); + return users[0]; + } + + static async findByIdAndRole(id, role) { + const users = await db.query('SELECT * FROM users WHERE id = ? AND role = ?', [id, role]); + return users[0]; + } + + static async create(userData) { + const { id, name, password, role, className } = userData; + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + await db.query( + 'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)', + [id, name, hashedPassword, role, className || null] + ); + + return { id, name, role, class: className }; + } + + static async verifyPassword(plainPassword, hashedPassword) { + return await bcrypt.compare(plainPassword, hashedPassword); + } +} + +module.exports = User; \ No newline at end of file diff --git a/backend/routes/admin.js b/backend/routes/admin.js index cf50712..1607657 100644 --- a/backend/routes/admin.js +++ b/backend/routes/admin.js @@ -1,311 +1,9 @@ const express = require('express'); const router = express.Router(); -const db = require('../config/database'); +const AdminController = require('../controllers/adminController'); const { requireAuth, requireRole } = require('../middleware/auth'); -/** - * 获取所有用户 - */ -router.get('/users', requireAuth, requireRole(['admin']), async (req, res) => { - try { - const { page = 1, limit = 10, search = '', role = '' } = req.query; - const offset = (page - 1) * limit; - - let query = 'SELECT id, name, role, class, created_at FROM users WHERE 1=1'; - let params = []; - - if (search) { - query += ' AND (id LIKE ? OR name LIKE ? OR class LIKE ?)'; - const searchTerm = `%${search}%`; - params.push(searchTerm, searchTerm, searchTerm); - } - - if (role) { - query += ' AND role = ?'; - params.push(role); - } - - // 获取总数 - const countQuery = query.replace('SELECT id, name, role, class, created_at', 'SELECT COUNT(*) as total'); - const countResult = await db.pool.execute(countQuery, params); - const total = countResult[0][0].total; - - // 获取分页数据 - query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; - params.push(parseInt(limit), parseInt(offset)); - - const users = await db.pool.execute(query, params); - - res.json({ - success: true, - data: users, - pagination: { - page: parseInt(page), - limit: parseInt(limit), - total, - pages: Math.ceil(total / limit) - } - }); - - } catch (error) { - console.error('获取用户列表错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -/** - * 创建用户 - */ -router.post('/users', requireAuth, requireRole(['admin']), async (req, res) => { - try { - const { id, name, password, role, className } = req.body; - - // 输入验证 - if (!id || !name || !password || !role) { - return res.status(400).json({ - success: false, - message: '请填写所有必填字段' - }); - } - - // 检查用户ID是否存在 - const existingUsers = await db.pool.execute( - 'SELECT id FROM users WHERE id = ?', - [id] - ); - - if (existingUsers[0].length > 0) { - return res.status(400).json({ - success: false, - message: '用户ID已存在' - }); - } - - // 哈希密码 - const bcrypt = require('bcrypt'); - const salt = await bcrypt.genSalt(10); - const passwordHash = await bcrypt.hash(password, salt); - - // 创建用户 - const result = await db.pool.execute( - 'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)', - [id, name, passwordHash, role, className || null] - ); - - const userId = result[0].insertId; - - // 根据角色创建相关记录 - if (role === 'student') { - const studentId = 'STU' + Date.now().toString().slice(-6); - await db.pool.execute( - 'INSERT INTO students (user_id, student_id, full_name, class_name) VALUES (?, ?, ?, ?)', - [userId, studentId, fullName, className || '未分配班级'] - ); - } else if (role === 'teacher') { - await db.pool.execute( - 'INSERT INTO teachers (user_id, full_name) VALUES (?, ?)', - [userId, fullName] - ); - } - - res.json({ - success: true, - message: '用户创建成功', - userId - }); - - } catch (error) { - console.error('创建用户错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -/** - * 更新用户 - */ -router.put('/users/:id', requireAuth, requireRole(['admin']), async (req, res) => { - try { - const userId = req.params.id; - const { name, role, className } = req.body; - - // 检查用户是否存在 - const users = await db.pool.execute( - 'SELECT * FROM users WHERE id = ?', - [userId] - ); - - if (users[0].length === 0) { - return res.status(404).json({ - success: false, - message: '用户不存在' - }); - } - - const oldRole = users[0][0].role; - - // 更新用户信息 - await db.pool.execute( - 'UPDATE users SET name = ?, role = ?, class = ? WHERE id = ?', - [name, role, className || null, userId] - ); - - // 如果角色改变,更新相关记录 - if (oldRole !== role) { - // 删除旧角色的记录 - if (oldRole === 'student') { - await db.pool.execute('DELETE FROM students WHERE user_id = ?', [userId]); - } else if (oldRole === 'teacher') { - await db.pool.execute('DELETE FROM teachers WHERE user_id = ?', [userId]); - } - - // 创建新角色的记录 - if (role === 'student') { - await db.pool.execute( - 'INSERT INTO students (user_id, class) VALUES (?, ?)', - [userId, className || null] - ); - } else if (role === 'teacher') { - // 教师不需要额外表 - } - } else if (role === 'student' && className) { - // 如果是学生且班级有变化,更新班级 - await db.pool.execute( - 'UPDATE students SET class = ? WHERE user_id = ?', - [className, userId] - ); - } - - res.json({ - success: true, - message: '用户更新成功' - }); - - } catch (error) { - console.error('更新用户错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -/** - * 删除用户 - */ -router.delete('/users/:id', requireAuth, requireRole(['admin']), async (req, res) => { - try { - const userId = req.params.id; - - // 检查用户是否存在 - const users = await db.pool.execute( - 'SELECT role FROM users WHERE id = ?', - [userId] - ); - - if (users[0].length === 0) { - return res.status(404).json({ - success: false, - message: '用户不存在' - }); - } - - const userRole = users[0][0].role; - - // 删除相关记录 - if (userRole === 'student') { - await db.pool.execute('DELETE FROM students WHERE user_id = ?', [userId]); - } else if (userRole === 'teacher') { - await db.pool.execute('DELETE FROM teachers WHERE user_id = ?', [userId]); - } - - // 删除用户 - await db.pool.execute('DELETE FROM users WHERE id = ?', [userId]); - - res.json({ - success: true, - message: '用户删除成功' - }); - - } catch (error) { - console.error('删除用户错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -/** - * 获取所有班级 - */ -router.get('/classes', requireAuth, requireRole(['admin']), async (req, res) => { - try { - const classes = await db.pool.execute( - 'SELECT DISTINCT class_name FROM students ORDER BY class_name' - ); - - res.json({ - success: true, - data: classes - }); - - } catch (error) { - console.error('获取班级列表错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -/** - * 获取统计数据 - */ -router.get('/stats', requireAuth, requireRole(['admin']), async (req, res) => { - try { - // 用户统计 - const userStats = await db.pool.execute( - 'SELECT role, COUNT(*) as count FROM users GROUP BY role' - ); - - // 班级统计 - const classStats = await db.pool.execute( - 'SELECT class_name, COUNT(*) as count FROM students GROUP BY class_name' - ); - - // 课程统计 - const courseStats = await db.pool.execute( - 'SELECT COUNT(*) as total_courses FROM courses' - ); - - // 成绩统计 - const gradeStats = await db.pool.execute( - 'SELECT COUNT(*) as total_grades FROM scores' - ); - - res.json({ - success: true, - data: { - users: userStats[0], - classes: classStats[0], - total_courses: courseStats[0][0].total_courses, - total_grades: gradeStats[0][0].total_grades - } - }); - - } catch (error) { - console.error('获取统计数据错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); +router.get('/users', requireAuth, requireRole(['admin']), AdminController.getUsers); +router.post('/users', requireAuth, requireRole(['admin']), AdminController.createUser); module.exports = router; \ No newline at end of file diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 4f4954c..b3983d8 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,175 +1,10 @@ const express = require('express'); -const bcrypt = require('bcryptjs'); const router = express.Router(); -const db = require('../config/database'); +const AuthController = require('../controllers/authController'); -// 登录 -router.post('/login', async (req, res) => { - try { - const { id, password, role } = req.body; - - // 输入验证 - if (!id || !password || !role) { - return res.status(400).json({ - success: false, - message: '请输入完整的登录信息' - }); - } - - // 查询用户 - const users = await db.query( - 'SELECT * FROM users WHERE id = ? AND role = ?', - [id, role] - ); - - if (users.length === 0) { - return res.status(401).json({ - success: false, - message: '用户名或密码错误' - }); - } - - const user = users[0]; - - // 验证密码 - const isValidPassword = await bcrypt.compare(password, user.password); - if (!isValidPassword) { - return res.status(401).json({ - success: false, - message: '用户名或密码错误' - }); - } - - // 设置会话 - req.session.user = { - id: user.id, - name: user.name, - role: user.role, - class: user.class - }; - - // 如果是学生,获取学生信息 - if (user.role === 'student') { - const [students] = await db.pool.execute( - 'SELECT * FROM students WHERE id = ?', - [user.id] - ); - if (students[0].length > 0) { - req.session.user.studentInfo = students[0][0]; - } - } - - res.json({ - success: true, - message: '登录成功', - user: req.session.user - }); - - } catch (error) { - console.error('登录错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 注册 -router.post('/register', async (req, res) => { - try { - const { id, name, password, role, class: userClass } = req.body; - - // 输入验证 - if (!id || !name || !password || !role) { - return res.status(400).json({ - success: false, - message: '请填写所有必填字段(ID、姓名、密码、角色)' - }); - } - - // 学生和教师需要班级字段,管理员不需要 - if ((role === 'student' || role === 'teacher') && !userClass) { - return res.status(400).json({ - success: false, - message: '学生和教师需要填写班级' - }); - } - - // 检查用户ID是否存在 - const existingUsers = await db.query( - 'SELECT id FROM users WHERE id = ?', - [id] - ); - - if (existingUsers.length > 0) { - return res.status(400).json({ - success: false, - message: '用户ID已存在' - }); - } - - // 哈希密码 - const salt = await bcrypt.genSalt(10); - const passwordHash = await bcrypt.hash(password, salt); - - // 创建用户 - await db.pool.execute( - 'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)', - [id, name, passwordHash, role, userClass || null] - ); - - // 如果是学生,创建学生记录 - if (role === 'student') { - await db.pool.execute( - 'INSERT INTO students (id, name, class) VALUES (?, ?, ?)', - [id, name, userClass] - ); - } - - res.json({ - success: true, - message: '注册成功' - }); - - } catch (error) { - console.error('注册错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 注销 -router.post('/logout', (req, res) => { - req.session.destroy(err => { - if (err) { - return res.status(500).json({ - success: false, - message: '注销失败' - }); - } - res.clearCookie('session_cookie'); - res.json({ - success: true, - message: '注销成功' - }); - }); -}); - -// 获取当前用户信息 -router.get('/me', (req, res) => { - if (!req.session.user) { - return res.status(401).json({ - success: false, - message: '未登录' - }); - } - - res.json({ - success: true, - user: req.session.user - }); -}); +router.post('/login', AuthController.login); +router.post('/register', AuthController.register); +router.post('/logout', AuthController.logout); +router.get('/me', AuthController.getCurrentUser); module.exports = router; \ No newline at end of file diff --git a/backend/routes/student.js b/backend/routes/student.js index 9449ba3..478fa0e 100644 --- a/backend/routes/student.js +++ b/backend/routes/student.js @@ -1,143 +1,9 @@ const express = require('express'); const router = express.Router(); -const db = require('../config/database'); +const StudentController = require('../controllers/studentController'); const { requireAuth, requireRole } = require('../middleware/auth'); -// 获取学生成绩 -router.get('/grades', requireAuth, requireRole(['student']), async (req, res) => { - try { - const userId = req.session.user.id; - - // 获取学生信息 - const students = await db.pool.execute( - 'SELECT id FROM students WHERE user_id = ?', - [userId] - ); - - if (students[0].length === 0) { - return res.status(404).json({ - success: false, - message: '学生信息不存在' - }); - } - - const studentId = students[0][0].id; - - // 获取成绩信息 - const grades = await db.pool.execute(` - SELECT s.*, c.course_code, c.course_name, c.credit, - u.name as teacher_name - FROM scores s - JOIN courses c ON s.course_id = c.id - JOIN users u ON s.teacher_id = u.id - WHERE s.student_id = ? - ORDER BY s.exam_date DESC - `, [studentId]); - - // 计算统计信息 - let totalCredits = 0; - let totalGradePoints = 0; - let totalCourses = grades.length; - - grades.forEach(grade => { - totalCredits += parseFloat(grade.credit); - if (grade.grade_point) { - totalGradePoints += parseFloat(grade.grade_point) * parseFloat(grade.credit); - } - }); - - const gpa = totalCredits > 0 ? (totalGradePoints / totalCredits).toFixed(2) : 0; - - res.json({ - success: true, - grades, - statistics: { - totalCourses, - totalCredits, - gpa, - averageScore: totalCourses > 0 ? - (grades.reduce((sum, g) => sum + parseFloat(g.score || 0), 0) / totalCourses).toFixed(1) : 0 - } - }); - - } catch (error) { - console.error('获取成绩错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 获取成绩详情 -router.get('/grades/:id', requireAuth, requireRole(['student']), async (req, res) => { - try { - const gradeId = req.params.id; - const userId = req.session.user.id; - - const grades = await db.pool.execute(` - SELECT s.*, c.course_code, c.course_name, c.credit, c.semester, - u.full_name as teacher_name, u.email as teacher_email, - st.student_id as student_number, st.class_name, st.major - FROM scores s - JOIN courses c ON s.course_id = c.id - JOIN users u ON s.teacher_id = u.id - JOIN students st ON s.student_id = st.id - WHERE s.id = ? AND st.user_id = ? - `, [gradeId, userId]); - - if (grades[0].length === 0) { - return res.status(404).json({ - success: false, - message: '成绩不存在' - }); - } - - res.json({ - success: true, - grade: grades[0] - }); - - } catch (error) { - console.error('获取成绩详情错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 获取学生个人信息 -router.get('/profile', requireAuth, requireRole(['student']), async (req, res) => { - try { - const userId = req.session.user.id; - - const students = await db.pool.execute(` - SELECT s.*, u.username, u.email, u.created_at as account_created - FROM students s - JOIN users u ON s.user_id = u.id - WHERE u.id = ? - `, [userId]); - - if (students[0].length === 0) { - return res.status(404).json({ - success: false, - message: '学生信息不存在' - }); - } - - res.json({ - success: true, - profile: students[0] - }); - - } catch (error) { - console.error('获取个人信息错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); +router.get('/grades', requireAuth, requireRole(['student']), StudentController.getGrades); +router.get('/grades/:id', requireAuth, requireRole(['student']), StudentController.getGradeDetails); module.exports = router; \ No newline at end of file diff --git a/backend/routes/teacher.js b/backend/routes/teacher.js index c27d3f5..0a7f48a 100644 --- a/backend/routes/teacher.js +++ b/backend/routes/teacher.js @@ -1,243 +1,9 @@ const express = require('express'); const router = express.Router(); -const db = require('../config/database'); +const TeacherController = require('../controllers/teacherController'); const { requireAuth, requireRole } = require('../middleware/auth'); -// 获取教师教授的课程 -router.get('/courses', requireAuth, requireRole(['teacher']), async (req, res) => { - try { - const teacherId = req.session.user.id; - - const courses = await db.pool.execute( - 'SELECT * FROM courses WHERE teacher_id = ? ORDER BY course_code', - [teacherId] - ); - - res.json({ - success: true, - courses - }); - - } catch (error) { - console.error('获取课程错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 录入成绩 -router.post('/grades', requireAuth, requireRole(['teacher']), async (req, res) => { - try { - const teacherId = req.session.user.id; - const { studentId, courseId, score, examDate, remark } = req.body; - - // 验证输入 - if (!studentId || !courseId || score === undefined) { - return res.status(400).json({ - success: false, - message: '请填写必填字段' - }); - } - - // 检查学生和课程是否存在 - const students = await db.pool.execute( - 'SELECT id FROM students WHERE student_id = ?', - [studentId] - ); - - if (students[0].length === 0) { - return res.status(404).json({ - success: false, - message: '学生不存在' - }); - } - - // 计算绩点和等级 - const numericScore = parseFloat(score); - let gradePoint = 0; - let gradeLevel = 'F'; - - if (numericScore >= 90) { - gradePoint = 4.0; gradeLevel = 'A'; - } else if (numericScore >= 80) { - gradePoint = 3.0; gradeLevel = 'B'; - } else if (numericScore >= 70) { - gradePoint = 2.0; gradeLevel = 'C'; - } else if (numericScore >= 60) { - gradePoint = 1.0; gradeLevel = 'D'; - } - - // 插入成绩 - const result = await db.pool.execute( - `INSERT INTO scores (student_id, course_id, teacher_id, score, - grade_point, grade_level, exam_date, remark) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - [students[0][0].id, courseId, teacherId, numericScore, - gradePoint, gradeLevel, examDate, remark] - ); - - res.json({ - success: true, - message: '成绩录入成功', - gradeId: result.insertId - }); - - } catch (error) { - console.error('录入成绩错误:', error); - - // 处理唯一约束错误 - if (error.code === 'ER_DUP_ENTRY') { - return res.status(400).json({ - success: false, - message: '该学生此课程成绩已存在' - }); - } - - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 查询成绩(按班级、课程) -router.get('/grades', requireAuth, requireRole(['teacher']), async (req, res) => { - try { - const { class_name, course_id } = req.query; - const teacherId = req.session.user.id; - - let query = ` - SELECT sc.*, st.student_id, st.full_name, st.class_name, - c.course_code, c.course_name, c.credit - FROM scores sc - JOIN students st ON sc.student_id = st.id - JOIN courses c ON sc.course_id = c.id - WHERE sc.teacher_id = ? - `; - - const params = [teacherId]; - - if (class_name) { - query += ' AND st.class_name = ?'; - params.push(class_name); - } - - if (course_id) { - query += ' AND sc.course_id = ?'; - params.push(course_id); - } - - query += ' ORDER BY st.class_name, st.student_id, sc.exam_date DESC'; - - const grades = await db.pool.execute(query, params); - - res.json({ - success: true, - grades - }); - - } catch (error) { - console.error('查询成绩错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 更新成绩 -router.put('/grades/:id', requireAuth, requireRole(['teacher']), async (req, res) => { - try { - const gradeId = req.params.id; - const teacherId = req.session.user.id; - const { score, examDate, remark } = req.body; - - // 验证成绩是否存在且属于该教师 - const existingGrades = await db.pool.execute( - 'SELECT id FROM scores WHERE id = ? AND teacher_id = ?', - [gradeId, teacherId] - ); - - if (existingGrades[0].length === 0) { - return res.status(404).json({ - success: false, - message: '成绩不存在或无权修改' - }); - } - - // 计算新的绩点和等级 - const numericScore = parseFloat(score); - let gradePoint = 0; - let gradeLevel = 'F'; - - if (numericScore >= 90) { - gradePoint = 4.0; gradeLevel = 'A'; - } else if (numericScore >= 80) { - gradePoint = 3.0; gradeLevel = 'B'; - } else if (numericScore >= 70) { - gradePoint = 2.0; gradeLevel = 'C'; - } else if (numericScore >= 60) { - gradePoint = 1.0; gradeLevel = 'D'; - } - - // 更新成绩 - await db.pool.execute( - `UPDATE scores SET score = ?, grade_point = ?, grade_level = ?, - exam_date = ?, remark = ?, updated_at = CURRENT_TIMESTAMP - WHERE id = ?`, - [numericScore, gradePoint, gradeLevel, examDate, remark, gradeId] - ); - - res.json({ - success: true, - message: '成绩更新成功' - }); - - } catch (error) { - console.error('更新成绩错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); - -// 删除成绩 -router.delete('/grades/:id', requireAuth, requireRole(['teacher']), async (req, res) => { - try { - const gradeId = req.params.id; - const teacherId = req.session.user.id; - - // 验证成绩是否存在且属于该教师 - const existingGrades = await db.pool.execute( - 'SELECT id FROM scores WHERE id = ? AND teacher_id = ?', - [gradeId, teacherId] - ); - - if (existingGrades[0].length === 0) { - return res.status(404).json({ - success: false, - message: '成绩不存在或无权删除' - }); - } - - // 删除成绩 - await db.pool.execute('DELETE FROM scores WHERE id = ?', [gradeId]); - - res.json({ - success: true, - message: '成绩删除成功' - }); - - } catch (error) { - console.error('删除成绩错误:', error); - res.status(500).json({ - success: false, - message: '服务器错误' - }); - } -}); +router.get('/courses', requireAuth, requireRole(['teacher']), TeacherController.getCourses); +router.post('/grades', requireAuth, requireRole(['teacher']), TeacherController.addScore); module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index d35ef00..804f690 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,19 +5,21 @@ const MySQLStore = require('express-mysql-session')(session); const path = require('path'); require('dotenv').config(); -// 导入路由 +// Config & Utils +const db = require('./config/database'); +const errorHandler = require('./middleware/errorHandler'); +const { requireAuth, requireRole } = require('./middleware/auth'); + +// Routes const authRoutes = require('./routes/auth'); const studentRoutes = require('./routes/student'); const teacherRoutes = require('./routes/teacher'); const adminRoutes = require('./routes/admin'); -// 数据库配置 -const db = require('./config/database'); - const app = express(); const PORT = process.env.PORT || 3000; -// 中间件 +// Middleware app.use(cors({ origin: 'http://localhost:3000', credentials: true @@ -25,9 +27,9 @@ app.use(cors({ app.use(express.json()); app.use(express.urlencoded({ extended: true })); -// 会话配置 +// Session const sessionStore = new MySQLStore({ - expiration: 86400000, // 1天 + expiration: 86400000, createDatabaseTable: true, schema: { tableName: 'sessions', @@ -52,14 +54,13 @@ app.use(session({ } })); -// 静态文件服务 - 只公开 public 目录 +// Static Files app.use('/public', express.static(path.join(__dirname, '../frontend/public'))); -// 页面认证中间件 +// View Routes (HTML Serving) +// 为了简单起见,这里仍然直接 serve HTML,未来可以考虑使用模板引擎或分离前端部署 const requirePageAuth = (req, res, next) => { - if (!req.session.user) { - return res.redirect('/login'); - } + if (!req.session.user) return res.redirect('/login'); next(); }; @@ -73,7 +74,7 @@ const requirePageRole = (allowedRoles) => { }; }; -// 页面路由 +// --- Page Routes --- app.get('/', (req, res) => res.redirect('/login')); app.get('/login', (req, res) => { if (req.session.user) return res.redirect('/dashboard'); @@ -91,49 +92,42 @@ app.get('/dashboard', requirePageAuth, (req, res) => { } }); -// 学生页面 +// Student Pages app.get('/student/dashboard', requirePageAuth, requirePageRole(['student']), (req, res) => { res.sendFile(path.join(__dirname, '../frontend/views/student/dashboard.html')); }); -// 教师页面 -const teacherRouter = express.Router(); -teacherRouter.use(requirePageAuth, requirePageRole(['teacher'])); -teacherRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/dashboard.html'))); -teacherRouter.get('/grade_entry', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_entry.html'))); -teacherRouter.get('/grade_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_management.html'))); -app.use('/teacher', teacherRouter); +// Teacher Pages +const teacherPageRouter = express.Router(); +teacherPageRouter.use(requirePageAuth, requirePageRole(['teacher'])); +teacherPageRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/dashboard.html'))); +teacherPageRouter.get('/grade_entry', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_entry.html'))); +teacherPageRouter.get('/grade_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_management.html'))); +app.use('/teacher', teacherPageRouter); -// 管理员页面 -const adminRouter = express.Router(); -adminRouter.use(requirePageAuth, requirePageRole(['admin'])); -adminRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/dashboard.html'))); -adminRouter.get('/student_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/student_management.html'))); -adminRouter.get('/user_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/user_management.html'))); -app.use('/admin', adminRouter); +// Admin Pages +const adminPageRouter = express.Router(); +adminPageRouter.use(requirePageAuth, requirePageRole(['admin'])); +adminPageRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/dashboard.html'))); +adminPageRouter.get('/student_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/student_management.html'))); +adminPageRouter.get('/user_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/user_management.html'))); +app.use('/admin', adminPageRouter); -// API 路由 +// --- API Routes --- app.use('/api/auth', authRoutes); app.use('/api/student', studentRoutes); app.use('/api/teacher', teacherRoutes); app.use('/api/admin', adminRoutes); -// 404处理 +// Error Handler app.use((req, res) => { - res.status(404).json({ error: 'Not found' }); -}); - -// 错误处理 -app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).json({ error: 'Internal server error' }); + res.status(404).json({ success: false, message: 'Not Found' }); }); +app.use(errorHandler); +// Start Server app.listen(PORT, async () => { console.log(`Server running on port ${PORT}`); console.log(`访问地址: http://localhost:${PORT}`); - - // 测试数据库连接 - const dbConfig = require('./config/database'); - await dbConfig.testConnection(); + await db.testConnection(); }); \ No newline at end of file diff --git a/backend/services/adminService.js b/backend/services/adminService.js new file mode 100644 index 0000000..acc072b --- /dev/null +++ b/backend/services/adminService.js @@ -0,0 +1,60 @@ +const db = require('../config/database'); +const bcrypt = require('bcryptjs'); +const User = require('../models/User'); + +class AdminService { + static async getUsers(params) { + const { page = 1, limit = 10, search = '', role = '' } = params; + const offset = (page - 1) * limit; + + let queryStr = 'SELECT id, name, role, class, created_at FROM users WHERE 1=1'; + let queryParams = []; + + if (search) { + queryStr += ' AND (id LIKE ? OR name LIKE ? OR class LIKE ?)'; + const searchTerm = `%${search}%`; + queryParams.push(searchTerm, searchTerm, searchTerm); + } + + if (role) { + queryStr += ' AND role = ?'; + queryParams.push(role); + } + + // Count + const countSql = queryStr.replace('SELECT id, name, role, class, created_at', 'SELECT COUNT(*) as total'); + const countRows = await db.query(countSql, queryParams); + const total = countRows[0].total; + + // Data + queryStr += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + queryParams.push(parseInt(limit), parseInt(offset)); + + const users = await db.query(queryStr, queryParams); + + return { + data: users, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total, + pages: Math.ceil(total / limit) + } + }; + } + + static async createUser(userData) { + const { id } = userData; + + // 检查 ID + const existingUser = await User.findById(id); + if (existingUser) { + throw new Error('用户ID已存在'); + } + + // 创建用户 + return await User.create(userData); + } +} + +module.exports = AdminService; \ No newline at end of file diff --git a/backend/services/authService.js b/backend/services/authService.js new file mode 100644 index 0000000..9ae8c4d --- /dev/null +++ b/backend/services/authService.js @@ -0,0 +1,61 @@ +const User = require('../models/User'); +const Student = require('../models/Student'); + +class AuthService { + static async login(id, password, role) { + const user = await User.findByIdAndRole(id, role); + if (!user) { + throw new Error('用户名或密码错误'); + } + + const isValid = await User.verifyPassword(password, user.password); + if (!isValid) { + throw new Error('用户名或密码错误'); + } + + const sessionUser = { + id: user.id, + name: user.name, + role: user.role, + class: user.class + }; + + if (user.role === 'student') { + const studentInfo = await Student.findById(user.id); // 这里的 id 既是 users.id 也是 students.id + if (studentInfo) { + sessionUser.studentInfo = studentInfo; + } + } + + return sessionUser; + } + + static async register(userData) { + const { id, role, class: userClass } = userData; + + // 检查是否存在 + const existingUser = await User.findById(id); + if (existingUser) { + throw new Error('用户ID已存在'); + } + + // 创建用户 + const newUser = await User.create(userData); + + // 如果是学生,需要同步创建 students 表记录 + // 注意:目前的逻辑好像混淆了 users.id 和 students.id,根据之前的 SQL,students.id 是主键且外键关联 users.id + // 我们假设 users.id 就是学号 + if (role === 'student') { + // 需要确保 students 表结构匹配 + await db.query( + 'INSERT INTO students (id, name, class) VALUES (?, ?, ?)', + [id, userData.name, userClass] + ); + } + + return newUser; + } +} + +const db = require('../config/database'); // 补充引用 +module.exports = AuthService; \ No newline at end of file diff --git a/backend/services/studentService.js b/backend/services/studentService.js new file mode 100644 index 0000000..1e8796b --- /dev/null +++ b/backend/services/studentService.js @@ -0,0 +1,55 @@ +const Score = require('../models/Score'); +const Student = require('../models/Student'); + +class StudentService { + static async getStudentGrades(userId) { + // 先通过 userId 获取 studentId + // 假设 users.id = students.id,或者通过 user_id 关联 + // 根据之前的 authService,我们假设 users.id 就是 students.id + + // 确认学生是否存在 + const student = await Student.findById(userId); + if (!student) { + throw new Error('学生信息不存在'); + } + + const grades = await Score.findByStudentId(userId); + + // 计算统计信息 + let totalCredits = 0; + let totalGradePoints = 0; + const totalCourses = grades.length; + + grades.forEach(grade => { + const credit = parseFloat(grade.credit); + totalCredits += credit; + if (grade.grade_point) { + totalGradePoints += parseFloat(grade.grade_point) * credit; + } + }); + + const gpa = totalCredits > 0 ? (totalGradePoints / totalCredits).toFixed(2) : 0; + const averageScore = totalCourses > 0 ? + (grades.reduce((sum, g) => sum + parseFloat(g.score || 0), 0) / totalCourses).toFixed(1) : 0; + + return { + grades, + statistics: { + totalCourses, + totalCredits, + gpa, + averageScore + } + }; + } + + static async getGradeDetails(scoreId, userId) { + const grade = await Score.findDetailsById(scoreId, userId); + if (!grade) { + throw new Error('成绩不存在'); + } + return grade; + } +} + +module.exports = StudentService; \ No newline at end of file diff --git a/backend/services/teacherService.js b/backend/services/teacherService.js new file mode 100644 index 0000000..ac73c3e --- /dev/null +++ b/backend/services/teacherService.js @@ -0,0 +1,50 @@ +const Course = require('../models/Course'); +const Score = require('../models/Score'); +const Student = require('../models/Student'); + +class TeacherService { + static async getCourses(teacherId) { + return await Course.findByTeacherId(teacherId); + } + + static async addScore(teacherId, scoreData) { + const { studentId, courseId, score } = scoreData; + + // 验证学生 + const student = await Student.findById(studentId); + if (!student) { + throw new Error('学生不存在'); + } + + // 验证课程(可选:验证是否是该教师的课程) + // const course = await Course.findById(courseId); + + // 检查重复 + const existingScore = await Score.findByStudentAndCourse(studentId, courseId); + if (existingScore) { + throw new Error('该学生此课程成绩已存在'); + } + + // 计算绩点和等级 + const numericScore = parseFloat(score); + let gradePoint = 0; + let gradeLevel = 'F'; + + if (numericScore >= 90) { gradePoint = 4.0; gradeLevel = 'A'; } + else if (numericScore >= 80) { gradePoint = 3.0; gradeLevel = 'B'; } + else if (numericScore >= 70) { gradePoint = 2.0; gradeLevel = 'C'; } + else if (numericScore >= 60) { gradePoint = 1.0; gradeLevel = 'D'; } + + const fullScoreData = { + ...scoreData, + teacherId, + gradePoint, + gradeLevel + }; + + const gradeId = await Score.create(fullScoreData); + return gradeId; + } +} + +module.exports = TeacherService; \ No newline at end of file diff --git a/backend/utils/response.js b/backend/utils/response.js new file mode 100644 index 0000000..ef84e14 --- /dev/null +++ b/backend/utils/response.js @@ -0,0 +1,16 @@ +const success = (res, data = null, message = 'Success') => { + res.json({ + success: true, + message, + data + }); +}; + +const error = (res, message = 'Internal Server Error', statusCode = 500) => { + res.status(statusCode).json({ + success: false, + message + }); +}; + +module.exports = { success, error }; \ No newline at end of file