const express = require('express'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const app = express(); // 初始连接映射数据 const initialConnectionMap = { pass: { //---Copper连接检查--- "main-permanent": { type: "copper", connectedTo: "remote-channel", wiremapstatus: "pass", performancestatus: "pass" }, "remote-channel": { type: "copper", connectedTo: "main-channel", wiremapstatus: "pass", performancestatus: "pass" }, "main-channel": { type: "copper", connectedTo: "remote-permanent", wiremapstatus: "pass", performancestatus: "pass" }, //---CFP连接检查--- "main-cfp-sm-out": { type: "fiber", connectedTo: "remote-cfp-in", fiberstatus: "ref-pass" }, "main-cfp-mm-out": { type: "fiber", connectedTo: "remote-cfp-in", fiberstatus: "ref-pass" }, "remote-cfp-sm-out": { type: "fiber", connectedTo: "main-cfp-in", fiberstatus: "ref-pass" }, "remote-cfp-mm-out": { type: "fiber", connectedTo: "main-cfp-in", fiberstatus: "ref-pass" }, }, Office: { "Room1-TO-1": { type: "copper", apitype:"t568b", connectedTo: "1A-1", wiremapstatus: "pass", performancestatus: "pass" }, "Room1-TO-2": { type: "copper", apitype:"t568b", connectedTo: "1A-2", wiremapstatus: "open", performancestatus: "pass" }, "Room2-TO-1": { type: "copper", apitype:"t568b", connectedTo: "1A-3", wiremapstatus: "short", performancestatus: "pass" }, "Room2-TO-2": { type: "copper", apitype:"t568b", connectedTo: "1A-4", wiremapstatus: "cross", performancestatus: "pass" }, "Room3-TO-1": { type: "copper", apitype:"t568b", connectedTo: "1A-5", wiremapstatus: "reversed", performancestatus: "pass" }, "Room3-TO-2": { type: "copper", apitype:"t568b", connectedTo: "1A-6", wiremapstatus: "miswire", performancestatus: "pass" }, "Room4-CAM": { type: "copper", apitype:"t568b", connectedTo: "1A-7", wiremapstatus: "pass", performancestatus: "return-loss-fail" }, "1B-1": { type: "fiber", apitype:"olts", connectedTo: "1B-5", fiberstatus: "sm-pass" }, "1B-2": { type: "fiber", apitype:"olts", connectedTo: "1B-6", fiberstatus: "connector-fail-start" }, "1B-3": { type: "fiber", apitype:"olts", connectedTo: "1B-7", fiberstatus: "splice-fail" }, "1B-4": { type: "fiber", apitype:"olts", connectedTo: "1B-8", fiberstatus: "bend" } }, Industry: { "Device1-1": { type: "copper", apitype:"workshop-m12", connectedTo: "Cabinet-A1", wiremapstatus: "pass", performancestatus: "pass" }, "Device1-2": { type: "copper", apitype:"workshop-m12", connectedTo: "Cabinet-A2", wiremapstatus: "pass", performancestatus: "pass" }, "Device2-1": { type: "copper", apitype:"workshop-m12", connectedTo: "Cabinet-A3", wiremapstatus: "pass", performancestatus: "pass" }, "Device2-2": { type: "copper", apitype:"workshop-m12", connectedTo: "Cabinet-A4", wiremapstatus: "pass", performancestatus: "pass" }, "Device3-1": { type: "copper", apitype:"workshop-2p", connectedTo: "Cabinet-R1", wiremapstatus: "pass", performancestatus: "pass" }, "Device3-2": { type: "copper", apitype:"workshop-2p", connectedTo: "Cabinet-R2", wiremapstatus: "pass", performancestatus: "pass" }, "Device4-1": { type: "copper", apitype:"workshop-2p", connectedTo: "Cabinet-R3", wiremapstatus: "pass", performancestatus: "pass" }, "Device4-2": { type: "copper", apitype:"workshop-2p", connectedTo: "Cabinet-R4", wiremapstatus: "pass", performancestatus: "pass" } }, DataCenter: { "CA-1A-1-port1": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port1", fiberstatus: "mm-pass" }, "CA-1A-1-port2": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port2", fiberstatus: "mm-pass" }, "CA-1A-1-port3": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port3", fiberstatus: "mm-pass" }, "CA-1A-1-port4": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port4", fiberstatus: "mm-pass" }, "CA-1A-1-port5": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port6", fiberstatus: "mm-pass" }, "CA-1A-1-port6": { type: "fiber", apitype:"olts", connectedTo: "CB-1A-1-port6", fiberstatus: "mm-pass" }, }, SkillCompetition: { "Room1-1": { type: "copper", connectedTo: "1A-1", wiremapstatus: "pass", performancestatus: "pass" }, "Room1-2": { type: "copper", connectedTo: "1A-2", wiremapstatus: "open", performancestatus: "pass" }, "Room2-1": { type: "copper", connectedTo: "1A-11", wiremapstatus: "short", performancestatus: "pass" }, "Room2-2": { type: "copper", connectedTo: "1A-12", wiremapstatus: "cross", performancestatus: "pass" }, "Room3-1": { type: "copper", connectedTo: "1B-5", wiremapstatus: "reversed", performancestatus: "pass" }, "Room3-2": { type: "copper", connectedTo: "1B-6", wiremapstatus: "miswire", performancestatus: "pass" }, }, WorldSkill: { "Room1-TO-1": { type: "copper", apitype:"t568b", connectedTo: "1F-RackA-1A-1", wiremapstatus: "short2", performancestatus: "pass" }, "Room1-TO-2": { type: "copper", apitype:"t568b", connectedTo: "1F-RackA-1A-2", wiremapstatus: "short", performancestatus: "pass" }, "Room3-TO-1": { type: "copper", apitype:"t568b", connectedTo: "1F-RackA-1A-5", wiremapstatus: "cross", performancestatus: "next-fail" }, "Room3-TO-2": { type: "copper", apitype:"t568b", connectedTo: "1F-RackA-1A-6", wiremapstatus: "reversed", performancestatus: "pass" }, "Room4-CAM": { type: "copper", apitype:"t568b", connectedTo: "1F-RackA-1A-12", wiremapstatus: "pass", performancestatus: "mptl-nextfail" }, // OLTS "2F-RackA-1A-1": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-1", fiberstatus: "sm-fail" }, "2F-RackA-1A-2": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-2", fiberstatus: "sm-pass" }, "2F-RackA-1A-3": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-3", fiberstatus: "sm-pass" }, "2F-RackA-1A-4": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-4", fiberstatus: "sm-pass" }, "2F-RackA-1A-5": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-6", fiberstatus: "sm-pass" }, "2F-RackA-1A-6": { type: "fiber", apitype:"olts", connectedTo: "1F-RackA-1B-5", fiberstatus: "sm-pass" }, // OTDR "1F-RackA-1C-1": { type: "fiber", apitype:"otdr", connectedTo: "x", fiberstatus: "sm-pass" }, "1F-RackA-1C-2": { type: "fiber", apitype:"otdr", connectedTo: "x", fiberstatus: "connector-fail-start" }, "1F-RackA-1C-3": { type: "fiber", apitype:"otdr", connectedTo: "x", fiberstatus: "splice-fail" }, "1F-RackA-1C-4": { type: "fiber", apitype:"otdr", connectedTo: "x", fiberstatus: "bend" }, // 车间 "RMA-1": { type: "copper", apitype:"workshop-m12", connectedTo: "PLC-Rack-1A-1", wiremapstatus: "pass-2pair", performancestatus: "workshop-m12-pass-30m" }, "RMA-2": { type: "copper", apitype:"workshop-m12", connectedTo: "PLC-Rack-1A-2", wiremapstatus: "sopen", performancestatus: "workshop-m12-pass-30m" }, "PLC-Rack-1B-1": { type: "copper", apitype:"workshop-2p", connectedTo: "1F-RackA-1A-1", wiremapstatus: "pass-2pair", performancestatus: "workshop-2p-pass-80m" }, "PLC-Rack-1B-2": { type: "copper", apitype:"workshop-2p", connectedTo: "1F-RackA-1A-2", wiremapstatus: "pass-2pair", performancestatus: "workshop-2p-pass-80m" }, }, CopperAnalyzer:{ }, }; // 初始化连接映射数据 let connectionMap; const connectionMapPath = path.join(__dirname, 'connection_maps', 'connectionMap.json'); try { if (fs.existsSync(connectionMapPath)) { const data = fs.readFileSync(connectionMapPath, 'utf8'); connectionMap = JSON.parse(data); //console.log('从connectionMap.json加载连接映射数据'); } else { connectionMap = JSON.parse(JSON.stringify(initialConnectionMap)); //console.log('使用默认初始连接映射数据'); } } catch (err) { console.error('加载连接映射数据时出错:', err); connectionMap = JSON.parse(JSON.stringify(initialConnectionMap)); } // 中间件 app.use(cors()); app.use(express.json()); // 获取连接映射的API app.get('/api/connectionMap', (req, res) => { const scene = req.query.scene; if (!scene) { //console.log('[ERROR] 获取连接映射失败: 缺少scene参数'); return res.status(400).json({ error: '缺少scene参数' }); } if (!connectionMap[scene]) { //console.log(`[ERROR] 获取连接映射失败: 无效的场景 (${scene})`); return res.status(404).json({ error: '无效的场景' }); } // 合并pass场景和当前场景的数据 const mergedConnectionMap = Object.assign({}, connectionMap['pass'], connectionMap[scene]); //console.log(`[GET] 获取连接映射 - 场景: ${scene}`); res.json(mergedConnectionMap); }); // 更新整个连接映射的API app.post('/api/connectionMap', (req, res) => { const scene = Object.keys(req.body)[0]; let newConnectionMap = req.body[scene]; // 验证数据格式 if (!scene || !newConnectionMap || typeof newConnectionMap !== 'object') { //console.log('[ERROR] 更新连接映射失败: 无效的数据格式'); return res.status(400).json({ error: '无效的数据格式' }); } // 禁止修改pass场景 if (scene === 'pass') { //console.log('[ERROR] 更新连接映射失败: 不允许修改pass场景'); return res.status(403).json({ error: '不允许修改pass场景' }); } // 移除与pass场景重复的数据 const passKeys = Object.keys(connectionMap.pass); newConnectionMap = Object.fromEntries( Object.entries(newConnectionMap).filter(([key]) => !passKeys.includes(key)) ); // 更新连接映射 const oldConnectionMap = JSON.stringify(connectionMap[scene]); connectionMap[scene] = newConnectionMap; //console.log(`[UPDATE] 连接映射已更新 - 场景: ${scene}`); //console.log(`[DIFF] 旧数据: ${oldConnectionMap}`); //console.log(`[DIFF] 新数据: ${JSON.stringify(connectionMap[scene])}`); res.json({ success: true, message: '连接映射已更新' }); // 持久化存储连接映射 const fs = require('fs'); const path = require('path'); const dirPath = path.join(__dirname, 'connection_maps'); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); } fs.writeFileSync(path.join(dirPath, `connectionMap.json`), JSON.stringify(connectionMap, null, 2)); //console.log(`[SAVE] 连接映射已持久化存储`); }); // 初始化连接映射的API app.post('/api/initConnectionMap', (req, res) => { connectionMap = JSON.parse(JSON.stringify(initialConnectionMap)); res.json({ success: true, message: '连接映射已恢复默认状态' }); // 持久化存储连接映射 const fs = require('fs'); const path = require('path'); const dirPath = path.join(__dirname, 'connection_maps'); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); } fs.writeFileSync(path.join(dirPath, `connectionMap.json`), JSON.stringify(initialConnectionMap, null, 2)); //console.log(`[SAVE] 连接映射已初始化并`); }); // -----------------**************--------连接映射-----------***************-------------- // 比赛状态存储 let competitionStatus = { UUID: null, isRunning: false, startTime: null, statisticsData: {} }; // 比赛开始API app.post('/api/competition/start', (req, res) => { if (competitionStatus.isRunning) { return res.status(400).json({ error: '比赛已在进行中' }); } competitionStatus = { UUID: require('crypto').randomUUID(), isRunning: true, startTime: new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString(), statisticsData: {} }; // 创建比赛临时数据目录 const tmpDirPath = path.join(__dirname, 'competition_tmp', competitionStatus.UUID); if (!fs.existsSync(path.join(__dirname, 'competition_tmp'))) { fs.mkdirSync(path.join(__dirname, 'competition_tmp')); } if (!fs.existsSync(tmpDirPath)) { fs.mkdirSync(tmpDirPath); } // 启动定时备份 const backupInterval = setInterval(() => { if (!competitionStatus.isRunning) { clearInterval(backupInterval); return; } const timestamp = new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString().replace(/[:.]/g, '-'); const backupData = { connectionMap, competitionStatus }; const dataStr = JSON.stringify(backupData, null, 2); const encodedData = encodeURIComponent(dataStr).split('').reverse().join(''); const finalData = `EST_ENCODED_DATA:${encodedData}`; fs.writeFileSync( path.join(tmpDirPath, `backup_${timestamp}.est`), finalData ); try { const maxAgeMs = 60 * 60 * 1000; const now = Date.now(); const files = fs.readdirSync(tmpDirPath).filter(f => f.endsWith('.est')); for (const f of files) { const fp = path.join(tmpDirPath, f); const stat = fs.statSync(fp); if (now - stat.mtimeMs > maxAgeMs) { fs.unlinkSync(fp); } } const remaining = fs.readdirSync(tmpDirPath) .filter(f => f.endsWith('.est')) .map(f => { const fp = path.join(tmpDirPath, f); const stat = fs.statSync(fp); return { fp, mtimeMs: stat.mtimeMs }; }) .sort((a, b) => a.mtimeMs - b.mtimeMs); if (remaining.length > 60) { const excess = remaining.length - 60; for (let i = 0; i < excess; i++) { fs.unlinkSync(remaining[i].fp); } } } catch (e) {} }, 60000); // 每分钟备份一次 //console.log(`[COMPETITION] 比赛开始 - UUID: ${competitionStatus.UUID}`); res.json({ success: true, UUID: competitionStatus.UUID }); }); // 比赛结束API app.post('/api/competition/end', (req, res) => { if (!competitionStatus.isRunning) { return res.status(400).json({ error: '没有进行中的比赛' }); } // 持久化存储比赛数据 const fs = require('fs'); const path = require('path'); const dateStr = new Date().toISOString().split('T')[0]; const fileName = `${dateStr}_${competitionStatus.UUID}.est`; const dirPath = path.join(__dirname, 'competition_data'); const saveData = { connectionMap, competitionStatus, }; if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); } // 使用编码和简单的字符替换来编码数据 const dataStr = JSON.stringify(saveData, null, 2); const encodedData = encodeURIComponent(dataStr).split('').reverse().join(''); // 添加自定义头部标识,表明这是编码后的文件 const finalData = `EST_ENCODED_DATA:${encodedData}`; fs.writeFileSync( path.join(dirPath, fileName), finalData ); competitionStatus.isRunning = false; //console.log(`[COMPETITION] 比赛结束 - 数据已保存到 ${fileName}`); res.json({ uuid: competitionStatus.UUID, success: true, data: saveData, }); competitionStatus.statisticsData = {}; }); // 比赛状态查询API app.get('/api/competition/status', (req, res) => { const response = { isRunning: competitionStatus.isRunning }; if (competitionStatus.isRunning) { response.UUID = competitionStatus.UUID; response.startTime = competitionStatus.startTime; } res.json(response); }); // 比赛数据上传API app.post('/api/competition/data', (req, res) => { const { UUID, fingerprint, timestamp, data } = req.body; // 验证比赛状态 if (!competitionStatus.isRunning) { return res.status(400).json({ error: '没有进行中的比赛' }); } // 验证UUID if (!UUID || UUID !== competitionStatus.UUID) { return res.status(400).json({ error: '无效的UUID或UUID不匹配当前比赛' }); } // 验证数据完整性 if (!data || !fingerprint) { return res.status(400).json({ error: '缺少必要数据' }); } try { // 使用指纹作为键,存储最新的数据 competitionStatus.statisticsData[fingerprint] = { ...data, lastUpdate: timestamp }; // 比赛数据查询API (根据UUID和fingerprint) app.get('/api/competition/data', (req, res) => { const { UUID, fingerprint } = req.query; // 验证参数 if (!UUID || !fingerprint) { //console.log('[ERROR] 查询比赛数据失败: 缺少UUID或fingerprint参数'); return res.status(400).json({ error: '缺少UUID或fingerprint参数' }); } // 检查比赛状态 if (!competitionStatus.isRunning || competitionStatus.UUID !== UUID) { //console.log('[ERROR] 查询比赛数据失败: 无效的比赛UUID或比赛未进行'); return res.status(404).json({ error: '无效的比赛UUID或比赛未进行' }); } // 获取指定指纹的数据 const data = competitionStatus.statisticsData[fingerprint]; if (!data) { //console.log(`[WARN] 未找到指纹 ${fingerprint} 的比赛数据`); return res.status(404).json({ error: '未找到指定指纹的比赛数据' }); } //console.log(`[GET] 获取比赛数据 - UUID: ${UUID}, 指纹: ${fingerprint}`); res.json({ success: true, data: { projects: data.projects || [] } }); }); res.json({ success: true }); } catch (error) { console.error('保存比赛数据失败:', error); res.status(500).json({ error: '服务器内部错误' }); } }); // 比赛数据查询API app.get('/api/competition/data', (req, res) => { res.json(competitionStatus.statisticsData); }); // -----------------**************--------教学数据-----------***************-------------- // 教学数据查询API const teachingDir = path.join(__dirname, 'teaching_data'); app.post('/api/teaching/data', (req, res) => { const { fingerprint, scenario, timestamp, data } = req.body; if (!fingerprint || !scenario || !data) { return res.status(400).json({ error: '缺少必要参数' }); } try { if (!fs.existsSync(teachingDir)) { fs.mkdirSync(teachingDir); } const filePath = path.join(teachingDir, `${scenario}.json`); let store = {}; if (fs.existsSync(filePath)) { try { const content = fs.readFileSync(filePath, 'utf8'); store = JSON.parse(content || '{}'); } catch { store = {}; } } store[fingerprint] = { ...data, lastUpdate: timestamp }; fs.writeFileSync(filePath, JSON.stringify(store, null, 2)); res.json({ success: true }); } catch (error) { console.error('保存教学数据失败:', error); res.status(500).json({ error: '服务器内部错误' }); } }); app.get('/api/teaching/data', (req, res) => { const { scenario, fingerprint, org } = req.query; if (!scenario) { return res.status(400).json({ error: '缺少scenario参数' }); } try { if (!fs.existsSync(teachingDir)) { if (fingerprint) { return res.status(404).json({ error: '未找到指定指纹数据' }); } if (!org) { return res.status(400).json({ error: '缺少org参数' }); } return res.json({}); } const filePath = path.join(teachingDir, `${scenario}.json`); if (!fs.existsSync(filePath)) { if (fingerprint) { return res.status(404).json({ error: '未找到指定指纹数据' }); } if (!org) { return res.status(400).json({ error: '缺少org参数' }); } return res.json({}); } const content = fs.readFileSync(filePath, 'utf8'); const store = JSON.parse(content || '{}'); if (fingerprint) { const entry = store[fingerprint] || null; if (!entry) { return res.status(404).json({ error: '未找到指定指纹数据' }); } if (org && String(entry.org || '').toLowerCase() !== String(org).toLowerCase()) { return res.status(404).json({ error: '未找到指定指纹数据' }); } return res.json(entry); } if (!org) { return res.status(400).json({ error: '缺少org参数' }); } const filtered = Object.fromEntries( Object.entries(store).filter(([_, v]) => String((v && v.org) || '').toLowerCase() === String(org).toLowerCase()) ); return res.json(filtered); } catch (error) { console.error('读取教学数据失败:', error); res.status(500).json({ error: '服务器内部错误' }); } }); // -----------------**************--------------------------***************-------------- // 启动服务器 if (require.main === module) { app.listen(PORT, () => { console.log(`[SERVER] 服务器运行在 http://localhost:${PORT}`); console.log('\x1b[31m%s\x1b[0m', '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); console.log('\x1b[31m\x1b[1m%s\x1b[0m', '!! 比赛期间请勿关闭此窗口 !!'); console.log('\x1b[31m\x1b[1m%s\x1b[0m', '!! 比赛期间避免键盘操作 如Ctrl+C等 !!'); console.log('\x1b[31m\x1b[1m%s\x1b[0m', '!! 关闭后比赛数据将会丢失 !!'); console.log('\x1b[31m%s\x1b[0m', '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); }); } module.exports = app;