//////////////////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // socket 网络编程 #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////////////////// #include "worker.h" #include "interface.h" #include "rocketmq.h" #include "log4.h" #include "front.h" ///////////////////////////////////////////////////////////////////////////////////////////////////// //shell日志打印开关 bool showinshellflag =false; //////////////////////////////////////////////////////////////////////////////////////////////////// extern std::list errorList, warnList, normalList; extern std::mutex errorListMutex, warnListMutex, normalListMutex; extern int IED_COUNT; extern int INITFLAG; extern int g_front_seg_index; extern std::string subdir; extern int G_TEST_NUM; extern int G_TEST_TYPE; extern int LEDGER_MAX_ITEMS; extern bool errorOutputEnabled; extern bool warnOutputEnabled; extern bool normalOutputEnabled; ////////////////////////////////////////////////////////////////////////////////////////////////////测试登录类 Worker::Worker(Front* front) : m_front(front), listenFD(-1), running(false), historyIndex(-1), stopViewLog(true), g_stopTelnetTest(true) {} Worker::~Worker() { stopServer(); } // 启动 Telnet 服务(监听端口) bool Worker::startServer(int port) { if (running) return false; listenFD = socket(AF_INET, SOCK_STREAM, 0); if (listenFD < 0) return false; int opt = 1; setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); if (bind(listenFD, (sockaddr*)&addr, sizeof(addr)) < 0) return false; if (listen(listenFD, 5) < 0) return false; running = true; serverThread = std::thread(&Worker::acceptLoop, this); std::thread periodicThread(&Worker::doPeriodicTask, this); periodicThread.detach(); return true; } // 停止 Telnet 服务 void Worker::stopServer() { if (!running) return; running = false; shutdown(listenFD, SHUT_RDWR); close(listenFD); if (serverThread.joinable()) serverThread.join(); } // 发送字符串 void Worker::sendStr(int fd, const std::string& s) { send(fd, s.c_str(), s.size(), 0); } // 接收客户端连接主循环 void Worker::acceptLoop() { while (running) { sockaddr_in caddr; socklen_t clen = sizeof(caddr); int cfd = accept(listenFD, (sockaddr*)&caddr, &clen); if (cfd >= 0) std::thread(&Worker::sessionThread, this, cfd).detach(); } } //打印提示符 void Worker::printPrompt(int clientFD) { sendStr(clientFD, "\r\n>"); } // 客户端会话线程处理函数 void Worker::sessionThread(int clientFD) { sendTelnetNegotiation(clientFD); sendStr(clientFD, "\r\nWelcome to the test shell. Type 'help'.\r\n> "); while (true) { char buf[512]; int n = recv(clientFD, buf, sizeof(buf), 0); if (n <= 0) break; for (int i = 0; i < n; ++i) { unsigned char c = (unsigned char)buf[i]; // 如果不是可打印字符,且也不是 上/下箭头、退格、回车等,就忽略 bool isPrintable = (c >= 32 && c <= 126); //可见字符范围 bool isArrowOrEsc = (c == 0x1b); //ESC bool isEnter = (c == '\r' || c == '\n'); //换行 bool isBackspace = (c == 0x7f || c == 0x08);//退格 bool isTab = (c == '\t'); //tab if( !isPrintable && !isArrowOrEsc && !isEnter && !isBackspace && !isTab ) { // 跳过不认识的控制字符:如 '\0' continue; } // Telnet 协议协商处理 if (c == IAC) { if (i + 1 < n && (buf[i+1] == TELDO || buf[i+1] == DONT || buf[i+1] == WILL || buf[i+1] == WONT)) i += 2; else i++; continue; } // 退出 viewlog / telnet 流程 if (c == '`') { stopViewLog = true; // [MOD] 支持停止日志查看 g_stopTelnetTest = true; // [MOD] 停止 telnet 测试 sendStr(clientFD, "\r\n[Log view stopped]\r\n> "); continue; } // 2) 回车/换行 => 执行命令 if (c == '\r' || c == '\n') { // 如果当前是 '\r' 并且下一个是 '\n',就跳过 '\n' if (c == '\r') { if (i + 1 < n && buf[i+1] == '\n') { i++; } // 这里再加一步,如果紧跟着是 '\0',也跳过 if (i + 1 < n && buf[i+1] == '\0') { i++; } } // 如果当前是 '\n',也可以检查一下下一个是不是 '\0'(有些客户端可能发 '\n\0') else { if (i + 1 < n && buf[i+1] == '\0') { i++; } } // 现在把 当前输入的指令 前后空白去掉 std::string cmdtrim = trim(currentCommand); if (!cmdtrim.empty()) { //输入指令非空则记录到历史 if (commandHistory.empty() || commandHistory.back() != cmdtrim) { //防止连续重复的历史记录 commandHistory.push_back(cmdtrim); } historyIndex = commandHistory.size(); //更新历史指令下标 processCommand(cmdtrim, clientFD); //处理当前指令 } // 处理后清空并打印新的提示符 currentCommand.clear(); printPrompt(clientFD); // 回车后处理新的命令行 continue; } // 上/下箭头处理 if (c == 0x1b && i + 2 < n && buf[i+1] == '[') { char arrow = buf[i+2]; if (arrow == 'A') handleUpArrow(clientFD); else if (arrow == 'B') handleDownArrow(clientFD); i += 2; continue; } // 退格处理 if (c == 0x7f || c == 0x08) { if (!currentCommand.empty()) { currentCommand.pop_back(); sendStr(clientFD, "\b \b"); } continue; } // 普通字符输入 currentCommand.push_back((char)c); sendBytes(clientFD, (const char*)&c, 1); } } close(clientFD); } // 发送 Telnet 协商指令 void Worker::sendTelnetNegotiation(int clientFD) { unsigned char will_echo[3] = { IAC, WILL, TELOPT_ECHO }; unsigned char will_sga[3] = { IAC, WILL, TELOPT_SUPPRESS_GO_AHEAD }; unsigned char dont_lin[3] = { IAC, DONT, TELOPT_LINEMODE }; sendBytes(clientFD, (const char*)will_echo, 3); sendBytes(clientFD, (const char*)will_sga, 3); sendBytes(clientFD, (const char*)dont_lin, 3); } // 发送字节数组 void Worker::sendBytes(int fd, const char* buf, int len) { send(fd, buf, len, 0); } void Worker::setTestNum(int num) { std::lock_guard locker(testMutex); G_TEST_NUM = num; } void Worker::setTestType(int type) { std::lock_guard locker(testMutex); G_TEST_TYPE = type; } void Worker::setMaxItems(int items) { std::lock_guard locker(testMutex); LEDGER_MAX_ITEMS = items; } // 日志控制 void Worker::setTestlog(bool flag) { redirectErrorOutput(flag); redirectWarnOutput(flag); redirectNormalOutput(flag); } void Worker::doPeriodicTask() { while (running) { { std::lock_guard locker(testMutex); std::cout << "[PeriodicTask] G_TEST_NUM = " << G_TEST_NUM << ", G_TEST_TYPE = " << G_TEST_TYPE << std::endl; if (G_TEST_NUM != 0) { std::cout << "[PeriodicTask] Executing rocketmq_test_300()\n"; rocketmq_test_300(G_TEST_NUM, g_front_seg_index, G_TEST_TYPE,m_front); //upload_data_test(); } } std::this_thread::sleep_for(std::chrono::seconds(60)); // 每 60 秒执行一次 } } // 命令处理逻辑扩展 void Worker::processCommand(const std::string &cmd, int clientFD) { std::cout << "Received command: " << cmd << std::endl; if (cmd == "help") { std::string helpText = "Available commands:\r\n" "G_TEST_NUM= - Set the G_TEST_NUM\r\n" "G_TEST_TYPE= - Set the G_TEST_TYPE 0:use ledger,1:use number\r\n" "LOG= - Set the LOG\r\n" "dir - Execute rocketmq_test_getdir\r\n" "rc - Execute rocketmq_test_rc\r\n" "rt - Execute rocketmq_test_rt\r\n" "ud - Execute rocketmq_test_ud\r\n" "set - Execute rocketmq_test_set\r\n" "log - Execute rocketmq_test_log\r\n" "upload - Execute upload file\r\n" "qvvr - Execute http_test_qvvr\r\n" "ledger - Execute ledger with optional terminal_id\r\n" "viewlog - View logs (ERROR, WARN, NORMAL, DEBUG)\r\n" "value - Execute value print with valuename\r\n" "exit - Exit the shell\r\n" "help - Show this help message\r\n"; sendStr(clientFD, "\r\x1B[K" + helpText); } else if (cmd.find("viewlog") == 0) { showinshellflag = true; handleViewLogCommand(cmd, clientFD); } else if (cmd.find("G_TEST_NUM=") == 0) { int num = std::atoi(cmd.substr(9).c_str()); setTestNum(num); sendStr(clientFD, "\r\x1B[KTEST_NUM updated\r\n"); } else if (cmd.find("G_TEST_TYPE=") == 0) { int type = std::atoi(cmd.substr(10).c_str()); setTestType(type); sendStr(clientFD, "\r\x1B[KTEST_TYPE updated\r\n"); } else if (cmd.find("LOG=") == 0) { int flag = std::atoi(cmd.substr(4).c_str()); setTestlog(flag); sendStr(clientFD, "\r\x1B[KLOG updated\r\n"); }else if (cmd.find("MAX=") == 0) { int flag = std::atoi(cmd.substr(4).c_str()); setMaxItems(flag); sendStr(clientFD, "\r\x1B[KMAX_ITEMS updated\r\n"); } else if (cmd == "rc") { rocketmq_test_rc(m_front); sendStr(clientFD, "\r\x1B[KExecuted rocketmq_test_rc\r\n"); } else if (cmd == "getdir") { rocketmq_test_getdir(m_front); sendStr(clientFD, "\r\x1B[KExecuted rocketmq_test_getdir\r\n"); } else if (cmd == "rt") { rocketmq_test_rt(m_front); sendStr(clientFD, "\r\x1B[KExecuted rocketmq_test_rt\r\n"); } else if (cmd == "ud") { rocketmq_test_ud(m_front); sendStr(clientFD, "\r\x1B[KExecuted rocketmq_test_ud\r\n"); } else if (cmd == "set") { rocketmq_test_set(m_front); sendStr(clientFD, "\r\x1B[KExecuted rocketmq_test_set\r\n"); } else if (cmd == "upload") { Fileupload_test(); sendStr(clientFD, "\r\x1B[KExecuted upload file\r\n"); } else if (cmd == "qvvr") { qvvr_test(); sendStr(clientFD, "\r\x1B[KExecuted http_test_qvvr\r\n"); } else if (cmd.find("ledger") == 0) { size_t pos = cmd.find(' '); if (pos != std::string::npos) { std::string terminalId = cmd.substr(pos + 1); ledger(terminalId.c_str(), clientFD); sendStr(clientFD, "\r\x1B[KExecuted ledger with terminal_id\r\n"); } else { ledger("", clientFD); sendStr(clientFD, "\r\x1B[KExecuted ledger without parameters\r\n"); } } else if (cmd.find("value") == 0) { size_t pos = cmd.find(' '); if (pos != std::string::npos) { std::string var = cmd.substr(pos + 1); sendStr(clientFD, "\r\x1B[KExecuted value with variable name: " + var + "\r\n"); value_print(var, clientFD); } else { sendStr(clientFD, "\r\x1B[KPlease provide a variable name\r\n"); } } else if (cmd == "exit") { sendStr(clientFD, "\r\x1B[KGoodbye! Exiting shell...\r\n"); shutdown(clientFD, SHUT_RDWR); close(clientFD); return; } else { sendStr(clientFD, "\r\x1B[KUnknown command\r\n"); } // 打印提示符 sendStr(clientFD, "> "); } // 上箭头历史回溯 void Worker::handleUpArrow(int fd) { if (!commandHistory.empty() && historyIndex > 0) { historyIndex--; currentCommand = commandHistory[historyIndex]; sendStr(fd, "\r\x1B[K> " + currentCommand); } } // 下箭头历史前进 void Worker::handleDownArrow(int fd) { if (!commandHistory.empty() && historyIndex < (int)commandHistory.size() - 1) { historyIndex++; currentCommand = commandHistory[historyIndex]; sendStr(fd, "\r\x1B[K> " + currentCommand); } else { currentCommand.clear(); sendStr(fd, "\r\x1B[K> "); } } // 字符串 trim std::string Worker::trim(const std::string& s) { auto start = s.begin(); while (start != s.end() && std::isspace(*start)) ++start; auto end = s.end(); do { --end; } while (std::distance(start, end) > 0 && std::isspace(*end)); return (start <= end) ? std::string(start, end + 1) : ""; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////测试shell用的函数 void Worker::printLedgerinshell(const terminal_dev& dev, int fd) { // —— 显示控制:最多打印的元素数量(防止过长)—— const size_t MAX_ITEMS = static_cast(LEDGER_MAX_ITEMS); // 非 constexpr std::ostringstream os; os << "\r\x1B[K------------------------------------\n"; os << "\r\x1B[K|-- guid : " << dev.guid << "\n"; os << "\r\x1B[K|-- busytype : " << dev.busytype << "\n"; os << "\r\x1B[K|-- isbusy : " << dev.isbusy << "\n"; os << "\r\x1B[K|-- busytimecount : " << dev.busytimecount << "\n"; //os << "\r\x1B[K|-- dev_index : " << dev.dev_index << "\n"; os << "\r\x1B[K|-- terminal_id : " << dev.terminal_id << "\n"; os << "\r\x1B[K|-- terminal_name : " << dev.terminal_name << "\n"; os << "\r\x1B[K|-- dev_ip : " << dev.addr_str << "\n"; os << "\r\x1B[K|-- dev_port : " << dev.port << "\n"; os << "\r\x1B[K|-- dev_type : " << dev.dev_type << "\n"; os << "\r\x1B[K|-- dev_key : " << dev.dev_key << "\n"; os << "\r\x1B[K|-- dev_series : " << dev.dev_series << "\n"; os << "\r\x1B[K|-- dev_processNo : " << dev.processNo << "\n"; os << "\r\x1B[K|-- maxProcessNum : " << dev.maxProcessNum << "\n"; os << "\r\x1B[K|-- org_name : " << dev.org_name << "\n"; os << "\r\x1B[K|-- maint_name : " << dev.maint_name << "\n"; os << "\r\x1B[K|-- station_name : " << dev.station_name << "\n"; os << "\r\x1B[K|-- tmnl_factory : " << dev.tmnl_factory << "\n"; os << "\r\x1B[K|-- tmnl_status : " << dev.tmnl_status << "\n"; os << "\r\x1B[K|-- timestamp : " << dev.timestamp << "\n"; os << "\r\x1B[K|-- Righttime : " << dev.Righttime << "\n"; os << "\r\x1B[K|-- mac : " << dev.mac << "\n"; // ========================= 终端级 · 内部定值 ========================= // internal_values(ushort 列表)与 dz_internal_info_list 一一对应,仅展示前 MAX_ITEMS 条 os << "\r\x1B[K|-- Internal Values (" << dev.internal_values.size() << "):\n"; { size_t idx = 0; for (auto itv = dev.internal_values.begin(); itv != dev.internal_values.end() && idx < MAX_ITEMS; ++itv, ++idx) { os << "\r\x1B[K |-- [" << idx << "] value: " << static_cast(*itv) << "\n"; } if (dev.internal_values.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (dev.internal_values.size() - MAX_ITEMS) << " more)\n"; } } // dz_internal_info_list(NameFixValue 描述,和 internal_values 对应) os << "\r\x1B[K|-- Internal Value Descriptions (" << dev.dz_internal_info_list.size() << "):\n"; { const size_t n = dev.dz_internal_info_list.size(); for (size_t i = 0; i < n && i < MAX_ITEMS; ++i) { const auto& nf = dev.dz_internal_info_list[i]; os << "\r\x1B[K |-- [" << i << "] " << "Name=" << trim_cstr(nf.sFixValueName, sizeof(nf.sFixValueName)) << ", Type=" << nf.DataType << ", Unit=" << trim_cstr(nf.sDimension, sizeof(nf.sDimension)) << ", Min=" << nf.MinValue << ", Max=" << nf.MaxValue << ", Default=" << nf.DefaultValue << "\n"; } if (n > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (n - MAX_ITEMS) << " more)\n"; } } // control_words(控制字描述) os << "\r\x1B[K|-- Control Words (" << dev.control_words.size() << "):\n"; { const size_t n = dev.control_words.size(); for (size_t i = 0; i < n && i < MAX_ITEMS; ++i) { const DZ_kzz_bit& b = dev.control_words[i]; os << "\r\x1B[K |-- [" << i << "] " << "name=\"" << trim_cstr(b.kzz_bit, sizeof(b.kzz_bit)) << "\"" << ", enable=" << static_cast(b.bit_enable) << "\n"; } if (n > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (n - MAX_ITEMS) << " more)\n"; } } // ========================= 监测点级 · line ========================= for (size_t i = 0; i < dev.line.size(); ++i) { const auto& ld = dev.line[i]; if (ld.monitor_id.empty()) continue; os << "\r\x1B[K|-- line[" << i << "]:\n"; os << "\r\x1B[K |-- monitor_id : " << ld.monitor_id << "\n"; os << "\r\x1B[K |-- monitor_name : " << ld.monitor_name << "\n"; os << "\r\x1B[K |-- logical_device_seq : " << ld.logical_device_seq << "\n"; os << "\r\x1B[K |-- terminal_id : " << ld.terminal_id << "\n"; os << "\r\x1B[K |-- voltage_level : " << ld.voltage_level << "\n"; os << "\r\x1B[K |-- terminal_connect : " << ld.terminal_connect << "\n"; os << "\r\x1B[K |-- status : " << ld.status << "\n"; os << "\r\x1B[K |-- timestamp : " << ld.timestamp << "\n"; os << "\r\x1B[K |-- CT1=" << ld.CT1 << ", CT2=" << ld.CT2 << ", PT1=" << ld.PT1 << ", PT2=" << ld.PT2 << "\n"; // --- 监测点 · 定值(set_values) --- os << "\r\x1B[K |-- Set Values (" << ld.set_values.size() << "):\n"; { size_t idx = 0; for (auto it = ld.set_values.begin(); it != ld.set_values.end() && idx < MAX_ITEMS; ++it, ++idx) { os << "\r\x1B[K |-- [" << idx << "] " << *it << "\n"; } if (ld.set_values.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (ld.set_values.size() - MAX_ITEMS) << " more)\n"; } } // --- 监测点 · 定值描述文件(dz_info_list) --- os << "\r\x1B[K |-- DZ Table (" << ld.dz_info_list.size() << "):\n"; { const size_t n = ld.dz_info_list.size(); for (size_t j = 0; j < n && j < MAX_ITEMS; ++j) { const DZ_TAB_STRUCT& z = ld.dz_info_list[j]; os << "\r\x1B[K |-- [" << j << "] " << "LN=" << z.LN_Num << ", DZ=" << z.DZ_Num << ", Name=" << trim_cstr(z.DZ_Name, sizeof(z.DZ_Name)) << ", Type=" << z.DZ_Type << ", Min=" << z.DZ_Min << ", Max=" << z.DZ_Max << ", Def=" << z.DZ_Default << ", Unit=" << trim_cstr(z.DZ_UNIT, sizeof(z.DZ_UNIT)) << "\n"; } if (n > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (n - MAX_ITEMS) << " more)\n"; } } // --- 监测点 · 暂态事件(qvvrevent) --- os << "\r\x1B[K |-- QVVR Event:\n"; // 1) 暂态事件列表 os << "\r\x1B[K |-- Events (" << ld.qvvrevent.qvvrdata.size() << "):\n"; { const size_t n = ld.qvvrevent.qvvrdata.size(); for (size_t k = 0; k < n && k < MAX_ITEMS; ++k) { const qvvr_data& e = ld.qvvrevent.qvvrdata[k]; os << "\r\x1B[K |-- [" << k << "] " << "used=" << e.used_status << ", type=" << e.QVVR_type << ", time=" << static_cast(e.QVVR_time) << ", per=" << e.QVVR_PerTime << ", amp=" << e.QVVR_Amg << ", phase="<< e.phase << "\n"; } if (n > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (n - MAX_ITEMS) << " more)\n"; } } // 2) 文件组列表 os << "\r\x1B[K |-- File Groups (" << ld.qvvrevent.qvvrfile.size() << "):\n"; { const size_t n = ld.qvvrevent.qvvrfile.size(); for (size_t g = 0; g < n && g < MAX_ITEMS; ++g) { const qvvr_file& fg = ld.qvvrevent.qvvrfile[g]; os << "\r\x1B[K |-- [" << g << "] " << "used=" << fg.used_status << ", tcount=" << fg.file_time_count << ", downloaded=" << fg.is_download << ", paired=" << fg.is_pair << "\n"; // 文件名列表(file_name) os << "\r\x1B[K |-- file_name (" << fg.file_name.size() << "):\n"; { size_t c = 0; for (const auto& fn : fg.file_name) { if (c++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- " << fn << "\n"; } if (fg.file_name.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (fg.file_name.size() - MAX_ITEMS) << " more)\n"; } } // 已下载列表(file_download) os << "\r\x1B[K |-- file_download (" << fg.file_download.size() << "):\n"; { size_t c = 0; for (const auto& fn : fg.file_download) { if (c++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- " << fn << "\n"; } if (fg.file_download.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (fg.file_download.size() - MAX_ITEMS) << " more)\n"; } } } if (n > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (n - MAX_ITEMS) << " more)\n"; } } // ========================= ★新增:补招打印 ========================= // ★新增:小工具—把状态/阶段枚举转成可读字符串 auto recallStatusStr = [](int st) -> const char* { switch (st) { case 0: return "NOT_STARTED(0)"; case 1: return "RUNNING(1)"; case 2: return "DONE(2)"; case 3: return "FAILED(3)"; case 4: return "EMPTY(4)"; default: return "UNKNOWN"; } }; auto phaseStr = [](RecallPhase p) -> const char* { switch (p) { case RecallPhase::IDLE: return "IDLE"; case RecallPhase::LISTING: return "LISTING"; case RecallPhase::DOWNLOADING: return "DOWNLOADING"; } return "UNKNOWN"; }; auto resultStr = [](ActionResult r) -> const char* { switch (r) { case ActionResult::PENDING: return "PENDING"; case ActionResult::FAIL: return "FAIL"; case ActionResult::OK: return "OK"; } return "UNKNOWN"; }; // --- ★新增:事件补招(RecallMonitor) --- os << "\r\x1B[K |-- Recall(Event) (" << ld.recall_list.size() << "):\n"; { size_t idx = 0; for (const auto& r : ld.recall_list) { if (idx++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- [" << (idx-1) << "] " << "status=" << recallStatusStr(r.recall_status) << ", StartTime=" << r.StartTime << ", EndTime=" << r.EndTime << ", STEADY=" << r.STEADY << ", VOLTAGE=" << r.VOLTAGE << "\n"; } if (ld.recall_list.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (ld.recall_list.size() - MAX_ITEMS) << " more)\n"; } } // --- ★新增:稳态补招(RecallFile)+ 状态机信息 --- os << "\r\x1B[K |-- Recall(Static Files) (" << ld.recall_list_static.size() << "):\n"; { size_t idx = 0; for (const auto& rf : ld.recall_list_static) { if (idx++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- [" << (idx-1) << "] " << "status=" << recallStatusStr(rf.recall_status) << ", StartTime=" << rf.StartTime << ", EndTime=" << rf.EndTime << ", STEADY=" << rf.STEADY << ", VOLTAGE=" << rf.VOLTAGE << "\n"; // ★新增:直下模式与目标时间列表 os << "\r\x1B[K |-- direct_mode=" << (rf.direct_mode ? "true" : "false") << ", target_filetimes(" << rf.target_filetimes << ")\n"; { os << "\r\x1B[K |.. " << rf.target_filetimes << "\n"; } // ★新增:状态机运行态 os << "\r\x1B[K |-- phase=" << phaseStr(rf.phase) << ", cur_dir_index=" << rf.cur_dir_index << ", cur_dir=" << rf.cur_dir << "\n"; os << "\r\x1B[K |-- list_result=" << resultStr(rf.list_result) << ", download_result=" << resultStr(rf.download_result) << "\n"; // ★新增:候选目录 os << "\r\x1B[K |-- dir_candidates(" << rf.dir_candidates.size() << ")\n"; { size_t c = 0; for (const auto& d : rf.dir_candidates) { if (c++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- " << d << "\n"; } if (rf.dir_candidates.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (rf.dir_candidates.size() - MAX_ITEMS) << " more)\n"; } } // ★新增:目录 -> 文件名列表(仅概要) os << "\r\x1B[K |-- dir_files(" << rf.dir_files.size() << " dirs)\n"; { size_t c = 0; for (const auto& kv : rf.dir_files) { if (c++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- [" << (c-1) << "] dir=" << kv.first << " files=" << kv.second.size() << "\n"; } if (rf.dir_files.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (rf.dir_files.size() - MAX_ITEMS) << " more)\n"; } } // ★新增:下载队列(概要) os << "\r\x1B[K |-- download_queue(" << rf.download_queue.size() << ")\n"; { size_t c = 0; for (const auto& path : rf.download_queue) { if (c++ >= MAX_ITEMS) break; os << "\r\x1B[K |-- " << path << "\n"; } if (rf.download_queue.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (rf.download_queue.size() - MAX_ITEMS) << " more)\n"; } } // ★新增:当前下载中文件 if (!rf.downloading_file.empty()) { os << "\r\x1B[K |-- downloading: " << rf.downloading_file << "\n"; } } if (ld.recall_list_static.size() > MAX_ITEMS) { os << "\r\x1B[K |.. (+" << (ld.recall_list_static.size() - MAX_ITEMS) << " more)\n"; } } // ======================= ★新增:补招打印结束 ======================= } os << "\r\x1B[K------------------------------------\n"; sendStr(fd, os.str()); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印所有的终端列表 void Worker::ledger(const std::string& terminal_id, int fd) { sendStr(fd, "\r\x1B[Kprint ledger in shell\n"); std::lock_guard lock(ledgermtx); bool found = false; if (terminal_id.empty()) { for (const auto& dev : terminal_devlist) { printLedgerinshell(dev, fd); } } else { for (const auto& dev : terminal_devlist) { if (dev.terminal_id == terminal_id) { printLedgerinshell(dev, fd); found = true; break; } } if (!found) { std::ostringstream msg; msg << "\r\x1B[Kterminal not exist: " << terminal_id << "\n"; sendStr(fd, msg.str()); } } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////打印指定的变量名 void Worker::value_print(const std::string& variableName, int clientFD) { std::string output; { std::lock_guard lock(ledgermtx); std::cout << "value_print hold lock !!!!!!!!!!!" << std::endl; if (variableName == "frontindex") { output = "frontindex = " + std::to_string(g_front_seg_index); } else if (variableName == "iedcount") { output = "ledger list = " + std::to_string(terminal_devlist.size()) + ", ied config count = " + std::to_string(IED_COUNT); } else if (variableName == "frontfun") { output = "frontfun = " + subdir; } else if (variableName == "log") { output = "showinshellflag = " + std::to_string(showinshellflag) + ", normalOutputEnabled = " + std::to_string(normalOutputEnabled) + ", warnOutputEnabled = " + std::to_string(warnOutputEnabled) + ", errorOutputEnabled = " + std::to_string(errorOutputEnabled); } else if (variableName == "init") { output = "INITFLAG = " + std::to_string(INITFLAG); } else { output = "Unknown variable name: " + variableName; } std::cout << "value_print free lock !!!!!!!!!!!" << std::endl; } sendStr(clientFD, "\r\x1B[K" + output + "\r\n"); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////日志开关控制 //日志消息列表 std::list* getLogList(const std::string& level) { if (level == "ERROR") return &errorList; if (level == "WARN") return &warnList; if (level == "NORMAL") return &normalList; return nullptr; } //日志锁 std::mutex* getLogMutex(const std::string& level) { if (level == "ERROR") return &errorListMutex; if (level == "WARN") return &warnListMutex; if (level == "NORMAL") return &normalListMutex; return nullptr; } void Worker::handleViewLogCommand(const std::string& command, int clientFD) { std::istringstream iss(command); std::string cmd, level; iss >> cmd >> level; std::transform(level.begin(), level.end(), level.begin(), ::toupper); if (level.empty()) { sendStr(clientFD, "\r\x1B[KUsage: viewlog [ERROR|WARN|NORMAL]\r\n> "); return; } std::list* logList = getLogList(level); std::mutex* logMutex = getLogMutex(level); if (!logList || !logMutex) { sendStr(clientFD, "\r\x1B[KInvalid log level! Use ERROR, WARN, NORMAL.\r\n> "); return; } stopViewLog = false; showinshellflag = true; sendStr(clientFD, std::string("\r\x1B[KViewing logs for level: ") + level + " (Press '`' to exit)\r\n> "); char inputBuf[16]; // --- 新增 begin: 目录创建 + 唯一文件名生成 + 打开文件 --- // 递归创建目录的小工具(最小实现,按‘/’逐级创建) auto ensure_dir = [](const std::string& path) -> bool { if (path.empty()) return false; std::string cur; cur.reserve(path.size()); for (size_t i = 0; i < path.size(); ++i) { cur.push_back(path[i]); if (path[i] == '/' && cur.size() > 1) { if (::access(cur.c_str(), F_OK) != 0) { if (::mkdir(cur.c_str(), 0755) != 0 && errno != EEXIST) return false; } } } // 末级(若不以 / 结尾) if (cur.back() != '/') { if (::access(cur.c_str(), F_OK) != 0) { if (::mkdir(cur.c_str(), 0755) != 0 && errno != EEXIST) return false; } } return true; }; const std::string logDir = "/FeProject/dat/log"; if (!ensure_dir(logDir)) { sendStr(clientFD, "\r\x1B[KFailed to create log directory: /FeProject/dat/log\r\n> "); return; } std::string filePath = logDir + "/temp.log"; int index = 1; while (::access(filePath.c_str(), F_OK) == 0) { filePath = logDir + "/temp_" + std::to_string(index++) + ".log"; } std::ofstream logFile(filePath.c_str(), std::ios::out | std::ios::trunc); if (!logFile.is_open()) { sendStr(clientFD, "\r\x1B[KFailed to open log file for writing.\r\n> "); return; } // --- 新增 end --- while (!stopViewLog) { // 1) 监听 shell 输入退出符号 ` fd_set read_fds; FD_ZERO(&read_fds); FD_SET(clientFD, &read_fds); timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 500000; // 500ms int activity = select(clientFD + 1, &read_fds, nullptr, nullptr, &timeout); if (activity > 0 && FD_ISSET(clientFD, &read_fds)) { int n = recv(clientFD, inputBuf, sizeof(inputBuf), 0); if (n > 0 && std::memchr(inputBuf, '`', static_cast(n))) { stopViewLog = true; showinshellflag = false; break; } } // --- 修改 begin: 批量获取日志(swap 全取,减少加锁时间) --- std::list tempLogs; { std::lock_guard lock(*logMutex); if (!logList->empty()) { tempLogs.swap(*logList); // 把 logList 中的内容全取出 } } // --- 修改 end --- if (!tempLogs.empty()) { for (const auto& logEntry : tempLogs) { if (!logEntry.empty()) { sendStr(clientFD, std::string("\r\x1B[K") + logEntry + "\r\n"); // --- 新增 begin: 写入文件 + 及时落盘 --- logFile << logEntry << '\n'; // --- 新增 end --- } } // --- 新增 begin: 刷新文件缓冲,保证实时可见 --- logFile.flush(); // --- 新增 end --- } else { std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } // 3) 打印退出提示 sendStr(clientFD, "\r\x1B[K\nLog view stopped. Returning to shell.\r\n> "); // --- 新增 begin: 关闭文件 --- logFile.close(); // --- 新增 end --- }