Files
EST-DSX/server/app.js
2025-09-16 16:39:48 +08:00

693 lines
18 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3007;
// 初始连接映射数据
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",
connectedTo: "Cabinet-A1",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device1-2": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-A2",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device2-1": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-A3",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device2-2": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-A4",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device3-1": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-R1",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device3-2": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-R2",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device4-1": {
type: "copper",
apitype:"workshop",
connectedTo: "Cabinet-R3",
wiremapstatus: "pass",
performancestatus: "pass"
},
"Device4-2": {
type: "copper",
apitype:"workshop",
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: "pass",
performancestatus: "lengthfail"
},
"Room1-TO-2": {
type: "copper",
apitype:"t568b",
connectedTo: "1F-RackA-1A-2",
wiremapstatus: "pass",
performancestatus: "return-loss-fail"
},
"Room3-TO-1": {
type: "copper",
apitype:"t568b",
connectedTo: "1F-RackA-1A-5",
wiremapstatus: "pass",
performancestatus: "next-fail"
},
"Room3-TO-2": {
type: "copper",
apitype:"t568b",
connectedTo: "1F-RackA-1A-6",
wiremapstatus: "open2",
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-pass"
},
"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-fail"
},
"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-lengthfail"
},
},
};
// 初始化连接映射数据
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
);
}, 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);
});
// 启动服务器
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', '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
});