feat: 实现成绩管理系统核心功能

添加响应工具、错误处理中间件和数据库模型
创建用户、学生、课程和成绩相关服务
实现管理员、教师和学生控制器的基本功能
重构路由处理并优化数据库查询
This commit is contained in:
祀梦
2025-12-21 22:10:48 +08:00
parent bcf2c71fad
commit b9a975004b
20 changed files with 659 additions and 937 deletions

View File

@@ -11,54 +11,32 @@ const pool = mysql.createPool({
queueLimit: 0 queueLimit: 0
}); });
// 测试数据库连接 // 封装基本查询方法
async function testConnection() { const query = async (sql, params) => {
try { try {
const connection = await pool.getConnection(); const [rows] = await pool.execute(sql, params);
console.log('数据库连接成功'); return rows;
connection.release(); } catch (error) {
return true; console.error('Database query error:', error);
} catch (error) { throw 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);
} }
};
await connection.commit(); // 测试连接
console.log('事务执行成功'); const testConnection = async () => {
} catch (error) { try {
await connection.rollback(); const connection = await pool.getConnection();
console.error('事务执行失败:', error.message); console.log('数据库连接成功');
throw error; connection.release();
} finally { return true;
connection.release(); } catch (error) {
} console.error('数据库连接失败:', error.message);
} return false;
}
};
module.exports = { module.exports = {
pool, pool,
query, query,
executeTransaction, testConnection
testConnection
}; };

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

17
backend/models/Course.js Normal file
View File

@@ -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;

55
backend/models/Score.js Normal file
View File

@@ -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;

23
backend/models/Student.js Normal file
View File

@@ -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;

33
backend/models/User.js Normal file
View File

@@ -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;

View File

@@ -1,311 +1,9 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const db = require('../config/database'); const AdminController = require('../controllers/adminController');
const { requireAuth, requireRole } = require('../middleware/auth'); const { requireAuth, requireRole } = require('../middleware/auth');
/** router.get('/users', requireAuth, requireRole(['admin']), AdminController.getUsers);
* 获取所有用户 router.post('/users', requireAuth, requireRole(['admin']), AdminController.createUser);
*/
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: '服务器错误'
});
}
});
module.exports = router; module.exports = router;

View File

@@ -1,175 +1,10 @@
const express = require('express'); const express = require('express');
const bcrypt = require('bcryptjs');
const router = express.Router(); const router = express.Router();
const db = require('../config/database'); const AuthController = require('../controllers/authController');
// 登录 router.post('/login', AuthController.login);
router.post('/login', async (req, res) => { router.post('/register', AuthController.register);
try { router.post('/logout', AuthController.logout);
const { id, password, role } = req.body; router.get('/me', AuthController.getCurrentUser);
// 输入验证
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
});
});
module.exports = router; module.exports = router;

View File

@@ -1,143 +1,9 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const db = require('../config/database'); const StudentController = require('../controllers/studentController');
const { requireAuth, requireRole } = require('../middleware/auth'); const { requireAuth, requireRole } = require('../middleware/auth');
// 获取学生成绩 router.get('/grades', requireAuth, requireRole(['student']), StudentController.getGrades);
router.get('/grades', requireAuth, requireRole(['student']), async (req, res) => { router.get('/grades/:id', requireAuth, requireRole(['student']), StudentController.getGradeDetails);
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: '服务器错误'
});
}
});
module.exports = router; module.exports = router;

View File

@@ -1,243 +1,9 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const db = require('../config/database'); const TeacherController = require('../controllers/teacherController');
const { requireAuth, requireRole } = require('../middleware/auth'); const { requireAuth, requireRole } = require('../middleware/auth');
// 获取教师教授的课程 router.get('/courses', requireAuth, requireRole(['teacher']), TeacherController.getCourses);
router.get('/courses', requireAuth, requireRole(['teacher']), async (req, res) => { router.post('/grades', requireAuth, requireRole(['teacher']), TeacherController.addScore);
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: '服务器错误'
});
}
});
module.exports = router; module.exports = router;

View File

@@ -5,19 +5,21 @@ const MySQLStore = require('express-mysql-session')(session);
const path = require('path'); const path = require('path');
require('dotenv').config(); 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 authRoutes = require('./routes/auth');
const studentRoutes = require('./routes/student'); const studentRoutes = require('./routes/student');
const teacherRoutes = require('./routes/teacher'); const teacherRoutes = require('./routes/teacher');
const adminRoutes = require('./routes/admin'); const adminRoutes = require('./routes/admin');
// 数据库配置
const db = require('./config/database');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
// 中间件 // Middleware
app.use(cors({ app.use(cors({
origin: 'http://localhost:3000', origin: 'http://localhost:3000',
credentials: true credentials: true
@@ -25,9 +27,9 @@ app.use(cors({
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
// 会话配置 // Session
const sessionStore = new MySQLStore({ const sessionStore = new MySQLStore({
expiration: 86400000, // 1天 expiration: 86400000,
createDatabaseTable: true, createDatabaseTable: true,
schema: { schema: {
tableName: 'sessions', tableName: 'sessions',
@@ -52,14 +54,13 @@ app.use(session({
} }
})); }));
// 静态文件服务 - 只公开 public 目录 // Static Files
app.use('/public', express.static(path.join(__dirname, '../frontend/public'))); app.use('/public', express.static(path.join(__dirname, '../frontend/public')));
// 页面认证中间件 // View Routes (HTML Serving)
// 为了简单起见,这里仍然直接 serve HTML未来可以考虑使用模板引擎或分离前端部署
const requirePageAuth = (req, res, next) => { const requirePageAuth = (req, res, next) => {
if (!req.session.user) { if (!req.session.user) return res.redirect('/login');
return res.redirect('/login');
}
next(); next();
}; };
@@ -73,7 +74,7 @@ const requirePageRole = (allowedRoles) => {
}; };
}; };
// 页面路由 // --- Page Routes ---
app.get('/', (req, res) => res.redirect('/login')); app.get('/', (req, res) => res.redirect('/login'));
app.get('/login', (req, res) => { app.get('/login', (req, res) => {
if (req.session.user) return res.redirect('/dashboard'); 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) => { app.get('/student/dashboard', requirePageAuth, requirePageRole(['student']), (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/views/student/dashboard.html')); res.sendFile(path.join(__dirname, '../frontend/views/student/dashboard.html'));
}); });
// 教师页面 // Teacher Pages
const teacherRouter = express.Router(); const teacherPageRouter = express.Router();
teacherRouter.use(requirePageAuth, requirePageRole(['teacher'])); teacherPageRouter.use(requirePageAuth, requirePageRole(['teacher']));
teacherRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/dashboard.html'))); teacherPageRouter.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'))); teacherPageRouter.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'))); teacherPageRouter.get('/grade_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_management.html')));
app.use('/teacher', teacherRouter); app.use('/teacher', teacherPageRouter);
// 管理员页面 // Admin Pages
const adminRouter = express.Router(); const adminPageRouter = express.Router();
adminRouter.use(requirePageAuth, requirePageRole(['admin'])); adminPageRouter.use(requirePageAuth, requirePageRole(['admin']));
adminRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/dashboard.html'))); adminPageRouter.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'))); adminPageRouter.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'))); adminPageRouter.get('/user_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/user_management.html')));
app.use('/admin', adminRouter); app.use('/admin', adminPageRouter);
// API 路由 // --- API Routes ---
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
app.use('/api/student', studentRoutes); app.use('/api/student', studentRoutes);
app.use('/api/teacher', teacherRoutes); app.use('/api/teacher', teacherRoutes);
app.use('/api/admin', adminRoutes); app.use('/api/admin', adminRoutes);
// 404处理 // Error Handler
app.use((req, res) => { app.use((req, res) => {
res.status(404).json({ error: 'Not found' }); res.status(404).json({ success: false, message: 'Not Found' });
});
// 错误处理
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
}); });
app.use(errorHandler);
// Start Server
app.listen(PORT, async () => { app.listen(PORT, async () => {
console.log(`Server running on port ${PORT}`); console.log(`Server running on port ${PORT}`);
console.log(`访问地址: http://localhost:${PORT}`); console.log(`访问地址: http://localhost:${PORT}`);
await db.testConnection();
// 测试数据库连接
const dbConfig = require('./config/database');
await dbConfig.testConnection();
}); });

View File

@@ -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;

View File

@@ -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根据之前的 SQLstudents.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;

View File

@@ -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;

View File

@@ -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;

16
backend/utils/response.js Normal file
View File

@@ -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 };