V0.21.18.250613_alpha:数据监控的CSV文件注入功能完备

This commit is contained in:
jinchao 2025-06-13 15:12:15 +08:00
parent 5b9bb35775
commit e1b4139d04
10 changed files with 329 additions and 73 deletions

Binary file not shown.

View File

@ -273,7 +273,7 @@ protected:
}
} else if constexpr (is_std_array_v<T>) {
// 对于嵌套数组,递归处理
start_pos = setStdArrayFromString(data[i], value, start_pos + i);
start_pos = setStdArrayFromString(data[i], value, start_pos);
} else {
static_assert(
std::is_arithmetic_v<T> || is_std_array_v<T>,

View File

@ -273,7 +273,7 @@ protected:
}
} else if constexpr (is_std_array_v<T>) {
// 对于嵌套数组,递归处理
start_pos = setStdArrayFromString(data[i], value, start_pos + i);
start_pos = setStdArrayFromString(data[i], value, start_pos);
} else {
static_assert(
std::is_arithmetic_v<T> || is_std_array_v<T>,

View File

@ -11,49 +11,96 @@ CSVDataInjectThread::~CSVDataInjectThread()
stop();
}
bool CSVDataInjectThread::Initialize(std::vector<std::string> structNames)
bool CSVDataInjectThread::Initialize(std::vector<InjectDataInfo> injectDataInfos)
{
m_csvFile.open(m_csvFilePath);
if (!m_csvFile.is_open()) {
return false;
}
m_injectDataInfos = injectDataInfos;
std::string headerLine;
if (!std::getline(m_csvFile, headerLine)) {
return false;
}
std::vector<std::string> interfaceNames;
// 修改CSV头解析逻辑
std::stringstream ss(headerLine);
std::string field;
std::getline(ss, field, ',');
std::getline(ss, field, ','); // 跳过第一列时间戳
// 使用getline读取所有字段包括最后一个
while (std::getline(ss, field, ',')) {
interfaceNames.push_back(field);
// 去除字段前后的空白字符
field.erase(0, field.find_first_not_of(" \t\r\n"));
field.erase(field.find_last_not_of(" \t\r\n") + 1);
if (!field.empty()) {
parseHeaderField(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);
for (const auto &injectDataInfo : m_injectDataInfos) {
auto dataMonitor = DataMonitorFactory::GetInstance(injectDataInfo.structName);
if (dataMonitor == nullptr) {
return false;
}
if (dataMonitor->isInitialized()) {
m_alreadyStartedMonitors[structName] = dataMonitor;
m_alreadyStartedMonitors[injectDataInfo.structName] = dataMonitor;
} else {
m_notStartedMonitors[structName] = dataMonitor;
m_notStartedMonitors[injectDataInfo.structName] = dataMonitor;
}
}
return true;
}
void CSVDataInjectThread::parseHeaderField(const std::string &headerField)
{
CSVHeaderField csvHeaderField;
csvHeaderField.fieldName = headerField.substr(0, headerField.find('['));
csvHeaderField.arrayDim = 0;
csvHeaderField.arrayIndex1 = 0;
csvHeaderField.arrayIndex2 = 0;
if (headerField.find('[') != std::string::npos) {
// 处理一维数组
size_t firstBracketPos = headerField.find('[');
size_t firstBracketEndPos = headerField.find(']');
csvHeaderField.arrayIndex1 = std::stoi(
headerField.substr(firstBracketPos + 1, firstBracketEndPos - firstBracketPos - 1));
csvHeaderField.arrayDim = 1;
// 检查是否有第二个方括号,判断是否为二维数组
if (headerField.find('[', firstBracketEndPos) != std::string::npos) {
size_t secondBracketPos = headerField.find('[', firstBracketEndPos);
size_t secondBracketEndPos = headerField.find(']', secondBracketPos);
csvHeaderField.arrayIndex2 = std::stoi(headerField.substr(
secondBracketPos + 1, secondBracketEndPos - secondBracketPos - 1));
csvHeaderField.arrayDim = 2;
}
}
for (const auto &injectDataInfo : m_injectDataInfos) {
for (int i = 0; i < injectDataInfo.interfaceNames.size(); i++) {
if (injectDataInfo.interfaceNames[i] == csvHeaderField.fieldName) {
csvHeaderField.structName = injectDataInfo.structName;
if (injectDataInfo.arraySizes[i].second > 1) {
csvHeaderField.arraySize1 = injectDataInfo.arraySizes[i].first;
} else {
csvHeaderField.arraySize1 = 0;
}
break;
}
}
}
if (csvHeaderField.structName.empty()) {
return;
}
m_headerFields.push_back(csvHeaderField);
}
void CSVDataInjectThread::start()
{
std::lock_guard<std::mutex> lock(m_mutex);
@ -107,14 +154,62 @@ void CSVDataInjectThread::updateData()
double timeStamp = std::stod(field);
m_nextExecuteTime = static_cast<int64_t>(timeStamp * 1000); // 转换为毫秒
// 解析每个结构体的数据
for (const auto &[structName, interfaceNames] : m_structInterfaceMap) {
std::unordered_map<std::string, std::string> dataMap;
for (const auto &interfaceName : interfaceNames) {
std::getline(ss, field, ',');
dataMap[interfaceName] = field;
// 为每个结构体初始化数据
std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::string>>>
tempDataMap;
for (const auto &injectDataInfo : m_injectDataInfos) {
std::unordered_map<std::string, std::vector<std::string>> dataMap;
for (int i = 0; i < injectDataInfo.interfaceNames.size(); i++) {
if (injectDataInfo.arraySizes[i].first > 1) {
for (int j = 0; j < injectDataInfo.arraySizes[i].first; j++) {
if (injectDataInfo.arraySizes[i].second > 1) {
for (int k = 0; k < injectDataInfo.arraySizes[i].second; k++) {
dataMap[injectDataInfo.interfaceNames[i]].push_back("0");
}
} else {
dataMap[injectDataInfo.interfaceNames[i]].push_back("0");
}
}
} else {
dataMap[injectDataInfo.interfaceNames[i]].push_back("0");
}
}
m_data[structName] = dataMap;
tempDataMap[injectDataInfo.structName] = dataMap;
}
// 读取下一行数据
for (int i = 0; i < m_headerFields.size(); i++) {
std::getline(ss, field, ',');
if (m_headerFields[i].arrayDim == 0) {
tempDataMap[m_headerFields[i].structName][m_headerFields[i].fieldName][0] = field;
} else if (m_headerFields[i].arrayDim == 1) {
tempDataMap[m_headerFields[i].structName][m_headerFields[i].fieldName]
[m_headerFields[i].arrayIndex1] = field;
} else if (m_headerFields[i].arrayDim == 2) {
tempDataMap[m_headerFields[i].structName][m_headerFields[i].fieldName]
[m_headerFields[i].arrayIndex1 * m_headerFields[i].arraySize1
+ m_headerFields[i].arrayIndex2] = field;
}
}
// 将tempDataMap转换为m_data格式
for (const auto &[structName, dataMap] : tempDataMap) {
std::unordered_map<std::string, std::string> structData;
for (const auto &[interfaceName, values] : dataMap) {
if (values.empty()) {
structData[interfaceName] = "0";
} else {
std::stringstream ss;
for (size_t i = 0; i < values.size(); ++i) {
ss << values[i];
if (i < values.size() - 1) {
ss << ",";
}
}
structData[interfaceName] = ss.str();
}
}
m_data[structName] = structData;
}
}

View File

@ -24,7 +24,7 @@ public:
*/
~CSVDataInjectThread();
bool Initialize(std::vector<std::string> structNames);
bool Initialize(std::vector<InjectDataInfo> injectDataInfos);
/**
* @brief 线
@ -50,10 +50,13 @@ private:
*/
void threadFunc();
void parseHeaderField(const std::string &headerField);
private:
std::string m_csvFilePath;
std::ifstream m_csvFile;
std::unordered_map<std::string, std::vector<std::string>> m_structInterfaceMap;
std::vector<InjectDataInfo> m_injectDataInfos;
std::vector<CSVHeaderField> m_headerFields;
std::thread m_thread; ///< 数据注入线程
std::atomic<bool> m_running; ///< 线程运行标志
std::mutex m_mutex; ///< 互斥锁
@ -65,6 +68,4 @@ private:
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
m_data; ///< 要注入的数据
std::atomic<int64_t> m_nextExecuteTime; ///< 下一次执行的时间点
};
using CSVDataInjectThreadPtr = std::shared_ptr<CSVDataInjectThread>;
};

View File

@ -209,3 +209,51 @@ struct ModelDefinition {
*/
std::vector<std::shared_ptr<NamespaceDefinition>> namespaceDefinitions;
};
struct InjectDataInfo {
/**
* @brief
*/
std::string structName;
/**
* @brief
*/
std::vector<std::string> interfaceNames;
/**
* @brief
*/
std::vector<std::pair<int, int>> arraySizes;
};
struct CSVHeaderField {
/**
* @brief
*/
std::string fieldName;
/**
* @brief
*/
std::string structName;
/**
* @brief
*/
int arrayDim;
/**
* @brief 1
*/
int arrayIndex1;
/**
* @brief 2
*/
int arrayIndex2;
/**
* @brief
*/
int arraySize1;
};

View File

@ -20,7 +20,7 @@ bool g_modelInfoMonitorStarted = false;
SystemControl *systemControl = nullptr;
bool g_systemControlStarted = false;
CSVDataInjectThreadPtr g_csvDataInjectThread;
CSVDataInjectThread *g_csvDataInjectThread = nullptr;
// 初始化函数实现
int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize)
@ -584,26 +584,40 @@ int XN_StopInjectContinuous(const char *structName, const int structNameLen, cha
}
// 从csv文件中注入数据接口
int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName,
const int structNameLen,
int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *injectDataInfo,
const int injectDataInfoLen,
const char *csvFilePath,
const int csvFilePathLen, char *infoMsg,
int infoMsgSize)
{
std::vector<std::string> 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;
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
for (const auto &structNameJson : structNamesJson) {
structNames.push_back(structNameJson.get<std::string>());
return -1;
}
if (g_csvDataInjectThread != nullptr) {
g_csvDataInjectThread->stop();
delete g_csvDataInjectThread;
g_csvDataInjectThread = nullptr;
}
std::vector<InjectDataInfo> injectDataInfos;
std::string injectDataInfoStr(injectDataInfo, injectDataInfoLen);
try {
nlohmann::json injectDataInfoJson = nlohmann::json::parse(injectDataInfoStr);
for (const auto &[structName, interfaceInfo] : injectDataInfoJson.items()) {
InjectDataInfo info;
info.structName = structName;
for (const auto &[interfaceName, size] : interfaceInfo.items()) {
info.interfaceNames.push_back(interfaceName);
if (size.is_array()) {
info.arraySizes.push_back({size[0].get<int>(), size[1].get<int>()});
} else {
info.arraySizes.push_back({0, 0});
}
}
injectDataInfos.push_back(info);
}
} catch (const nlohmann::json::parse_error &e) {
if (infoMsg && infoMsgSize > 0) {
@ -628,31 +642,42 @@ int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName,
}
return -1;
}
if (g_csvDataInjectThread == nullptr) {
g_csvDataInjectThread = std::make_shared<CSVDataInjectThread>(csvFilePathStr);
bool ret = g_csvDataInjectThread->Initialize(structNames);
if (ret) {
g_csvDataInjectThread->start();
} else {
try {
g_csvDataInjectThread = new CSVDataInjectThread(csvFilePathStr);
if (!g_csvDataInjectThread->Initialize(injectDataInfos)) {
delete g_csvDataInjectThread;
g_csvDataInjectThread = nullptr;
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "CSV 注入线程初始化失败", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
} else {
g_csvDataInjectThread->start();
} catch (const std::exception &e) {
if (g_csvDataInjectThread) {
delete g_csvDataInjectThread;
g_csvDataInjectThread = nullptr;
}
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "CSV 注入线程已在运行", infoMsgSize - 1);
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
std::cout << "CSV注入线程已启动" << std::endl;
return 0;
}
int XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize)
{
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (g_csvDataInjectThread == nullptr) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "CSV 注入线程已不存在", infoMsgSize - 1);
@ -660,14 +685,20 @@ int XN_GetCsvDataInjectStatus(char *infoMsg, int infoMsgSize)
}
return -1;
}
if (g_csvDataInjectThread->isRunning()) {
return 1;
}
return 0;
return g_csvDataInjectThread->isRunning() ? 1 : 0;
}
int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize)
{
if (!g_initialized) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "DDSMonitor Not Initialized", infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
if (g_csvDataInjectThread == nullptr) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, "CSV 注入线程已不存在", infoMsgSize - 1);
@ -676,9 +707,18 @@ int XN_StopCsvDataInject(char *infoMsg, int infoMsgSize)
return -1;
}
g_csvDataInjectThread->stop();
g_csvDataInjectThread.reset(); // 释放智能指针
std::cout << "CSV注入线程已停止" << std::endl;
try {
g_csvDataInjectThread->stop();
delete g_csvDataInjectThread;
g_csvDataInjectThread = nullptr;
} catch (const std::exception &e) {
if (infoMsg && infoMsgSize > 0) {
strncpy(infoMsg, e.what(), infoMsgSize - 1);
infoMsg[infoMsgSize - 1] = '\0';
}
return -1;
}
return 0;
}

View File

@ -214,8 +214,8 @@ extern "C"
* @param infoMsgSize
* @return 0: , -1:
*/
int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *structName,
const int structNameLen,
int XNMONITORSERVER_EXPORT XN_InjectDataInterfaceFromCsv(const char *InjectDataInfo,
const int InjectDataInfoLen,
const char *csvFilePath,
const int csvFilePathLen,
char *infoMsg, int infoMsgSize);

View File

@ -21,6 +21,7 @@ class DataMonitor extends HTMLElement {
isInjecting: false,
fileName: '',
structNames: [],
structData: {}, // 用于存储结构体数据
filePath: '' // 添加文件路径
};
this.monitorStatus = 0; // 监控状态0-未监控1-监控中2-错误
@ -419,7 +420,6 @@ class DataMonitor extends HTMLElement {
throw new Error(`获取CSV注入状态失败: ${csvStatusResponse.status} ${csvStatusResponse.statusText}`);
}
const csvStatusData = await csvStatusResponse.json();
console.log('CSV注入状态:', csvStatusData.data);
// 如果状态为0触发停止注入
if (csvStatusData.data === 0) {
// 模拟点击停止CSV注入按钮
@ -1916,7 +1916,7 @@ class DataMonitor extends HTMLElement {
'Content-Type': 'application/json'
},
body: JSON.stringify({
structName: JSON.stringify(this.csvState.structNames),
structName: JSON.stringify(this.csvState.structData),
csvFilePath: this.csvState.filePath
})
});
@ -2127,7 +2127,7 @@ class DataMonitor extends HTMLElement {
// 检查每个头部是否在接口表中
const missingInterfaces = [];
const invalidInterfaces = [];
const structNames = []; // 用于按顺序收集结构体名称
const structData = {}; // 用于存储结构体数据
// 检查第一个接口(时间)是否在接口表中
const firstHeader = validateResult.headers[0];
@ -2141,14 +2141,49 @@ class DataMonitor extends HTMLElement {
// 检查其他接口是否在接口表中
for (let i = 1; i < validateResult.headers.length; i++) {
const header = validateResult.headers[i];
// 提取接口名称和数组索引
const match = header.match(/^(.+?)(?:\[(\d+)\](?:\[(\d+)\])?)?$/);
if (!match) continue;
const baseInterfaceName = match[1];
const index1 = match[2] ? parseInt(match[2]) : null;
const index2 = match[3] ? parseInt(match[3]) : null;
const interfaceInfo = this.interfaces.find(interfaceItem =>
interfaceItem.InterfaceName === header
interfaceItem.InterfaceName === baseInterfaceName
);
if (!interfaceInfo) {
missingInterfaces.push(header);
} else {
// 按顺序收集结构体名称
structNames.push(interfaceInfo.ModelStructName);
// 构造结构体数据
if (!structData[interfaceInfo.ModelStructName]) {
structData[interfaceInfo.ModelStructName] = {};
}
if (!structData[interfaceInfo.ModelStructName][baseInterfaceName]) {
const size1 = interfaceInfo.InterfaceArraySize_1 || 0;
const size2 = interfaceInfo.InterfaceArraySize_2 || 0;
structData[interfaceInfo.ModelStructName][baseInterfaceName] = [size1, size2];
}
// 检查数组索引是否越界
if (index1 !== null) {
if (index2 !== null) {
// 二维数组
if (index1 >= interfaceInfo.InterfaceArraySize_1 || index2 >= interfaceInfo.InterfaceArraySize_2) {
console.warn(`接口 ${baseInterfaceName} 的数组索引 [${index1}][${index2}] 超出范围`);
continue;
}
} else {
// 一维数组
if (index1 >= interfaceInfo.InterfaceArraySize_1) {
console.warn(`接口 ${baseInterfaceName} 的数组索引 [${index1}] 超出范围`);
continue;
}
}
}
}
}
@ -2183,7 +2218,7 @@ class DataMonitor extends HTMLElement {
// 更新CSV状态
this.csvState.fileName = result.file.name;
this.csvState.structNames = structNames;
this.csvState.structData = structData; // 保存结构体数据
this.csvState.filePath = result.file.path;
}

View File

@ -326,7 +326,7 @@ router.post('/stop-all-continuous', async (req, res) => {
/**
* @brief 从CSV文件注入数据
* @route POST /api/data-monitor/inject-csv
* @param {string} structName - 结构体名称
* @param {string} structName - 结构体数据JSON字符串格式为 {structName1: {interfaceName1: [size1, size2], ...}, structName2: ...}
* @param {string} csvFilePath - CSV文件路径
* @returns {Object} 返回注入结果
*/
@ -340,6 +340,43 @@ router.post('/inject-csv', async (req, res) => {
});
}
// 解析并验证 structName JSON 字符串
let structData;
try {
structData = JSON.parse(structName);
// 验证数据结构
if (typeof structData !== 'object' || structData === null) {
return res.status(400).json({
success: false,
message: '结构体数据格式错误:必须是有效的对象'
});
}
// 验证每个结构体的接口数据
for (const [structName, interfaces] of Object.entries(structData)) {
if (typeof interfaces !== 'object' || interfaces === null) {
return res.status(400).json({
success: false,
message: `结构体 ${structName} 的接口数据格式错误:必须是有效的对象`
});
}
for (const [interfaceName, sizes] of Object.entries(interfaces)) {
if (!Array.isArray(sizes) || sizes.length !== 2 ||
typeof sizes[0] !== 'number' || typeof sizes[1] !== 'number' ||
sizes[0] < 0 || sizes[1] < 0) {
return res.status(400).json({
success: false,
message: `接口 ${interfaceName} 的数据格式错误:必须是包含两个非负数字的数组 [size1, size2]`
});
}
}
}
} catch (error) {
return res.status(400).json({
success: false,
message: `结构体数据解析失败: ${error.message}`
});
}
const result = systemMonitor.injectDataInterfaceFromCsv(structName, csvFilePath);
if (result.includes('失败')) {
return res.status(500).json({ success: false, message: result });