From fc46cee50fa4bc0962abe74460d363898addf3c3 Mon Sep 17 00:00:00 2001 From: jinchao <383321154@qq.com> Date: Thu, 12 Jun 2025 14:50:27 +0800 Subject: [PATCH] =?UTF-8?q?V0.21.15.250612=5Falpha:=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E9=A1=B5=E9=9D=A2=E6=B7=BB=E5=8A=A0CSV?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Release/.gitignore | 3 + Release/database/XNSim.db | Bin 1224704 -> 1224704 bytes XNMonitorServer/CMakeLists.txt | 2 + XNMonitorServer/CSVDataInjectThread.cpp | 148 ++++++++++++ XNMonitorServer/CSVDataInjectThread.h | 68 ++++++ XNMonitorServer/XNMonitorInterface.cpp | 78 ++++++- XNMonitorServer/XNMonitorInterface.h | 17 +- XNSimHtml/.gitignore | 5 + XNSimHtml/assets/icons/png/inject.png | Bin 0 -> 4548 bytes XNSimHtml/assets/icons/png/inject_b.png | Bin 0 -> 5071 bytes XNSimHtml/components/data-monitor.js | 291 +++++++++++------------- XNSimHtml/routes/DataMonitor.js | 86 +++++++ XNSimHtml/routes/filesystem.js | 210 ++++++++++++++++- XNSimHtml/utils/file-utils.js | 21 +- XNSimHtml/utils/xnCoreService.js | 51 ++++- 15 files changed, 793 insertions(+), 187 deletions(-) create mode 100644 Release/.gitignore create mode 100644 XNMonitorServer/CSVDataInjectThread.cpp create mode 100644 XNMonitorServer/CSVDataInjectThread.h create mode 100644 XNSimHtml/.gitignore create mode 100644 XNSimHtml/assets/icons/png/inject.png create mode 100644 XNSimHtml/assets/icons/png/inject_b.png diff --git a/Release/.gitignore b/Release/.gitignore new file mode 100644 index 0000000..b3927b0 --- /dev/null +++ b/Release/.gitignore @@ -0,0 +1,3 @@ +log/* +testData/* +Packages/* \ No newline at end of file diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 4f0a6ea29accbb439866b09e811040ef24608260..67fceec3842bd7b89667da772780acac2ce25c94 100644 GIT binary patch delta 272 zcmZp8;MMTJYl1Z6#ECM_j1wCZS`!$zCNM2|&uqrKdpi3ECPyaT-P=7rFctFha5GCY z8t54r>KU4vwIBb>1jNih%mT!$K+Fcj>_E)1{rF!_TOlrHJ`M)HyL?rA9Gevte(}~9 zm~lueipxfOibD-CGB7gLH89gPG)l}V$VfCau`;o=GB$ZOb;GmXb}o?3f7&(wMf2Xol-$g`L{qSh&dwkk g+uP;1fS4PId4QM~i1~n+ABY8jSa5s0oKWRN01|g{EC2ui delta 134 zcmZp8;MMTJYl1Z6go!fFj1w9YS`!$zCNM2|&uq$jd^-CFCdchwAD9YxCkF6M-~E?~ ztNr3%CLm@8Viq7~1!6WJW(Q)9?HB)Y8VhkS@l`SK-Q}y=tf-L3*Ip&Z1;pGy%mc){ UKnxP&2Vwyr7TjJXC)73(02RwMwEzGB diff --git a/XNMonitorServer/CMakeLists.txt b/XNMonitorServer/CMakeLists.txt index 68d2658..11dd228 100644 --- a/XNMonitorServer/CMakeLists.txt +++ b/XNMonitorServer/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(XNMonitorServer SHARED DataMonitorFactory.cpp DataInjectThread.h DataInjectThread.cpp + CSVDataInjectThread.h + CSVDataInjectThread.cpp ) # 添加头文件搜索路径 diff --git a/XNMonitorServer/CSVDataInjectThread.cpp b/XNMonitorServer/CSVDataInjectThread.cpp new file mode 100644 index 0000000..179c4ec --- /dev/null +++ b/XNMonitorServer/CSVDataInjectThread.cpp @@ -0,0 +1,148 @@ +#include "CSVDataInjectThread.h" +#include "DataMonitorFactory.h" + +CSVDataInjectThread::CSVDataInjectThread(std::string csvFilePath) + : m_csvFilePath(csvFilePath), m_running(false), m_nextExecuteTime(0) +{ +} + +CSVDataInjectThread::~CSVDataInjectThread() +{ + stop(); +} + +bool CSVDataInjectThread::Initialize(std::vector structNames) +{ + m_csvFile.open(m_csvFilePath); + if (!m_csvFile.is_open()) { + return false; + } + + std::string headerLine; + if (!std::getline(m_csvFile, headerLine)) { + return false; + } + + std::vector interfaceNames; + std::stringstream ss(headerLine); + std::string field; + std::getline(ss, field, ','); + while (std::getline(ss, field, ',')) { + interfaceNames.push_back(field); + } + + // 将结构体和接口名称一一对应 + if (structNames.size() != interfaceNames.size()) { + return false; + } + + for (int i = 0; i < structNames.size(); i++) { + m_structInterfaceMap[structNames[i]].push_back(interfaceNames[i]); + } + + for (const auto &[structName, interfaceNames] : m_structInterfaceMap) { + auto dataMonitor = DataMonitorFactory::GetInstance(structName); + if (dataMonitor == nullptr) { + return false; + } + if (dataMonitor->isInitialized()) { + m_alreadyStartedMonitors[structName] = dataMonitor; + } else { + m_notStartedMonitors[structName] = dataMonitor; + } + } + return true; +} + +void CSVDataInjectThread::start() +{ + std::lock_guard lock(m_mutex); + if (!m_running) { + m_running = true; + m_thread = std::thread(&CSVDataInjectThread::threadFunc, this); + } +} + +void CSVDataInjectThread::stop() +{ + { + std::lock_guard lock(m_mutex); + if (m_running) { + m_running = false; + m_cv.notify_all(); + } + } + + if (m_thread.joinable()) { + m_thread.join(); + } + + // 关闭文件 + if (m_csvFile.is_open()) { + m_csvFile.close(); + } + + // 释放未启动的监控器 + for (const auto &[structName, dataMonitor] : m_notStartedMonitors) { + DataMonitorFactory::ReleaseInstance(structName); + } + m_notStartedMonitors.clear(); + m_alreadyStartedMonitors.clear(); +} + +void CSVDataInjectThread::updateData() +{ + // 读取下一行数据 + std::string line; + if (!std::getline(m_csvFile, line)) { + // 文件读取完毕,停止线程 + m_running = false; + return; + } + + // 解析数据 + std::stringstream ss(line); + std::string field; + std::getline(ss, field, ','); + double timeStamp = std::stod(field); + m_nextExecuteTime = static_cast(timeStamp * 1000); // 转换为毫秒 + + // 解析每个结构体的数据 + for (const auto &[structName, interfaceNames] : m_structInterfaceMap) { + std::unordered_map dataMap; + for (const auto &interfaceName : interfaceNames) { + std::getline(ss, field, ','); + dataMap[interfaceName] = field; + } + m_data[structName] = dataMap; + } +} + +void CSVDataInjectThread::threadFunc() +{ + // 读取第一行数据 + updateData(); + + while (m_running) { + int64_t nextTime = m_nextExecuteTime; + + // 等待直到到达执行时间 + auto now = std::chrono::system_clock::now(); + auto targetTime = std::chrono::system_clock::from_time_t(nextTime / 1000) + + std::chrono::milliseconds(nextTime % 1000); + + if (now < targetTime) { + std::this_thread::sleep_until(targetTime); + } + + // 执行数据注入 + for (const auto &[structName, dataMonitor] : m_alreadyStartedMonitors) { + if (dataMonitor && m_data.find(structName) != m_data.end()) { + dataMonitor->setDataByString(m_data[structName]); + } + } + + // 读取下一行数据 + updateData(); + } +} \ No newline at end of file diff --git a/XNMonitorServer/CSVDataInjectThread.h b/XNMonitorServer/CSVDataInjectThread.h new file mode 100644 index 0000000..4158683 --- /dev/null +++ b/XNMonitorServer/CSVDataInjectThread.h @@ -0,0 +1,68 @@ +#pragma once + +#include "DataMonitor.h" +#include +#include +#include +#include +#include + +/** + * @brief 数据注入线程类,用于持续向数据监控器注入数据 + */ +class CSVDataInjectThread +{ +public: + /** + * @brief 构造函数 + * @param csvFilePath CSV文件路径 + */ + CSVDataInjectThread(std::string csvFilePath); + + /** + * @brief 析构函数 + */ + ~CSVDataInjectThread(); + + bool Initialize(std::vector structNames); + + /** + * @brief 启动数据注入线程 + */ + void start(); + + /** + * @brief 停止数据注入线程 + */ + void stop(); + + /** + * @brief 从CSV文件读取下一行数据并更新执行时间 + * 如果文件读取完毕,将停止线程 + */ + void updateData(); + +private: + /** + * @brief 线程执行函数 + */ + void threadFunc(); + +private: + std::string m_csvFilePath; + std::ifstream m_csvFile; + std::unordered_map> m_structInterfaceMap; + std::thread m_thread; ///< 数据注入线程 + std::atomic m_running; ///< 线程运行标志 + std::mutex m_mutex; ///< 互斥锁 + std::condition_variable m_cv; ///< 条件变量 + std::unordered_map + m_alreadyStartedMonitors; ///< 已经启动的数据监控器 + std::unordered_map + m_notStartedMonitors; ///< 未启动的数据监控器 + std::unordered_map> + m_data; ///< 要注入的数据 + std::atomic m_nextExecuteTime; ///< 下一次执行的时间点 +}; + +using CSVDataInjectThreadPtr = std::shared_ptr; \ No newline at end of file diff --git a/XNMonitorServer/XNMonitorInterface.cpp b/XNMonitorServer/XNMonitorInterface.cpp index 8bf231d..1f9706b 100644 --- a/XNMonitorServer/XNMonitorInterface.cpp +++ b/XNMonitorServer/XNMonitorInterface.cpp @@ -8,6 +8,7 @@ #include "SystemControl.h" #include "DataMonitorFactory.h" #include "DataInjectThread.h" +#include "CSVDataInjectThread.h" // 全局变量 static bool g_initialized = false; @@ -19,6 +20,8 @@ bool g_modelInfoMonitorStarted = false; SystemControl *systemControl = nullptr; bool g_systemControlStarted = false; +CSVDataInjectThreadPtr g_csvDataInjectThread; + // 初始化函数实现 int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize) { @@ -587,8 +590,79 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName, const int csvFilePathLen, char *infoMsg, int infoMsgSize) { - // TODO: 从csv文件中注入数据接口 - return -1; + std::vector structNames; + std::string structNameStr(structName, structNameLen); + try { + nlohmann::json structNamesJson = nlohmann::json::parse(structNameStr); + if (!structNamesJson.is_array()) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "Invalid struct name format - expected JSON array", + infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + for (const auto &structNameJson : structNamesJson) { + structNames.push_back(structNameJson.get()); + } + } catch (const nlohmann::json::parse_error &e) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "Invalid JSON format", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + std::string csvFilePathStr(csvFilePath, csvFilePathLen); + if (csvFilePathStr.empty()) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "CSV 文件路径为空", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + if (!std::filesystem::exists(csvFilePathStr)) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "CSV 文件不存在", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + if (g_csvDataInjectThread == nullptr) { + g_csvDataInjectThread = std::make_shared(csvFilePathStr); + bool ret = g_csvDataInjectThread->Initialize(structNames); + if (ret) { + g_csvDataInjectThread->start(); + } else { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "CSV 注入线程初始化失败", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + } else { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "CSV 注入线程已在运行", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + return 0; +} + +int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize) +{ + if (g_csvDataInjectThread == nullptr) { + if (infoMsg && infoMsgSize > 0) { + strncpy(infoMsg, "CSV 注入线程已不存在", infoMsgSize - 1); + infoMsg[infoMsgSize - 1] = '\0'; + } + return -1; + } + + g_csvDataInjectThread->stop(); + g_csvDataInjectThread.reset(); // 释放智能指针 + return 0; } int XNMONITORSERVER_EXPORT XN_StartCollectData(const char *structName, const int structNameLen, diff --git a/XNMonitorServer/XNMonitorInterface.h b/XNMonitorServer/XNMonitorInterface.h index f72faaf..4ececd1 100644 --- a/XNMonitorServer/XNMonitorInterface.h +++ b/XNMonitorServer/XNMonitorInterface.h @@ -210,14 +210,23 @@ extern "C" * @param structNameLen 结构体名称长度 * @param csvFilePath csv文件路径 * @param csvFilePathLen csv文件路径长度 - * @param injectTimes 注入次数 * @param infoMsg 错误信息 * @param infoMsgSize 错误信息大小 * @return 0: 成功, -1: 失败 */ - int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv( - const char *structName, const int structNameLen, const char *csvFilePath, - const int csvFilePathLen, int injectTimes, char *infoMsg, int infoMsgSize); + int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName, + const int structNameLen, + const char *csvFilePath, + const int csvFilePathLen, + char *infoMsg, int infoMsgSize); + + /** + * @brief 停止csv数据注入 + * @param infoMsg 错误信息 + * @param infoMsgSize 错误信息大小 + * @return 0: 成功, -1: 失败 + */ + int XNMONITORSERVER_EXPORT XN_StopCsvDataInject(char *infoMsg, int infoMsgSize); //******************** csv数据采集 ********************* diff --git a/XNSimHtml/.gitignore b/XNSimHtml/.gitignore new file mode 100644 index 0000000..0127500 --- /dev/null +++ b/XNSimHtml/.gitignore @@ -0,0 +1,5 @@ +log/* +logs/* +upload/* +uploads/* +node_modules/* \ No newline at end of file diff --git a/XNSimHtml/assets/icons/png/inject.png b/XNSimHtml/assets/icons/png/inject.png new file mode 100644 index 0000000000000000000000000000000000000000..ed282795b4f26383b6e2cc6b5e2ef0fc018644bf GIT binary patch literal 4548 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 Zf=A1A6*ROM?vG^v$k5^(!@%{<{{YD(MXdk; literal 0 HcmV?d00001 diff --git a/XNSimHtml/assets/icons/png/inject_b.png b/XNSimHtml/assets/icons/png/inject_b.png new file mode 100644 index 0000000000000000000000000000000000000000..aa4f40eda4f0fd992fd8ac6e08ff4015a54645c7 GIT binary patch literal 5071 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
- + -
- -
@@ -1791,58 +1770,17 @@ class DataMonitor extends HTMLElement { }); // 添加CSV文件注入相关的事件监听 - const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton'); - const modal = this.shadowRoot.getElementById('csvModal'); - const modalClose = this.shadowRoot.getElementById('modalClose'); - const modalCancel = this.shadowRoot.getElementById('modalCancel'); - const modalConfirm = this.shadowRoot.getElementById('modalConfirm'); + const csvUploadButton = this.shadowRoot.getElementById('csvUploadButton'); const fileInput = this.shadowRoot.getElementById('csvFileInput'); - const fileInputTrigger = this.shadowRoot.getElementById('fileInputTrigger'); - if (csvInjectButton) { - csvInjectButton.addEventListener('click', this.handleCsvInject); - } - - if (modalClose) { - modalClose.addEventListener('click', this.handleModalClose); - } - - if (modalCancel) { - modalCancel.addEventListener('click', this.handleModalCancel); - } - - if (modalConfirm) { - modalConfirm.addEventListener('click', this.handleModalConfirm); + if (csvUploadButton && fileInput) { + csvUploadButton.addEventListener('click', () => { + fileInput.click(); + }); } if (fileInput) { - fileInput.addEventListener('change', this.handleFileInput); - } - - if (fileInputTrigger) { - fileInputTrigger.addEventListener('dragover', (e) => { - e.preventDefault(); - e.stopPropagation(); - fileInputTrigger.style.borderColor = '#1890ff'; - }); - - fileInputTrigger.addEventListener('dragleave', (e) => { - e.preventDefault(); - e.stopPropagation(); - fileInputTrigger.style.borderColor = '#d9d9d9'; - }); - - fileInputTrigger.addEventListener('drop', (e) => { - e.preventDefault(); - e.stopPropagation(); - fileInputTrigger.style.borderColor = '#d9d9d9'; - - const files = e.dataTransfer.files; - if (files.length > 0) { - fileInput.files = files; - this.handleFileInput({ target: fileInput }); - } - }); + fileInput.addEventListener('change', this.handleCsvFileSelect); } } @@ -1952,80 +1890,115 @@ class DataMonitor extends HTMLElement { } /** - * @description 处理CSV文件注入按钮点击事件 + * @description 处理CSV文件选择事件 + * @param {Event} event - 文件选择事件 */ - handleCsvInject() { - const modal = this.shadowRoot.getElementById('csvModal'); - if (modal) { - modal.classList.add('active'); - } - } - - /** - * @description 处理模态框关闭事件 - */ - handleModalClose() { - const modal = this.shadowRoot.getElementById('csvModal'); - if (modal) { - modal.classList.remove('active'); - } - } - - /** - * @description 处理模态框取消按钮点击事件 - */ - handleModalCancel() { - this.handleModalClose(); - } - - /** - * @description 处理模态框确认按钮点击事件 - */ - async handleModalConfirm() { - const fileInput = this.shadowRoot.getElementById('csvFileInput'); - const frequencyInput = this.shadowRoot.getElementById('injectFrequency'); - const durationInput = this.shadowRoot.getElementById('injectDuration'); - - if (!fileInput.files.length) { - alert('请选择CSV文件'); - return; - } - - const file = fileInput.files[0]; - const frequency = parseInt(frequencyInput.value); - const duration = parseInt(durationInput.value); + async handleCsvFileSelect(event) { + const file = event.target.files[0]; + if (!file) return; if (!this.validateCsvFile(file)) { return; } try { - // TODO: 实现CSV文件注入逻辑 - console.log('CSV文件注入:', { - file: file.name, - frequency, - duration + // 创建 FormData 对象 + const formData = new FormData(); + formData.append('file', file); + + // 上传文件 + const response = await fetch('/api/filesystem/upload', { + method: 'POST', + body: formData }); - this.handleModalClose(); - } catch (error) { - console.error('CSV文件注入失败:', error); - alert(`CSV文件注入失败: ${error.message}`); - } - } + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.message || '文件上传失败'); + } - /** - * @description 处理文件输入事件 - * @param {Event} event - 文件输入事件 - */ - handleFileInput(event) { - const file = event.target.files[0]; - const fileName = this.shadowRoot.getElementById('fileName'); - - if (file) { - fileName.textContent = file.name; - } else { - fileName.textContent = ''; + if (!result.success) { + throw new Error(result.message || '文件上传失败'); + } + + console.log('CSV文件上传成功:', result.file.name); + + // 验证CSV文件头部 + const validateResponse = await fetch(`/api/filesystem/validate-csv-headers?filename=${encodeURIComponent(result.file.name)}`); + if (!validateResponse.ok) { + throw new Error('验证CSV文件头部失败'); + } + + const validateResult = await validateResponse.json(); + if (!validateResult.success) { + throw new Error(validateResult.message || '验证CSV文件头部失败'); + } + + // 检查每个头部是否在接口表中 + const missingInterfaces = []; + const invalidInterfaces = []; + + // 检查第一个接口(时间)是否在接口表中 + const firstHeader = validateResult.headers[0]; + const firstHeaderExists = this.interfaces.some(interfaceItem => + interfaceItem.InterfaceName === firstHeader + ); + if (firstHeaderExists) { + invalidInterfaces.push(`第一个接口 "${firstHeader}" 不应该在接口表中`); + } + + // 检查其他接口是否在接口表中 + for (let i = 1; i < validateResult.headers.length; i++) { + const header = validateResult.headers[i]; + const exists = this.interfaces.some(interfaceItem => + interfaceItem.InterfaceName === header + ); + if (!exists) { + missingInterfaces.push(header); + } + } + + // 合并错误信息 + const errorMessages = []; + if (invalidInterfaces.length > 0) { + errorMessages.push(invalidInterfaces.join('\n')); + } + if (missingInterfaces.length > 0) { + errorMessages.push(`以下接口在系统中不存在:\n${missingInterfaces.join('\n')}`); + } + + if (errorMessages.length > 0) { + // 删除已上传的文件 + try { + const deleteResponse = await fetch(`/api/filesystem/upload/${encodeURIComponent(result.file.name)}`, { + method: 'DELETE' + }); + if (!deleteResponse.ok) { + console.error('删除验证失败的文件时出错'); + } + } catch (deleteError) { + console.error('删除验证失败的文件时出错:', deleteError); + } + throw new Error(errorMessages.join('\n\n')); + } + + console.log('CSV文件头部验证通过'); + + // 更新文件名显示 + const csvFileName = this.shadowRoot.getElementById('csvFileName'); + if (csvFileName) { + csvFileName.textContent = result.file.name; + } + + // 显示数据注入按钮 + const csvInjectButton = this.shadowRoot.getElementById('csvInjectButton'); + if (csvInjectButton) { + csvInjectButton.style.display = 'flex'; + } + } catch (error) { + console.error('CSV文件处理失败:', error); + alert(`CSV文件处理失败: ${error.message}`); } } @@ -2035,7 +2008,19 @@ class DataMonitor extends HTMLElement { * @returns {boolean} 是否验证通过 */ validateCsvFile(file) { - // TODO: 实现CSV文件验证逻辑 + // 检查文件类型 + if (!file.name.toLowerCase().endsWith('.csv')) { + alert('请选择CSV文件'); + return false; + } + + // 检查文件大小(限制为10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + alert('文件大小不能超过10MB'); + return false; + } + return true; } @@ -2052,6 +2037,6 @@ class DataMonitor extends HTMLElement { this.stopDataUpdateTimer(); } } - customElements.define('data-monitor', DataMonitor); + diff --git a/XNSimHtml/routes/DataMonitor.js b/XNSimHtml/routes/DataMonitor.js index b663256..6621d0e 100644 --- a/XNSimHtml/routes/DataMonitor.js +++ b/XNSimHtml/routes/DataMonitor.js @@ -281,4 +281,90 @@ router.post('/stop-continuous', async (req, res) => { } }); +/** + * @brief 停止所有持续注入数据 + * @route POST /api/data-monitor/stop-all-continuous + * @returns {Object} 返回停止结果 + */ +router.post('/stop-all-continuous', async (req, res) => { + try { + // 获取所有正在注入的结构体 + const injectingStructs = Array.from(continuousInjectStatus.keys()); + + if (injectingStructs.length === 0) { + return res.json({ + success: true, + message: '当前没有正在进行的注入' + }); + } + + // 停止所有结构体的注入 + const results = []; + for (const structName of injectingStructs) { + const result = systemMonitor.stopInjectContinuous(structName); + if (result.includes('失败')) { + results.push({ structName, success: false, message: result }); + } else { + results.push({ structName, success: true, message: '停止成功' }); + } + continuousInjectStatus.delete(structName); + } + + // 检查是否所有结构体都成功停止 + const allSuccess = results.every(r => r.success); + + res.json({ + success: allSuccess, + message: allSuccess ? '所有持续注入已停止' : '部分持续注入停止失败', + details: results + }); + } catch (error) { + res.status(500).json({ success: false, message: `停止所有持续注入数据失败: ${error.message}` }); + } +}); + +/** + * @brief 从CSV文件注入数据 + * @route POST /api/data-monitor/inject-csv + * @param {string} structName - 结构体名称 + * @param {string} csvFilePath - CSV文件路径 + * @returns {Object} 返回注入结果 + */ +router.post('/inject-csv', async (req, res) => { + try { + const { structName, csvFilePath} = req.body; + if (!structName || !csvFilePath) { + return res.status(400).json({ + success: false, + message: '结构体名称、CSV文件路径和注入次数不能为空' + }); + } + + const result = systemMonitor.injectDataInterfaceFromCsv(structName, csvFilePath); + if (result.includes('失败')) { + return res.status(500).json({ success: false, message: result }); + } + res.json({ success: true, message: result }); + } catch (error) { + res.status(500).json({ success: false, message: `从CSV文件注入数据失败: ${error.message}` }); + } +}); + +/** + * @brief 停止CSV数据注入 + * @route POST /api/data-monitor/stop-csv-inject + * @returns {Object} 返回停止结果 + */ +router.post('/stop-csv-inject', async (req, res) => { + try { + const result = systemMonitor.stopCsvDataInject(); + if (result.includes('失败')) { + return res.status(500).json({ success: false, message: result }); + } + res.json({ success: true, message: result }); + } catch (error) { + res.status(500).json({ success: false, message: `停止CSV数据注入失败: ${error.message}` }); + } +}); + module.exports = router; diff --git a/XNSimHtml/routes/filesystem.js b/XNSimHtml/routes/filesystem.js index c82da92..e225014 100644 --- a/XNSimHtml/routes/filesystem.js +++ b/XNSimHtml/routes/filesystem.js @@ -1,8 +1,47 @@ const express = require('express'); const router = express.Router(); -const fs = require('fs').promises; +const fsPromises = require('fs').promises; +const fs = require('fs'); const path = require('path'); -const { getActualLogPath } = require('../utils/file-utils'); +const multer = require('multer'); +const { getActualLogPath, getUploadPath, saveUploadedFile, isAllowedFileType } = require('../utils/file-utils'); + +// 获取上传目录路径 +const uploadPath = getUploadPath(); + +// 配置 multer 存储 +const storage = multer.diskStorage({ + destination: async function (req, file, cb) { + try { + const uploadPath = await getUploadPath(); + cb(null, uploadPath); + } catch (error) { + cb(error); + } + }, + filename: function (req, file, cb) { + cb(null, file.originalname); + } +}); + +// 文件过滤器 +const fileFilter = (req, file, cb) => { + // 允许的文件类型 + const allowedTypes = ['.csv']; + if (isAllowedFileType(file.originalname, allowedTypes)) { + cb(null, true); + } else { + cb(new Error('不支持的文件类型')); + } +}; + +const upload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 10 * 1024 * 1024 // 限制文件大小为10MB + } +}); // 读取目录内容 router.get('/readdir', async (req, res) => { @@ -17,7 +56,7 @@ router.get('/readdir', async (req, res) => { // 检查目录是否存在 try { - const stats = await fs.stat(logDirPath); + const stats = await fsPromises.stat(logDirPath); if (!stats.isDirectory()) { return res.status(400).json({ error: '指定的路径不是目录' }); } @@ -25,7 +64,7 @@ router.get('/readdir', async (req, res) => { // 如果目录不存在,尝试创建它 if (statError.code === 'ENOENT') { try { - await fs.mkdir(logDirPath, { recursive: true }); + await fsPromises.mkdir(logDirPath, { recursive: true }); } catch (mkdirError) { console.error('创建日志目录失败:', mkdirError); return res.status(500).json({ error: '创建日志目录失败' }); @@ -36,7 +75,7 @@ router.get('/readdir', async (req, res) => { } // 读取目录内容 - const files = await fs.readdir(logDirPath); + const files = await fsPromises.readdir(logDirPath); // 返回文件列表 res.json({ files }); @@ -78,7 +117,7 @@ router.get('/stat', async (req, res) => { } // 获取文件状态 - const stats = await fs.stat(filePath); + const stats = await fsPromises.stat(filePath); // 返回文件信息 res.json({ @@ -133,7 +172,7 @@ router.get('/readFile', async (req, res) => { } // 获取文件状态 - const stats = await fs.stat(filePath); + const stats = await fsPromises.stat(filePath); // 检查文件大小,限制读取过大的文件 const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB @@ -142,7 +181,7 @@ router.get('/readFile', async (req, res) => { } // 读取文件内容 - const content = await fs.readFile(filePath, 'utf-8'); + const content = await fsPromises.readFile(filePath, 'utf-8'); // 设置响应头 res.setHeader('Content-Type', 'text/plain; charset=utf-8'); @@ -164,4 +203,159 @@ router.get('/readFile', async (req, res) => { } }); +// 获取上传文件列表 +router.get('/upload-files', async (req, res) => { + try { + const uploadPath = await getUploadPath(); + + // 读取目录内容 + const files = await fsPromises.readdir(uploadPath); + + // 获取每个文件的详细信息 + const fileDetails = await Promise.all(files.map(async (fileName) => { + const filePath = path.join(uploadPath, fileName); + const stats = await fsPromises.stat(filePath); + return { + name: fileName, + size: stats.size, + created: stats.birthtime, + modified: stats.mtime, + path: filePath + }; + })); + + res.json({ files: fileDetails }); + } catch (error) { + console.error('获取上传文件列表失败:', error); + res.status(500).json({ error: '获取上传文件列表失败', message: error.message }); + } +}); + +// 上传文件 +router.post('/upload', upload.single('file'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ error: '未提供文件' }); + } + + // 获取上传目录路径 + const uploadPath = await getUploadPath(); + + // 确保上传目录存在 + try { + await fsPromises.access(uploadPath); + } catch (error) { + if (error.code === 'ENOENT') { + // 如果目录不存在,创建它 + await fsPromises.mkdir(uploadPath, { recursive: true }); + } else { + throw error; + } + } + + // 保存文件并获取最终路径 + const finalPath = await saveUploadedFile(req.file); + + // 获取文件状态 + const stats = await fsPromises.stat(finalPath); + + res.json({ + success: true, + file: { + name: path.basename(finalPath), + size: stats.size, + path: finalPath, + created: stats.birthtime, + modified: stats.mtime + } + }); + } catch (error) { + console.error('文件上传失败:', error); + res.status(500).json({ error: '文件上传失败', message: error.message }); + } +}); + +// 删除上传的文件 +router.delete('/upload/:filename', async (req, res) => { + try { + const { filename } = req.params; + const uploadPath = await getUploadPath(); + const filePath = path.join(uploadPath, filename); + + // 安全检查:确保文件在上传目录内 + if (!filePath.startsWith(uploadPath)) { + return res.status(403).json({ error: '无权删除该文件' }); + } + + // 删除文件 + await fsPromises.unlink(filePath); + + res.json({ success: true, message: '文件删除成功' }); + } catch (error) { + console.error('删除文件失败:', error); + + if (error.code === 'ENOENT') { + return res.status(404).json({ error: '文件不存在' }); + } + + res.status(500).json({ error: '删除文件失败', message: error.message }); + } +}); + +// 验证CSV文件头部 +router.get('/validate-csv-headers', async (req, res) => { + try { + const { filename } = req.query; + if (!filename) { + return res.status(400).json({ + success: false, + message: '未提供文件名' + }); + } + + // 获取上传目录路径 + const uploadPath = await getUploadPath(); + const filePath = path.join(uploadPath, filename); + + // 检查文件是否存在 + try { + await fsPromises.access(filePath); + } catch (error) { + return res.status(404).json({ + success: false, + message: '文件不存在' + }); + } + + // 只读取文件的第一行 + const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); + let firstLine = ''; + + try { + for await (const chunk of fileStream) { + const lines = chunk.split('\n'); + firstLine = lines[0]; + break; + } + } finally { + // 确保关闭文件流 + fileStream.destroy(); + } + + // 解析CSV头部 + const headers = firstLine.split(',').map(header => header.trim()); + + res.json({ + success: true, + headers: headers + }); + } catch (error) { + console.error('验证CSV文件头部失败:', error); + res.status(500).json({ + success: false, + message: '验证CSV文件头部失败: ' + error.message + }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/XNSimHtml/utils/file-utils.js b/XNSimHtml/utils/file-utils.js index 2f97c18..877b4b8 100644 --- a/XNSimHtml/utils/file-utils.js +++ b/XNSimHtml/utils/file-utils.js @@ -135,24 +135,9 @@ async function getUploadPath() { // 保存上传的文件 async function saveUploadedFile(file) { - try { - const uploadPath = await getUploadPath(); - const filePath = path.join(uploadPath, file.originalname); - - // 如果文件已存在,添加时间戳 - if (fs.existsSync(filePath)) { - const timestamp = new Date().getTime(); - const ext = path.extname(file.originalname); - const name = path.basename(file.originalname, ext); - const newFileName = `${name}_${timestamp}${ext}`; - return path.join(uploadPath, newFileName); - } - - return filePath; - } catch (error) { - console.error('处理上传文件路径失败:', error); - throw error; - } + const uploadPath = await getUploadPath(); + const filePath = path.join(uploadPath, file.originalname); + return filePath; } // 检查文件类型是否允许 diff --git a/XNSimHtml/utils/xnCoreService.js b/XNSimHtml/utils/xnCoreService.js index f793b6e..8567c71 100644 --- a/XNSimHtml/utils/xnCoreService.js +++ b/XNSimHtml/utils/xnCoreService.js @@ -59,7 +59,9 @@ try { 'XN_GetDataMonitorInfo': ['int', [StringType, 'int', StringType, 'int', StringType, 'int', StringType, 'int']], 'XN_InjectDataInterface': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']], 'XN_StartInjectContinuous': ['int', [StringType, 'int', StringType, 'int', 'double', StringType, 'int']], - 'XN_StopInjectContinuous': ['int', [StringType, 'int', StringType, 'int']] + 'XN_StopInjectContinuous': ['int', [StringType, 'int', StringType, 'int']], + 'XN_InjectDataInterfaceFromCsv': ['int', [StringType, 'int', StringType, 'int', StringType, 'int']], + 'XN_StopCsvDataInject': ['int', [StringType, 'int']] }); } catch (error) { console.error(`加载 ${monitorLibName} 失败:`, error); @@ -437,6 +439,49 @@ function stopInjectContinuous(structName) { } } +// 从CSV文件注入数据 +function injectDataInterfaceFromCsv(structName, csvFilePath) { + if (!monitorLib) { + return '监控服务器库未加载'; + } + try { + const errorMsg = Buffer.alloc(1024); + const result = monitorLib.XN_InjectDataInterfaceFromCsv( + structName, + structName.length, + csvFilePath, + csvFilePath.length, + errorMsg, + errorMsg.length + ); + + if (result !== 0) { + return `注入失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`; + } + return '注入成功'; + } catch (error) { + return `注入失败: ${error.message}`; + } +} + +// 停止CSV数据注入 +function stopCsvDataInject() { + if (!monitorLib) { + return '监控服务器库未加载'; + } + try { + const errorMsg = Buffer.alloc(1024); + const result = monitorLib.XN_StopCsvDataInject(errorMsg, errorMsg.length); + + if (result !== 0) { + return `停止失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`; + } + return '停止成功'; + } catch (error) { + return `停止失败: ${error.message}`; + } +} + module.exports = { loginLib, monitorLib, @@ -460,5 +505,7 @@ module.exports = { getDataMonitorInfo, injectDataInterface, startInjectContinuous, - stopInjectContinuous + stopInjectContinuous, + injectDataInterfaceFromCsv, + stopCsvDataInject }; \ No newline at end of file