first commit
This commit is contained in:
64
backend/config/database.js
Normal file
64
backend/config/database.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '123456',
|
||||
database: process.env.DB_NAME || 'score_management',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
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);
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
console.log('事务执行成功');
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
console.error('事务执行失败:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pool,
|
||||
query,
|
||||
executeTransaction,
|
||||
testConnection
|
||||
};
|
||||
170
backend/middleware/auth.js
Normal file
170
backend/middleware/auth.js
Normal file
@@ -0,0 +1,170 @@
|
||||
const db = require('../config/database');
|
||||
|
||||
/**
|
||||
* 认证中间件 - 检查用户是否已登录
|
||||
*/
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '请先登录'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* 角色权限中间件 - 检查用户是否具有指定角色
|
||||
* @param {Array} allowedRoles - 允许的角色数组
|
||||
*/
|
||||
const requireRole = (allowedRoles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '请先登录'
|
||||
});
|
||||
}
|
||||
|
||||
const userRole = req.session.user.role;
|
||||
|
||||
if (!allowedRoles.includes(userRole)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
const getCurrentUserId = (req) => {
|
||||
return req.session.user ? req.session.user.id : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户角色
|
||||
*/
|
||||
const getCurrentUserRole = (req) => {
|
||||
return req.session.user ? req.session.user.role : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查是否是自己的数据(学生只能访问自己的数据)
|
||||
*/
|
||||
const checkOwnership = async (req, res, next) => {
|
||||
try {
|
||||
const userId = getCurrentUserId(req);
|
||||
const userRole = getCurrentUserRole(req);
|
||||
|
||||
// 管理员和教师可以访问所有数据
|
||||
if (userRole === 'admin' || userRole === 'teacher') {
|
||||
return next();
|
||||
}
|
||||
|
||||
// 学生只能访问自己的数据
|
||||
if (userRole === 'student') {
|
||||
const studentId = req.params.studentId || req.body.studentId;
|
||||
|
||||
// 检查学生ID是否属于当前用户
|
||||
const [students] = await db.execute(
|
||||
'SELECT id FROM students WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (students.length === 0) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '未找到学生信息'
|
||||
});
|
||||
}
|
||||
|
||||
const userStudentId = students[0].id;
|
||||
|
||||
// 如果请求中没有指定学生ID,默认使用当前用户的学生ID
|
||||
if (!studentId) {
|
||||
req.studentId = userStudentId;
|
||||
return next();
|
||||
}
|
||||
|
||||
// 检查请求的学生ID是否与当前用户的学生ID匹配
|
||||
if (parseInt(studentId) !== userStudentId) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '无权访问其他学生的数据'
|
||||
});
|
||||
}
|
||||
|
||||
req.studentId = userStudentId;
|
||||
return next();
|
||||
}
|
||||
|
||||
// 其他角色无权访问
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('所有权检查错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 输入验证中间件
|
||||
*/
|
||||
const validateInput = (schema) => {
|
||||
return (req, res, next) => {
|
||||
const { error } = schema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: error.details[0].message
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 日志记录中间件
|
||||
*/
|
||||
const logOperation = (operation) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = getCurrentUserId(req);
|
||||
const userRole = getCurrentUserRole(req);
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
|
||||
// 记录操作日志
|
||||
await db.execute(
|
||||
'INSERT INTO operation_logs (user_id, operation, ip_address, user_role) VALUES (?, ?, ?, ?)',
|
||||
[userId, operation, ip, userRole]
|
||||
);
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('日志记录错误:', error);
|
||||
// 日志记录失败不影响主要操作
|
||||
next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
requireAuth,
|
||||
requireRole,
|
||||
getCurrentUserId,
|
||||
getCurrentUserRole,
|
||||
checkOwnership,
|
||||
validateInput,
|
||||
logOperation
|
||||
};
|
||||
1491
backend/package-lock.json
generated
Normal file
1491
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
backend/package.json
Normal file
26
backend/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "grade-management-system",
|
||||
"version": "1.0.0",
|
||||
"description": "学生成绩管理系统后端",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"express-session": "^1.17.3",
|
||||
"express-mysql-session": "^3.0.0",
|
||||
"mysql2": "^3.6.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"body-parser": "^1.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
}
|
||||
311
backend/routes/admin.js
Normal file
311
backend/routes/admin.js
Normal file
@@ -0,0 +1,311 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../config/database');
|
||||
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: '服务器错误'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
175
backend/routes/auth.js
Normal file
175
backend/routes/auth.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const router = express.Router();
|
||||
const db = require('../config/database');
|
||||
|
||||
// 登录
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
143
backend/routes/student.js
Normal file
143
backend/routes/student.js
Normal file
@@ -0,0 +1,143 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../config/database');
|
||||
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: '服务器错误'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
243
backend/routes/teacher.js
Normal file
243
backend/routes/teacher.js
Normal file
@@ -0,0 +1,243 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../config/database');
|
||||
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: '服务器错误'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
128
backend/server.js
Normal file
128
backend/server.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const session = require('express-session');
|
||||
const MySQLStore = require('express-mysql-session')(session);
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
// 导入路由
|
||||
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;
|
||||
|
||||
// 中间件
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:3000',
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 会话配置
|
||||
const sessionStore = new MySQLStore({
|
||||
expiration: 86400000, // 1天
|
||||
createDatabaseTable: true,
|
||||
schema: {
|
||||
tableName: 'sessions',
|
||||
columnNames: {
|
||||
session_id: 'session_id',
|
||||
expires: 'expires',
|
||||
data: 'data'
|
||||
}
|
||||
}
|
||||
}, db.pool);
|
||||
|
||||
app.use(session({
|
||||
key: 'session_cookie',
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
store: sessionStore,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
maxAge: 86400000,
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production'
|
||||
}
|
||||
}));
|
||||
|
||||
// 静态文件服务
|
||||
app.use(express.static(path.join(__dirname, '../frontend')));
|
||||
|
||||
// 重定向旧路径 /frontend/html/* 到 /html/*
|
||||
app.get('/frontend/html/*', (req, res) => {
|
||||
const path = req.params[0];
|
||||
res.redirect(`/html/${path}`);
|
||||
});
|
||||
|
||||
// 路由
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/student', studentRoutes);
|
||||
app.use('/api/teacher', teacherRoutes);
|
||||
app.use('/api/admin', adminRoutes);
|
||||
|
||||
// 认证中间件
|
||||
const { requireAuth, requireRole } = require('./middleware/auth');
|
||||
|
||||
// 页面路由
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../frontend/html/login.html'));
|
||||
});
|
||||
|
||||
app.get('/dashboard', requireAuth, (req, res) => {
|
||||
// 根据用户角色重定向到不同的仪表板
|
||||
const role = req.session.user?.role;
|
||||
|
||||
switch (role) {
|
||||
case 'student':
|
||||
res.redirect('/html/student_dashboard.html');
|
||||
break;
|
||||
case 'teacher':
|
||||
res.redirect('/html/teacher_dashboard.html');
|
||||
break;
|
||||
case 'admin':
|
||||
res.redirect('/html/admin_dashboard.html');
|
||||
break;
|
||||
default:
|
||||
// 如果没有角色信息,重定向到登录页面
|
||||
res.redirect('/');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// 学生页面路由
|
||||
app.get('/student/*', requireAuth, requireRole(['student', 'admin', 'teacher']), (req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// 教师页面路由
|
||||
app.get('/teacher/*', requireAuth, requireRole(['teacher', 'admin']), (req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// 管理员页面路由
|
||||
app.get('/admin/*', requireAuth, requireRole(['admin']), (req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// 404处理
|
||||
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' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
console.log(`访问地址: http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user