// 文件名: src/web/web_server.cc #include "web_server.h" #include #include #include "config/config_manager.h" #include "spdlog/spdlog.h" WebServer::WebServer(SystemMonitor::SystemMonitor &monitor, DeviceManager &deviceManager, LiveDataCache &liveDataCache, AlarmService &alarm_service, uint16_t port) : crow::Crow(), m_monitor(monitor), m_device_manager(deviceManager), m_live_data_cache(liveDataCache), m_alarm_service(alarm_service), m_port(port) { auto &cors = this->get_middleware(); cors.global() .origin("*") .headers("Content-Type", "Authorization") .methods("GET"_method, "POST"_method, "OPTIONS"_method); this->loglevel(crow::LogLevel::Warning); setup_routes(); } WebServer::~WebServer() { stop(); } void WebServer::start() { if (m_thread.joinable()) { spdlog::warn("Web server is already running."); return; } m_thread = std::thread([this]() { spdlog::info("Starting Web server on port {}", m_port); this->bindaddr("0.0.0.0").port(m_port).run(); spdlog::info("Web server has stopped."); }); } void WebServer::stop() { crow::Crow::stop(); if (m_thread.joinable()) { m_thread.join(); } } void WebServer::setup_routes() { CROW_ROUTE((*this), "/api/system/id").methods("GET"_method)([this] { auto deviceID = ConfigManager::getInstance().getDeviceID(); crow::json::wvalue response; response["deviceID"] = deviceID; return response; }); CROW_ROUTE((*this), "/api/system/status").methods("GET"_method)([this] { auto cpu_util = m_monitor.getCpuUtilization(); auto mem_info = m_monitor.getMemoryInfo(); crow::json::wvalue response; response["cpu_usage_percentage"] = cpu_util.totalUsagePercentage; response["memory_total_kb"] = mem_info.total_kb; response["memory_free_kb"] = mem_info.available_kb; response["memory_usage_percentage"] = (mem_info.total_kb > 0) ? (1.0 - static_cast(mem_info.available_kb) / mem_info.total_kb) * 100.0 : 0.0; return response; }); CROW_ROUTE((*this), "/api/devices").methods("GET"_method)([this] { auto devices_info = m_device_manager.get_all_device_info(); std::vector devices_json; for (const auto &info : devices_info) { crow::json::wvalue device_obj; device_obj["id"] = info.id; device_obj["type"] = info.type; device_obj["is_running"] = info.is_running; crow::json::wvalue details_obj; for (const auto &pair : info.connection_details) { details_obj[pair.first] = pair.second; } device_obj["connection_details"] = std::move(details_obj); devices_json.push_back(std::move(device_obj)); } auto res = crow::response(crow::json::wvalue(devices_json)); res.set_header("Content-Type", "application/json"); return res; }); CROW_ROUTE((*this), "/api/data/latest").methods("GET"_method)([this] { auto latest_data_map = m_live_data_cache.get_all_data(); crow::json::wvalue response; for (const auto &pair : latest_data_map) { response[pair.first] = crow::json::load(pair.second); } // 1. 将 JSON 对象转换为 crow::response auto res = crow::response(response); // 2. 明确设置 Content-Type 头部 res.set_header("Content-Type", "application/json"); // 3. 返回 response 对象 return res; }); CROW_ROUTE((*this), "/api/alarms/active").methods("GET"_method)([this] { try { auto json_string = m_alarm_service.getActiveAlarmsJson().dump(); auto res = crow::response(200, json_string); res.set_header("Content-Type", "application/json"); return res; } catch (const std::exception &e) { spdlog::error("Error processing /api/alarms/active: {}", e.what()); crow::json::wvalue error_resp; error_resp["error"] = "Failed to retrieve active alarms."; auto res = crow::response(500, error_resp.dump()); res.set_header("Content-Type", "application/json"); return res; } }); CROW_ROUTE((*this), "/api/alarms/history") .methods("GET"_method)([this](const crow::request &req) { int limit = 100; if (req.url_params.get("limit")) { try { limit = std::stoi(req.url_params.get("limit")); } catch (const std::exception &) { /* ignore invalid */ } } if (limit <= 0) limit = 100; try { auto json_string = m_alarm_service.getAlarmHistoryJson(limit).dump(); auto res = crow::response(200, json_string); res.set_header("Content-Type", "application/json"); return res; } catch (const std::exception &e) { spdlog::error("Error processing /api/alarms/history: {}", e.what()); crow::json::wvalue error_resp; error_resp["error"] = "Failed to retrieve alarm history."; auto res = crow::response(500, error_resp.dump()); res.set_header("Content-Type", "application/json"); return res; } }); CROW_ROUTE((*this), "/api/alarms/reload").methods("POST"_method)([this]() { spdlog::info("Web API: Received request to reload alarm rules..."); bool success = m_alarm_service.reload_rules(); if (success) { crow::json::wvalue response_json; response_json["status"] = "success"; response_json["message"] = "Alarm rules reloaded successfully."; auto res = crow::response(200, response_json.dump()); res.set_header("Content-Type", "application/json"); return res; } else { crow::json::wvalue error_json; error_json["status"] = "error"; error_json["message"] = "Failed to reload alarm rules. Check service logs for details."; auto res = crow::response(500, error_json.dump()); res.set_header("Content-Type", "application/json"); return res; } }); CROW_ROUTE((*this), "/api/alarms/clear") .methods("POST"_method)([this](const crow::request &req) { crow::json::rvalue j_body; try { j_body = crow::json::load(req.body); } catch (const std::exception &e) { spdlog::warn("Failed to parse request body for /api/alarms/clear: {}", e.what()); auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}"); res.set_header("Content-Type", "application/json"); return res; } if (!j_body.has("rule_id") || !j_body.has("device_id")) { auto res = crow::response( 400, "{\"error\":\"Missing 'rule_id' or 'device_id'.\"}"); res.set_header("Content-Type", "application/json"); return res; } std::string rule_id = j_body["rule_id"].s(); std::string device_id = j_body["device_id"].s(); bool success = m_alarm_service.manually_clear_alarm(rule_id, device_id); if (success) { auto res = crow::response( 200, "{\"status\":\"success\", \"message\":\"Alarm cleared.\"}"); res.set_header("Content-Type", "application/json"); return res; } else { auto res = crow::response( 404, "{\"status\":\"error\", \"message\":\"Alarm not " "found or not active.\"}"); res.set_header("Content-Type", "application/json"); return res; } }); CROW_ROUTE((*this), "/api/alarms/config").methods("GET"_method)([this]() { std::string rules_json_string = m_alarm_service.get_rules_as_json_string(); auto res = crow::response(200, rules_json_string); res.set_header("Content-Type", "application/json"); return res; }); CROW_ROUTE((*this), "/api/alarms/config") .methods("POST"_method)([this](const crow::request &req) { const std::string &new_rules_content = req.body; bool success = m_alarm_service.save_rules_from_json_string(new_rules_content); if (success) { crow::json::wvalue response_json; response_json["status"] = "success"; response_json["message"] = "Rules saved successfully. A reload is required to apply."; auto res = crow::response(200, response_json.dump()); res.set_header("Content-Type", "application/json"); return res; } else { crow::json::wvalue error_json; error_json["status"] = "error"; error_json["message"] = "Failed to save rules. Invalid JSON format or server error."; auto res = crow::response(400, error_json.dump()); res.set_header("Content-Type", "application/json"); return res; } }); // --- [!! 新增路由: 设备配置管理 !!] --- /** * @brief GET /api/devices/config * 获取设备配置 (devices.json) 的原始 JSON 字符串 */ CROW_ROUTE((*this), "/api/devices/config").methods("GET"_method)([this]() { std::string rules_json_string = m_device_manager.get_config_as_json_string(); auto res = crow::response(200, rules_json_string); res.set_header("Content-Type", "application/json"); return res; }); /** * @brief POST /api/devices/config * 保存设备配置 (devices.json) 的原始 JSON 字符串 */ CROW_ROUTE((*this), "/api/devices/config") .methods("POST"_method)([this](const crow::request &req) { const std::string &new_rules_content = req.body; // save_config_from_json_string 内部包含 JSON 格式和 Schema 校验 bool success = m_device_manager.save_config_from_json_string(new_rules_content); if (success) { crow::json::wvalue response_json; response_json["status"] = "success"; response_json["message"] = "Device config saved successfully. A " "reload is required to apply."; auto res = crow::response(200, response_json.dump()); res.set_header("Content-Type", "application/json"); return res; } else { crow::json::wvalue error_json; error_json["status"] = "error"; error_json["message"] = "Failed to save rules. Invalid JSON format " "or schema. Check service logs."; auto res = crow::response(400, error_json.dump()); res.set_header("Content-Type", "application/json"); return res; } }); /** * @brief POST /api/devices/reload * 通知后端从磁盘重载 devices.json 并应用 */ CROW_ROUTE((*this), "/api/devices/reload").methods("POST"_method)([this]() { spdlog::info("Web API: Received request to reload device rules..."); bool success = m_device_manager.reload_config_from_file(); if (success) { crow::json::wvalue response_json; response_json["status"] = "success"; response_json["message"] = "Device rules reload posted successfully."; auto res = crow::response(200, response_json.dump()); res.set_header("Content-Type", "application/json"); return res; } else { crow::json::wvalue error_json; error_json["status"] = "error"; error_json["message"] = "Failed to post device rules reload. Check service logs."; auto res = crow::response(500, error_json.dump()); res.set_header("Content-Type", "application/json"); return res; } }); }