Files
est-api/farmeworkapi/farmeworkapi.js
2025-12-25 03:32:20 +00:00

1224 lines
35 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const mysql = require('mysql2/promise');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const crypto = require('crypto');
const fs = require('fs'); // 使用同步版本的 fs
const fsPromises = require('fs').promises; // 使用 promises 版本的 fs
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const CryptoJS = require('crypto-js');
const util = require('util');
const fileUpload = require('express-fileupload');
const app = express();
// 创建日志文件流
const logStream = fs.createWriteStream(path.join(__dirname, 'server.log'), { flags: 'a' });
// 创建管理员日志文件流
const adminLogStream = fs.createWriteStream(path.join(__dirname, 'admin.log'), { flags: 'a' });
// 自定义日志函数
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `${timestamp} - ${message}\n`;
console.log(logMessage);
logStream.write(logMessage);
}
// 允许所有源的 CORS 请求
app.use(cors());
app.use(express.json());
app.use(fileUpload({
limits: { fileSize: 5 * 1024 * 1024 }, // 限制文件大小为5MB
abortOnLimit: true
}));
// 创建数据库连接池
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
charset: 'utf8mb4'
});
// 创建 SurveyKing 数据库连接池
const surveyKingPool = mysql.createPool({
host: process.env.SurveyKing_DB_HOST,
port: process.env.SurveyKing_DB_PORT,
user: process.env.SurveyKing_DB_USER,
password: process.env.SurveyKing_DB_PASSWORD,
database: process.env.SurveyKing_DB_NAME,
charset: 'utf8mb4'
});
// 在文件顶部的导入语句之后添加
pool.getConnection()
.then(connection => {
log('Successfully connected to the database.');
connection.release();
})
.catch(err => {
log(`Error connecting to the database: ${err}`);
});
// 在文件顶部的导入语句之后添加
surveyKingPool.getConnection()
.then(connection => {
log('Successfully connected to the SurveyKing database.');
connection.release();
})
.catch(err => {
log(`Error connecting to the SurveyKing database: ${err}`);
});
// 验证令牌的中间件
const authenticateToken = async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [decoded.userId]);
if (rows.length === 0 || new Date() > new Date(rows[0].expiration_date)) {
return res.status(403).json({ error: '无效的令牌或账号已过期' });
}
// 检查token是否是最新的
if (token !== rows[0].active_token) {
return res.status(403).json({ error: '您的账号已在其他设备登录' });
}
req.user = rows[0];
next();
} catch (error) {
return res.status(403).json({ error: '无效的令牌' });
}
};
// 生成一个简单的密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
// 添加获取公钥的路由
app.get('/public-key', (req, res) => {
res.json({ publicKey });
});
// 登录路由
app.post('/login', async (req, res) => {
const { data, key, iv } = req.body;
try {
// 解密数据
const decrypted = CryptoJS.AES.decrypt(
data,
CryptoJS.enc.Base64.parse(key),
{
iv: CryptoJS.enc.Base64.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
// 将解密后的数据转换为字符串
const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
// 解析 JSON 数据
const { student_id_or_username, password } = JSON.parse(decryptedString);
log(`Login attempt for: ${student_id_or_username}`);
try {
const [rows] = await pool.query('SELECT * FROM users WHERE username = ? OR student_id = ?', [student_id_or_username, student_id_or_username]);
log(`Database query result: ${rows}`);
if (rows.length === 0) {
log('User not found');
return res.status(401).json({ error: '用户名/邮箱或密码错误' });
}
const user = rows[0];
if (new Date() > new Date(user.expiration_date)) {
log(`Account expired for user: ${user.username}`);
return res.status(403).json({ error: '账户已过期请联系系统管理员xxx' });
}
const isPasswordValid = await bcrypt.compare(password, user.password);
log(`Password validation result: ${isPasswordValid}`);
if (!isPasswordValid) {
log('Invalid password');
return res.status(401).json({ error: '用户名/学号或密码错误' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '24h' });
// 更新用户的active_token
await pool.query('UPDATE users SET active_token = ?, last_login = ? WHERE id = ?', [token, new Date(), user.id]);
log(`Login successful for user: ${user.username}`);
res.json({
success: true,
username: user.username,
token,
level: user.level,
model:LICENSE_INFO.model
});
} catch (error) {
log(`登录失败: ${error}`);
res.status(500).json({ error: '登录失败', details: error.message });
}
} catch (error) {
log(`登录解密失败: ${error}`);
res.status(500).json({
error: '登录失败',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// 登入路由
app.post('/logout', authenticateToken, (req, res) => {
res.json({ success: true });
});
// 检认证状态
app.get('/check-auth', authenticateToken, async (req, res) => {
try {
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [req.user.id]);
if (rows.length === 0 || new Date() > new Date(rows[0].expiration_date)) {
return res.status(403).json({ error: '账户已过期或无效' });
}
res.json({
isAuthenticated: true,
username: req.user.username,
level: req.user.level,
organization: req.user.organization,
});
} catch (error) {
log(`检查认证状态失败: ${error}`);
res.status(500).json({ error: '检查认证状态失败' });
}
});
// 验证令牌
app.post('/verify-token', async (req, res) => {
const { token } = req.body;
if (!token) {
return res.json({ valid: false });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [decoded.userId]);
if (rows.length === 0 || new Date() > new Date(rows[0].expiration_date)) {
return res.json({ valid: false });
}
res.json({ valid: true, username: rows[0].username, level: rows[0].level });
} catch (error) {
res.json({ valid: false });
}
});
// 修改获取用户信息路由
app.get('/user-info', authenticateToken, async (req, res) => {
try {
const [rows] = await pool.query(
'SELECT username, class_name, student_id, organization, created_at, last_login, level FROM users WHERE id = ?',
[req.user.id]
);
if (rows.length > 0) {
res.json({
class_name: rows[0].class_name,
username: rows[0].username,
student_id: rows[0].student_id,
organization: LICENSE_INFO.user,
created_at: rows[0].created_at,
last_login: rows[0].last_login,
level: rows[0].level,
model:LICENSE_INFO.model
});
} else {
res.status(404).json({ error: '用户不存在' });
}
} catch (error) {
log(`获取用户信息失败: ${error}`);
res.status(500).json({ error: '获取用户信息失败' });
}
});
// 验证管理员权限
app.get('/verify-admin', authenticateToken, async (req, res) => {
try {
const [rows] = await pool.query('SELECT level FROM users WHERE id = ?', [req.user.id]);
if (rows.length > 0 && rows[0].level >= 7) {
res.json({ isAdmin: true });
} else {
res.json({ isAdmin: false });
}
} catch (error) {
log(`验证管理员权限失败: ${error}`);
res.status(500).json({ error: '验证管理员权限失败' });
}
});
// 查询用户
app.get('/admin/users', authenticateToken, async (req, res) => {
try {
const [userRows] = await pool.query('SELECT level FROM users WHERE id = ?', [req.user.id]);
if (userRows.length === 0 || userRows[0].level < 7) {
return res.status(403).json({ error: '没有权限访问此资源' });
}
const [rows] = await pool.query(
'SELECT id, username, student_id, class_name, organization, created_at, last_login, level FROM users'
);
res.json(rows);
} catch (error) {
log(`获取用户列表失败: ${error}`);
res.status(500).json({ error: '获取用户列表失败' });
}
});
// 批量创建用户
app.post('/admin/users', authenticateToken, async (req, res) => {
try {
// 权限校验:仅管理员(level >= 3)可执行
const [adminCheck] = await pool.query('SELECT level FROM users WHERE id = ?', [req.user.id]);
if (adminCheck.length === 0 || adminCheck[0].level < 7) {
return res.status(403).json({ success: false, error: '没有权限执行此操作' });
}
const { class_name, student_ids } = req.body;
if (!class_name || !student_ids) {
return res.status(400).json({ success: false, error: '请提供班级和学号' });
}
// 支持多种分隔符:空格、英文逗号、中文逗号、换行
const idList = student_ids
.split(/[\s,\n]+/)
.map(id => id.trim())
.filter(id => id.length > 0);
if (idList.length === 0) {
return res.status(400).json({ success: false, error: '未检测到有效学号' });
}
// 根据LICENSE_INFO.model确定最大用户数量
let maxUsers = 0; // 默认值
const licenseModel = LICENSE_INFO.model || '';
if (licenseModel.includes('EST-05E')) {
maxUsers = 10;
} else if (licenseModel.includes('EST-10E')) {
maxUsers = 60;
} else if (licenseModel.includes('EST-100E')) {
maxUsers = 100;
} else if (licenseModel.includes('EST-05C')) {
maxUsers = 10;
} else if (licenseModel.includes('EST-10C')) {
maxUsers = 60;
} else if (licenseModel.includes('EST-100C')) {
maxUsers = 100;
} else if (licenseModel.includes('EST-10A')) {
maxUsers = 60;
} else if (licenseModel.includes('EST-100A')) {
maxUsers = 100;
}else if (licenseModel.includes('EST-100D')) {
maxUsers = 100;
}
// 检查当前用户数量
const [currentUsers] = await pool.query('SELECT COUNT(*) as count FROM users');
const currentUserCount = currentUsers[0].count -1;
// 计算可以创建的最大新用户数量
const maxNewUsers = maxUsers - currentUserCount;
// 如果新用户数量超过限制,返回错误
if (idList.length > maxNewUsers) {
return res.status(400).json({
success: false,
error: `超出许可证用户数量限制,当前许可证(${licenseModel})最多允许${maxUsers}个用户,已有${currentUserCount}个用户,最多可创建${maxNewUsers}个新用户`
});
}
let createdCount = 0;
let skipped = [];
for (const sid of idList) {
// 检查是否已存在
const [exists] = await pool.query('SELECT id FROM users WHERE student_id = ?', [sid]);
if (exists.length > 0) {
skipped.push(sid);
continue;
}
const plainPassword = sid;
const password = await bcrypt.hash(plainPassword, 10); // saltRounds = 10
const organization = LICENSE_INFO.user;
const level = 0
await pool.query(
`INSERT INTO users (username, student_id, class_name, organization, level, password, created_at)
VALUES (?, ?, ?, ?, ?, ?, NOW())`,
[sid, sid, class_name, organization, level, password]
);
createdCount++;
}
res.json({
success: true,
createdCount,
skipped,
message: `成功创建 ${createdCount} 个用户,跳过 ${skipped.length} 个已存在的用户`,
licenseInfo: {
model: licenseModel,
maxUsers: maxUsers,
currentUsers: currentUserCount + createdCount,
remainingSlots: maxUsers - (currentUserCount + createdCount)
}
});
} catch (error) {
console.error('批量创建用户失败:', error);
res.status(500).json({ success: false, error: '批量创建用户失败', details: error.message });
}
});
// 删除用户
app.delete('/admin/users/:student_id', authenticateToken, async (req, res) => {
try {
const [adminCheck] = await pool.query('SELECT level FROM users WHERE id = ?', [req.user.id]);
if (adminCheck.length === 0 || adminCheck[0].level < 7) {
return res.status(403).json({ success: false, error: '没有权限执行此操作' });
}
const studentId = req.params.student_id;
const [result] = await pool.query('DELETE FROM users WHERE student_id = ?', [studentId]);
if (result.affectedRows === 0) {
return res.status(404).json({ success: false, error: '未找到该用户' });
}
res.json({ success: true, message: `已删除用户 ${studentId}` });
} catch (error) {
console.error('删除用户失败:', error);
res.status(500).json({ success: false, error: '删除用户失败' });
}
});
let onlineUsers = new Map();
let onlineHistory = [];
const ONLINE_DATA_FILE = path.join(__dirname, 'online_data.json');
const LONG_TERM_HISTORY_FILE = path.join(__dirname, 'long_term_history.json');
// 加载在线用户数据
async function loadOnlineData() {
try {
const data = await fsPromises.readFile(ONLINE_DATA_FILE, 'utf8');
const parsedData = JSON.parse(data);
onlineHistory = parsedData.history;
onlineUsers = new Map(parsedData.users.map(([id, user]) => [parseInt(id), user]));
} catch (error) {
if (error.code !== 'ENOENT') {
console.error('加载在线用户数据失败:', error);
}
}
}
// 保存在线用户数据
async function saveOnlineData() {
try {
const data = JSON.stringify({
history: onlineHistory,
users: Array.from(onlineUsers.entries())
});
await fsPromises.writeFile(ONLINE_DATA_FILE, data, 'utf8');
} catch (error) {
console.error('保存在线用户数据失败:', error);
}
}
// 更新在线用户并记录历史
async function updateOnlineUsers() {
const now = new Date();
// 清理超时的用户例如15分钟无活动
for (const [userId, userData] of onlineUsers.entries()) {
if (now - new Date(userData.lastActivity) > 5 * 60 * 1000) {
onlineUsers.delete(userId);
}
}
const currentOnlineUsers = {
time: now.toISOString(),
count: onlineUsers.size,
users: Array.from(onlineUsers.values()).map(u => u.username)
};
onlineHistory.push(currentOnlineUsers);
// 将超过24小时的记录移动到长期历史记录
const oneDayAgo = new Date(now - 24 * 60 * 60 * 1000);
const oldRecords = onlineHistory.filter(item => new Date(item.time) <= oneDayAgo);
onlineHistory = onlineHistory.filter(item => new Date(item.time) > oneDayAgo);
await saveOnlineData();
}
// 每10秒更新一次在线用户状态
setInterval(updateOnlineUsers, 10000);
// 在服务器启动时加载在线用户数据
loadOnlineData().then(() => {
console.log('在线用户数据加载完成');
});
// 登录路由
app.post('/login', async (req, res) => {
// ... 现有的登录逻辑 ...
if (response.data.success) {
// 添加用户到在线列表
onlineUsers.set(user.id, {
username: user.username,
lastActivity: new Date().toISOString()
});
await saveOnlineData();
}
res.json({ success: true });
});
// 获取在线用户和历史记录
app.get('/online-users', authenticateToken, (req, res) => {
res.json({
currentOnline: {
count: onlineUsers.size,
users: Array.from(onlineUsers.values()).map(u => u.username)
},
history: onlineHistory
});
});
// 更新用户活动时间
app.post('/update-activity', authenticateToken, async (req, res) => {
if (onlineUsers.has(req.user.id)) {
onlineUsers.get(req.user.id).lastActivity = new Date().toISOString();
} else {
onlineUsers.set(req.user.id, {
username: req.user.username,
lastActivity: new Date().toISOString()
});
}
await saveOnlineData();
res.sendStatus(200);
});
// 登出路由
app.post('/logout', authenticateToken, async (req, res) => {
onlineUsers.delete(req.user.id);
await saveOnlineData();
res.json({ success: true });
});
// --------------------------LIC验证-------------------
// 全局许可证信息
const LICENSE_INFO = {
isValid: false,
model: "",
user: "",
serial: "",
activation_code: "",
activated_at: "",
expires_at: "",
gold_service_expires_at: "",
issued_at: "",
issuer: "",
hardware_id: "" // 新增硬件码字段
};
// 获取系统硬件序列号
async function getHardwareSerial() {
try {
const hardwareSerial = await fsPromises.readFile('/hardware_serial', 'utf8');
return hardwareSerial.trim();
} catch (error) {
log(`读取硬件序列号失败: ${error.message}`);
throw error;
}
}
// License 验证相关功能
// 获取目录下所有 .lic 文件
async function getLicenseFiles() {
try {
const licenseDir = path.join(__dirname, 'license');
const files = await fsPromises.readdir(licenseDir);
return files.filter(file => file.endsWith('.lic'));
} catch (error) {
if (error.code === 'ENOENT') {
// 如果目录不存在,返回空数组
return [];
}
log(`获取 License 文件失败: ${error.message}`);
throw error;
}
}
// 清空 LICENSE_INFO
function clearLicenseInfo() {
Object.keys(LICENSE_INFO).forEach(key => {
LICENSE_INFO[key] = typeof LICENSE_INFO[key] === 'boolean' ? false : "";
});
}
// 读取公钥文件
async function readPublicKey() {
try {
const pubKeyPath = path.join(__dirname, 'pub.pem');
const publicKeyData = await fsPromises.readFile(pubKeyPath, 'utf8');
return publicKeyData;
} catch (error) {
log(`读取公钥文件失败: ${error.message}`);
throw error;
}
}
// 验证 License 文件
async function verifyLicense(licenseFile, publicKey) {
try {
const licenseDir = path.join(__dirname, 'license');
const licenseData = await fsPromises.readFile(path.join(licenseDir, licenseFile), 'utf8');
const license = JSON.parse(licenseData);
// 解码 payload
const payloadStr = Buffer.from(license.payload, 'base64').toString('utf8');
const payload = JSON.parse(payloadStr);
// 验证签名
const verify = crypto.createVerify('SHA256');
verify.update(payloadStr);
const isValid = verify.verify(publicKey, license.signature, 'base64');
// 验证硬件码匹配
const hardwareSerial = await getHardwareSerial();
const hardwareMatches = payload.hardware_id === hardwareSerial;
// 只有在签名验证成功且硬件码匹配时才更新许可证信息
if (isValid && hardwareMatches) {
LICENSE_INFO.isValid = true;
LICENSE_INFO.model = payload.model || "";
LICENSE_INFO.user = payload.user || "";
LICENSE_INFO.serial = payload.serial || "";
LICENSE_INFO.activation_code = payload.activation_code || "";
LICENSE_INFO.activated_at = payload.activated_at || "";
LICENSE_INFO.expires_at = payload.expires_at || "";
LICENSE_INFO.gold_service_expires_at = payload.gold_service_expires_at || "";
LICENSE_INFO.issued_at = payload.issued_at || "";
LICENSE_INFO.issuer = payload.issuer || "";
LICENSE_INFO.hardware_id = payload.hardware_id || "";
} else {
clearLicenseInfo();
}
return {
isValid,
hardwareMatches,
licenseFile,
payload
};
} catch (error) {
log(`验证 License 文件失败 (${licenseFile}): ${error.message}`);
clearLicenseInfo();
return {
isValid: false,
hardwareMatches: false,
licenseFile,
error: error.message
};
}
}
// 验证所有 License 文件
async function verifyAllLicenses() {
try {
const licenseFiles = await getLicenseFiles();
if (licenseFiles.length === 0) {
log('未找到任何 License 文件');
clearLicenseInfo();
return [];
}
// 只验证最新的 license 文件
const latestLicenseFile = licenseFiles[licenseFiles.length - 1];
log(`验证最新的 License 文件: ${latestLicenseFile}`);
try {
const publicKey = await readPublicKey();
log('成功读取公钥文件 pub.pem');
const result = await verifyLicense(latestLicenseFile, publicKey);
// 输出验证结果
if (result.isValid && result.hardwareMatches) {
log(`License 验证成功: ${latestLicenseFile}`);
log(`License 信息: ${JSON.stringify(result.payload, null, 2)}`);
} else {
log(`License 验证失败: ${latestLicenseFile}`);
if (!result.isValid) {
log('签名验证失败');
}
if (!result.hardwareMatches) {
log('硬件码不匹配');
}
if (result.error) {
log(`错误信息: ${result.error}`);
}
clearLicenseInfo();
}
return [result];
} catch (error) {
log(`读取公钥失败: ${error.message}`);
clearLicenseInfo();
return [{
isValid: false,
hardwareMatches: false,
licenseFile: latestLicenseFile,
error: '无法读取公钥文件'
}];
}
} catch (error) {
log(`验证所有 License 文件失败: ${error.message}`);
clearLicenseInfo();
throw error;
}
}
// 获取许可证信息
function getLicenseInfo() {
return LICENSE_INFO;
}
// 添加获取许可证信息的API端点
app.get('/license-info', authenticateToken, (req, res) => {
res.json({
success: true,
licenseInfo: LICENSE_INFO
});
});
// 添加获取产品型号的API端点
app.get('/product-model', (req, res) => {
res.json({
success: true,
isValid: LICENSE_INFO.isValid,
model: LICENSE_INFO.model
});
});
// 添加上传许可证文件的API端点
app.post('/upload-license', async (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).json({ success: false, error: '未上传文件' });
}
const licenseFile = req.files.license;
// 验证文件扩展名
if (!licenseFile.name.endsWith('.lic')) {
return res.status(400).json({ success: false, error: '文件必须是.lic格式' });
}
try {
// 验证硬件码匹配
const hardwareSerial = await getHardwareSerial();
// 读取并解析上传的 license 文件内容
const licenseContent = licenseFile.data.toString('utf8');
const license = JSON.parse(licenseContent);
const payloadStr = Buffer.from(license.payload, 'base64').toString('utf8');
const payload = JSON.parse(payloadStr);
// 检查硬件码是否匹配
if (payload.hardware_id !== hardwareSerial) {
return res.status(400).json({
success: false,
error: '硬件码不匹配,无法使用此许可证'
});
}
// 确保 license 目录存在
const licenseDir = path.join(__dirname, 'license');
await fsPromises.mkdir(licenseDir, { recursive: true });
// 删除现有的所有 license 文件
const existingFiles = await getLicenseFiles();
for (const file of existingFiles) {
await fsPromises.unlink(path.join(licenseDir, file));
log(`删除旧的许可证文件: ${file}`);
}
// 保存新的 license 文件
await licenseFile.mv(path.join(licenseDir, licenseFile.name));
log(`新的许可证文件 ${licenseFile.name} 上传成功到 license 目录`);
// 重新验证许可证
await verifyAllLicenses();
res.json({
success: true,
message: '许可证文件上传并验证成功',
licenseInfo: LICENSE_INFO
});
} catch (error) {
log(`许可证文件上传失败: ${error.message}`);
res.status(500).json({ success: false, error: '许可证文件上传失败' });
}
});
// 检查许可证状态的API端点
app.get('/license-status', (req, res) => {
res.json({
success: true,
isValid: LICENSE_INFO.isValid
});
});
// 监视 license 目录变化
const licenseDir = path.join(__dirname, 'license');
fs.mkdir(licenseDir, { recursive: true }, (err) => {
if (err) {
log(`创建 license 目录失败: ${err.message}`);
return;
}
fs.watch(licenseDir, async (eventType, filename) => {
if (filename && filename.endsWith('.lic')) {
log(`检测到 license 目录变化: ${eventType} - ${filename}`);
try {
await verifyAllLicenses();
} catch (error) {
log(`处理 license 目录变化时发生错误: ${error.message}`);
}
}
});
});
// 在服务器启动时验证 License 文件
(async function checkLicensesOnStartup() {
log('正在验证 License 文件...');
try {
await verifyAllLicenses();
log('License 验证完成');
} catch (error) {
log(`License 验证过程中发生错误: ${error.message}`);
}
})();
// ---------------------------------------------------------------
// -----------------添加查询 SurveyKing 答案数据的接口----------
app.get('/survey-answers', async (req, res) => {
try {
const { org } = req.query;
if (!org) {
return res.status(400).json({
success: false,
error: '请提供组织名称参数 (org)'
});
}
// 基础查询
const query = `
SELECT
project_id,
answer,
exam_score,
update_at
FROM t_answer
WHERE 1=1
`;
// 执行查询
const [rows] = await surveyKingPool.query(query);
// 处理结果,解析 answer 列中的 JSON 数据并只返回匹配组织的数据
const processedData = rows.map(row => {
try {
const answerData = JSON.parse(row.answer);
// 查找包含 estorg 的键
let organization = '';
let username = '';
// 遍历对象查找 estorg
for (const key in answerData) {
if (answerData[key].estorg) {
organization = answerData[key].estorg;
}
if (answerData[key].estuser) {
username = answerData[key].estuser;
}
}
// 只返回匹配组织的数据
if (organization && organization.toLowerCase() === org.toLowerCase()) {
return {
projectId: row.project_id,
organization: organization,
username: username,
score: row.exam_score,
submitTime: row.update_at
};
}
return null;
} catch (error) {
console.error(`解析答案数据失败: ${error}`);
return null;
}
}).filter(item => item !== null); // 移除不匹配的数据
// 按提交时间降序排序
processedData.sort((a, b) => new Date(b.submitTime) - new Date(a.submitTime));
res.json({
success: true,
total: processedData.length,
data: processedData
});
} catch (error) {
console.error(`获取答案数据失败: ${error}`);
res.status(500).json({
success: false,
error: '获取答案数据失败',
message: error.message
});
}
});
// --------------------------------------------------
// -----------场景化功能切换API-----------------------
app.post('/admin/toggle-scenario', authenticateToken, async (req, res) => {
try {
// 验证管理员权限
const [adminCheck] = await pool.query('SELECT level FROM users WHERE id = ?', [req.user.id]);
if (adminCheck.length === 0 || adminCheck[0].level < 7) {
return res.status(403).json({
success: false,
message: '没有权限执行此操作'
});
}
const { student_id, new_level } = req.body;
// 如果是要关闭场景化功能,直接执行
if (new_level === 0) {
const [updateResult] = await pool.query(
'UPDATE users SET level = ? WHERE student_id = ?',
[0, student_id]
);
if (updateResult.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '未找到该用户'
});
}
return res.json({
success: true,
message: '已关闭场景化功能',
new_level: 0
});
}
// 如果是要开启场景化功能,需要检查许可证和当前已开启的用户数量
// 根据LICENSE_INFO.model确定最大用户数
let maxScenarioUsers = 0;
const model = LICENSE_INFO.model;
switch (model) {
case 'EST-05E':
maxScenarioUsers = 5;
break;
case 'EST-10E':
maxScenarioUsers = 30;
break;
case 'EST-100E':
maxScenarioUsers = 50;
break;
case 'EST-05C':
maxScenarioUsers = 5;
break;
case 'EST-10C':
maxScenarioUsers = 30;
break;
case 'EST-100C':
maxScenarioUsers = 50;
break;
case 'EST-10A':
maxScenarioUsers = 30;
break;
case 'EST-100A':
maxScenarioUsers = 50;
break;
case 'EST-100D':
maxScenarioUsers = 50;
break;
default:
maxScenarioUsers = 0;
}
// 如果没有有效的许可证或型号不支持
if (!LICENSE_INFO.isValid || maxScenarioUsers === 0) {
return res.status(403).json({
success: false,
message: '无有效许可证或当前型号不支持场景化功能'
});
}
// 查询当前已开启场景化功能的用户数量level=1或level=4
const [countResult] = await pool.query(
'SELECT COUNT(*) as count FROM users WHERE level = 1 OR level = 4'
);
const currentCount = countResult[0].count;
// 如果当前数量已达到最大值,拒绝请求
if (currentCount >= maxScenarioUsers) {
return res.status(403).json({
success: false,
message: `已达到最大场景化用户数量限制${maxScenarioUsers}位用户`
});
}
// 更新用户等级为1开启场景化功能
const [updateResult] = await pool.query(
'UPDATE users SET level = ? WHERE student_id = ?',
[1, student_id]
);
if (updateResult.affectedRows === 0) {
return res.status(404).json({
success: false,
message: '未找到该用户'
});
}
return res.json({
success: true,
message: '已开启场景化功能',
new_level: 1
});
} catch (error) {
console.error('切换场景化功能失败:', error);
res.status(500).json({
success: false,
message: '操作失败,请稍后再试',
error: error.message
});
}
});
// ---------------------------------------------------
// ---------------------宿主机网络配置----------------------------
// 网络配置相关 API
const NETWORK_CONFIG_FILE = path.join(__dirname, 'network');
// 读取网络配置
async function readNetworkConfig() {
try {
const config = await fsPromises.readFile(NETWORK_CONFIG_FILE, 'utf8');
const configObj = {};
config.split('\n').forEach(line => {
const [key, value] = line.split('=').map(part => part.trim());
if (key && value) {
// 移除引号
configObj[key] = value.replace(/^"(.*)"$/, '$1');
}
});
return configObj;
} catch (error) {
log(`读取网络配置失败: ${error.message}`);
throw error;
}
}
// 写入网络配置
async function writeNetworkConfig(config) {
try {
let configContent = '';
for (const [key, value] of Object.entries(config)) {
// DNS 需要特殊处理,添加引号
if (key === 'DNS') {
configContent += `${key}="${value}"\n`;
} else {
configContent += `${key}=${value}\n`;
}
}
await fsPromises.writeFile(NETWORK_CONFIG_FILE, configContent);
} catch (error) {
log(`写入网络配置失败: ${error.message}`);
throw error;
}
}
// 获取网络配置 API
app.get('/network-config', authenticateToken, async (req, res) => {
try {
const config = await readNetworkConfig();
res.json({
success: true,
config
});
} catch (error) {
res.status(500).json({
success: false,
error: '获取网络配置失败'
});
}
});
// 更新网络配置 API
app.post('/network-config', authenticateToken, async (req, res) => {
try {
const { config } = req.body;
// 基本验证
if (!config || typeof config !== 'object') {
return res.status(400).json({
success: false,
error: '无效的配置数据'
});
}
if (!config.BOOTPROTO) {
return res.status(400).json({
success: false,
error: '缺少必需字段: BOOTPROTO'
});
}
// 限定 BOOTPROTO 的取值
if (!['dhcp', 'static'].includes(config.BOOTPROTO)) {
return res.status(400).json({
success: false,
error: 'BOOTPROTO 仅支持 dhcp 或 static'
});
}
// 如果是静态 IP验证额外字段
if (config.BOOTPROTO === 'static') {
const staticFields = ['IPADDR', 'NETMASK', 'GATEWAY', 'DNS'];
for (const field of staticFields) {
if (!config[field]) {
return res.status(400).json({
success: false,
error: `静态 IP 配置缺少必需字段: ${field}`
});
}
}
}
const existingConfig = await readNetworkConfig();
const allowedUpdateKeys = ['BOOTPROTO', 'IPADDR', 'NETMASK', 'GATEWAY', 'DNS'];
const updatedConfig = { ...existingConfig };
for (const key of allowedUpdateKeys) {
if (config[key] !== undefined) {
updatedConfig[key] = config[key];
}
}
if (config.BOOTPROTO === 'dhcp') {
for (const key of ['IPADDR', 'NETMASK', 'GATEWAY', 'DNS']) {
delete updatedConfig[key];
}
}
await writeNetworkConfig(updatedConfig);
res.json({
success: true,
message: '网络配置已更新'
});
} catch (error) {
res.status(500).json({
success: false,
error: '更新网络配置失败'
});
}
});
// ------------------------------------------------------
// -------------------------API启动设置-----------------
const PORT = process.env.PORT || 3000;
// 如果作为主模块运行(独立启动),才开启监听;作为子服务被挂载时不监听端口
if (require.main === module) {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
log(`Server running on port ${PORT}`);
});
}
// 导出 Express 应用,供主入口统一挂载
module.exports = app;
// 在程序退出时关闭日志流
process.on('exit', () => {
logStream.end();
});
// 捕获未捕获的异常并记录日志
process.on('uncaughtException', (error) => {
log(`Uncaught Exception: ${error.message}`);
process.exit(1);
});
// 捕获未处理的 Promise 拒绝并记录日志
process.on('unhandledRejection', (reason, promise) => {
log(`Unhandled Rejection at: ${promise}, reason: ${reason}`);
});