#pragma once /* * pq_app.h * * 这是整个工具的公共边界文件: * - interface.cpp 负责 Win32 GUI,通过这些结构体读取/显示配置和台账。 * - mq.cpp 负责 HTTP 台账/ICD 获取、RocketMQ 初始化和数据追踪落盘。 * - main.cpp 负责 JSON/XML 映射解析,生成 Excel 2003 XML 格式的 .xls。 * - icd_mapper.cpp 负责把外部 ICD 中的 DOI 描述补写到工作簿。 * * 头文件中只放跨模块共享的数据结构和函数声明,不放 Win32/RocketMQ 的实现细节。 */ #include #include #include #include /* * 工具运行配置。 * * 输入来源: * 1. pqDefaultConfig() 给出默认值; * 2. GUI 中 readConfig() 会用用户界面上的编辑框覆盖默认值; * 3. 命令行模式直接使用 main.cpp 中传入的 XML/JSON/origin 路径。 * * 关键字段: * frontInst/frontIp 前置节点标识和 IP,参与台账请求和追踪消息。 * terminalStatus 终端状态过滤条件,支持 JSON 数组字符串,如 "[0]"。 * monitorStatus 预留的测点状态过滤字段。 * icdFlag 为 "1" 时按 devType 请求 ICD 列表,为其他值时请求全量/默认列表。 * dataStatTopic 统计数据 Topic,对应 frontType=cfg_stat_data。 * dataRealtimeTopic 实时数据 Topic,对应 frontType=cfg_3s_data。 * topicAsk/tagAsk/keyAsk cfg_3s_data 发送 set_log 后的实时数据请求目标。 * dataTopic/dataTag/dataKey GUI 读取 frontType 后会设置成当前活跃数据 Topic/Tag/Key。 * traceRootDir 每次“数据追踪”按进程/装置/测点保存 JSON、origin 和报表的根目录。 * * 输出影响: * xmlSaveDir、jsonDataPath、originPaths、outputPath、traceRootDir 控制程序落盘位置。 */ struct PqToolConfig { std::string frontInst = "6c2adb324940e19e208d717f4970f2ee"; std::string frontIp = "192.168.1.68"; std::string terminalStatus = "[0]"; std::string monitorStatus = "[1,2]"; std::string icdFlag = "0"; std::string webDevice = "http://192.168.1.68:10202/nodeDevice/nodeDeviceList"; std::string webIcd = "http://192.168.1.68:10202/icd/icdPathList"; std::string webFiledownload = "http://192.168.1.68:10207/file/download"; std::string xmlSaveDir = "runtime\\downloaded_xml"; std::string producer = "TRACE_PRODUCER"; std::string ipport = "192.168.1.68:9876"; std::string producerAccessKey = "rmqroot"; std::string producerSecretKey = "001@#njcnmq"; std::string topicLOG = "ask_log_Topic"; std::string tagLOG = "6c2adb324940e19e208d717f4970f2ee"; std::string keyLOG = "Test_Keys"; std::string topicAsk = "ask_data_Topic"; std::string tagAsk = "6c2adb324940e19e208d717f4970f2ee"; std::string keyAsk = "Test_Keys"; std::string consumer = "TRACE_CONSUMER"; std::string consumerIpport = "192.168.1.68:9876"; std::string consumerAccessKey = "rmqroot"; std::string consumerSecretKey = "001@#njcnmq"; std::string dataStatTopic = "MQ_INST_1697676490574298_Bm3DLamM%LN_Topic"; std::string dataRealtimeTopic = "MQ_INST_1697676490574298_Bm3DLamM%Real_Time_Data_Topic"; std::string dataTopic = "MQ_INST_1697676490574298_Bm3DLamM%LN_Topic"; std::string dataStatTag = "6c2adb324940e19e208d717f4970f2ee"; std::string dataStatKey = "Test_Keys"; std::string dataRealtimeTag = "6c2adb324940e19e208d717f4970f2ee"; std::string dataRealtimeKey = "Test_Keys"; std::string dataTag = "6c2adb324940e19e208d717f4970f2ee"; std::string dataKey = "Test_Keys"; std::string traceTopic = "Trace_Topic"; std::string traceTag = "6c2adb324940e19e208d717f4970f2ee"; std::string traceKey = "Test_Key"; std::string xmlPath; std::string jsonDataPath = "runtime\\json\\jsondata.txt"; std::vector jsonDataPaths; std::vector originPaths; std::string outputPath = "runtime\\final_sorted.xls"; std::string traceRootDir = "runtime\\trace_data"; }; /* * 台账中的单个测点。 * * 输入:pqFetchLedgerAndIcd() 从 WebDevice 接口的 monitorData 数组解析。 * 输出:GUI 左侧树显示;数据追踪时 monitor.id 会写入 MQ 追踪消息。 */ struct PqLedgerMonitor { std::string id; std::string name; std::string lineNo; std::string voltageLevel; std::string connectType; std::string status; std::vector> fields; }; /* * 台账中的单个装置。 * * 输入:pqFetchLedgerAndIcd() 从 WebDevice 接口的 data 数组解析。 * 输出: * - GUI 按 processNo -> device -> monitor 组织成树; * - devType 用于匹配并下载对应 ICD/XML; * - xmlPath 记录当前装置优先使用的 XML 模板。 */ struct PqLedgerDevice { std::string id; std::string name; std::string ip; std::string status; std::string devType; std::string devKey; std::string orgName; std::string stationName; std::string subName; std::string manufacturer; std::string port; std::string series; std::string updateTime; std::string processNo; std::string maxProcessNum; std::string xmlPath; std::vector> fields; std::vector monitors; }; // 日志回调:业务函数只发字符串,GUI/命令行决定显示到日志框还是控制台。 using PqLogFn = std::function; /* * MQ/追踪过程中抛给 GUI 的事件。 * * kind: * sent 已构造并发送/落盘的数据追踪请求; * data DATATopic 收到的 jsondata 数据; * trace TraceTopic 收到的 origin/原始 MMS 数据。 * * path/traceDir 让 GUI 能把事件定位到具体追踪目录并自动刷新解析预览。 */ struct PqMqEvent { std::string kind; std::string title; std::string body; std::string path; std::string monitorId; std::string traceDir; }; using PqMqEventFn = std::function; /** * @brief 创建一份完整的默认运行配置。 * @details mq.cpp 会把相对路径修正到 exe 所在目录旁的 runtime 目录;GUI 随后再用 * 编辑框内容覆盖相应字段。返回值是独立副本,调用方可以安全修改。 * @return 包含 HTTP、RocketMQ、Topic/Tag/Key 和落盘路径默认值的 PqToolConfig。 */ PqToolConfig pqDefaultConfig(); /* * 获取台账并下载 ICD/XML。 * * 输入:cfg 中的 WebDevice/WebIcd/WebFiledownload、状态过滤和保存目录。 * 输出: * devices 装置和测点列表; * firstXmlPath 第一个成功下载的 XML,用于自动填充 GUI 的 XML 路径; * return true 表示接口请求和解析流程完成,异常会抛出到调用方。 */ bool pqFetchLedgerAndIcd(const PqToolConfig& cfg, std::vector& devices, std::string& firstXmlPath, PqLogFn log); /* * 初始化 RocketMQ 运行时。 * * 输入:生产者、消费者、Topic、Tag、Key、NameServer 和鉴权配置。 * 输出:运行配置会保存到 runtime\json\mq_runtime_config.json; * 如果 DLL 不可用,函数仍返回 true,但后续发送会落到 mq_outbox_*.jsonl。 */ bool pqInitializeMq(const PqToolConfig& cfg, PqLogFn log, PqMqEventFn event = {}); /* * 构造数据追踪消息体。 * * 输入: * device.processNo 统计数据追踪时参与 processNo;实时数据固定 processNo=0。 * monitor.id 写入 messageBody.id,代表追踪的测点。 * frontType cfg_stat_data 或 cfg_3s_data。 * 输出:外层 RocketMQ JSON 字符串,key 为 guid,messageBody 是内部 JSON 字符串。 */ std::string pqBuildTracePayload(const PqToolConfig& cfg, const PqLedgerDevice& device, const PqLedgerMonitor& monitor, const std::string& frontType); /* * 发送一次数据追踪请求。 * * 执行步骤: * 1. 构造 payload 并在 trace_context.json 中记录本次追踪上下文; * 2. 写 sent_trace_request.json 和 last_trace_request.json,便于复盘; * 3. RocketMQ 可用时先同步发送到 topicLOG/tagLOG/keyLOG; * 4. cfg_3s_data 再发送实时数据请求到 topicAsk/tagAsk/keyAsk; * 5. RocketMQ 不可用时追加到对应 Topic 的 mq_outbox_*.jsonl 作为兜底。 */ bool pqSendTraceRequest(const PqToolConfig& cfg, const PqLedgerDevice& device, const PqLedgerMonitor& monitor, const std::string& frontType, PqLogFn log, PqMqEventFn event = {}); /** * @brief 清空指定装置/测点本次追踪可消费的历史文本记录。 * @details 路径由 pqTraceDirectoryFor() 统一计算;只移除 txt、json、jsonl、log 等 * 追踪记录并保留目录,以保证点击“数据追踪”后只读取新到达的数据。 * @param cfg 包含 traceRootDir 的运行配置。 * @param device 当前台账装置,参与进程和装置目录名计算。 * @param monitor 当前测点,参与最终目录名计算。 */ void pqClearTraceRecords(const PqToolConfig& cfg, const PqLedgerDevice& device, const PqLedgerMonitor& monitor); /** * @brief 计算某个装置和测点的稳定追踪目录。 * @details 目录布局为 traceRootDir/process_x/device_x/monitor_x;实现会清理不适合 * Windows 文件名的字符,所有发送、消费、自动解析流程都应调用本函数,避免 * 各模块自行拼路径后指向不同目录。 * @return UTF-8 编码的目录字符串;本函数只计算路径,不创建目录。 */ std::string pqTraceDirectoryFor(const PqToolConfig& cfg, const PqLedgerDevice& device, const PqLedgerMonitor& monitor); /** * @brief 为当前装置选择最合适的 ICD/XML 映射模板。 * @details 查找顺序为 device.xmlPath、icd_response.json 中的下载缓存、cfg.xmlPath; * 只有实际存在的普通文件才会返回,防止解析阶段拿到失效路径。 * @param cfg 提供 XML 缓存目录和用户配置的兜底路径。 * @param device 提供装置自身的 xmlPath、devType 等识别信息。 * @return 找到时返回 UTF-8 路径,未找到时返回空字符串。 */ std::string pqFindDeviceXmlPath(const PqToolConfig& cfg, const PqLedgerDevice& device); /* * 解析 XML + 单个 jsondata + 多个 origin,生成 Excel XML 工作簿。 * 这是旧调用形式,内部会转成 vector 版本。 */ bool pqRunParser(const std::string& xmlPath, const std::string& jsonPath, const std::vector& originPaths, const std::string& outPath, std::string* err, const std::string& frontType = ""); /* * 解析 XML + 多个 jsondata + 多个 origin,生成 Excel XML 工作簿。 * * 输入: * xmlPath ICD/XML 映射模板; * jsonPaths DATATopic 统计/实时数据,可按 DATA_TYPE 分桶; * originPaths TraceTopic 原始 MMS 数据,用于值对比和未匹配列表; * frontType 为空表示生成所有工作表;cfg_stat_data/cfg_3s_data 表示只生成对应流程。 * 输出: * outPath Excel 2003 XML 格式的 .xls; * err 失败时写入异常信息; * return true 成功,false 失败。 */ bool pqRunParser(const std::string& xmlPath, const std::vector& jsonPaths, const std::vector& originPaths, const std::string& outPath, std::string* err, const std::string& frontType = ""); /* * 把外部 ICD 中 LN/DOI 的 desc 导入到工作簿“未匹配的原始数据”页。 * 通过原始路径前缀匹配 DO 路径,帮助解释没有在当前映射中匹配到的 origin 数据。 */ bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath, const std::string& workbookPath, std::string* message = nullptr); /** * @brief 启动 Win32 图形界面并运行消息循环。 * @details main.cpp 在无命令行参数时调用;函数注册窗口类、创建 AppState 和各页面, * 然后通过 GetMessage/DispatchMessage 把用户操作交给 interface.cpp::wndProc。 * @return 正常退出时返回消息循环的退出码,初始化失败时返回非零值。 */ int runPqGuiApp();