diff --git a/config/video_config.json b/config/video_config.json index 7f8334e..924bd13 100644 --- a/config/video_config.json +++ b/config/video_config.json @@ -7,41 +7,41 @@ "enabled": true, "id": "cam_01_intrusion", "input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301", - "module_type": "intrusion_detection", - "output_rtsp": "rtsp://127.0.0.1:8554/ch1301", "module_config": { - "model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn", - "rknn_thread_num": 3, + "class_num": 80, "intrusion_zone": [ 100, 100, - 300, - 300 + 1820, + 1820 ], - "time_threshold_sec": 3.0, - "class_num": 80, - "label_path": "/app/edge-proxy/models/coco_80_labels_list.txt" - } + "label_path": "/app/edge-proxy/models/coco_80_labels_list.txt", + "model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn", + "rknn_thread_num": 3, + "time_threshold_sec": 3 + }, + "module_type": "intrusion_detection", + "output_rtsp": "rtsp://127.0.0.1:8554/ch1301" }, { "enabled": true, "id": "cam_03_intrusion", "input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1501", - "module_type": "intrusion_detection", - "output_rtsp": "rtsp://127.0.0.1:8554/ch1501", "module_config": { - "model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn", - "rknn_thread_num": 3, + "class_num": 80, "intrusion_zone": [ 100, 100, 300, 300 ], - "time_threshold_sec": 3.0, - "class_num": 80, - "label_path": "/app/edge-proxy/models/coco_80_labels_list.txt" - } + "label_path": "/app/edge-proxy/models/coco_80_labels_list.txt", + "model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn", + "rknn_thread_num": 3, + "time_threshold_sec": 3 + }, + "module_type": "intrusion_detection", + "output_rtsp": "rtsp://127.0.0.1:8554/ch1501" } ] } \ No newline at end of file diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc index 95cc77d..60f5674 100644 --- a/src/config/config_manager.cc +++ b/src/config/config_manager.cc @@ -140,56 +140,7 @@ std::string ConfigManager::getVideoConfigPath() { get("video_config_path", "video_config.json"); } -// bool ConfigManager::getIsVideoServiceEnabled() const { -// std::shared_lock lock(m_mutex); -// try { -// if (m_config_json.contains("video_service")) { -// return m_config_json["video_service"].value("enabled", false); -// } -// } catch (const json::type_error &e) { -// spdlog::warn( -// "Config type mismatch for key 'video_service.enabled'. Error: {}", -// e.what()); -// } -// return false; -// } - -// std::vector -// ConfigManager::getVideoStreamConfigs() const { -// std::vector configs; - -// std::shared_lock lock(m_mutex); - -// try { -// if (m_config_json.contains("video_streams") && -// m_config_json["video_streams"].is_array()) { -// for (const auto &stream_json : m_config_json["video_streams"]) { -// VideoStreamConfig cfg; -// cfg.id = stream_json.value("id", ""); -// cfg.enabled = stream_json.value("enabled", false); -// cfg.input_url = stream_json.value("input_url", ""); -// cfg.output_rtsp = stream_json.value("output_rtsp", ""); - -// cfg.module_type = stream_json.value("module_type", ""); -// cfg.module_config = stream_json.value("module_config", -// json::object()); - -// if (cfg.module_type.empty()) { -// spdlog::warn("Video stream '{}' has no 'module_type' defined. It -// may " -// "fail to start.", -// cfg.id); -// } - -// configs.push_back(cfg); -// } -// } else { -// spdlog::warn("Config key 'video_streams' not found or is not an -// array."); -// } -// } catch (const json::exception &e) { -// spdlog::error("Error parsing 'video_streams': {}", e.what()); -// } - -// return configs; -// } \ No newline at end of file +std::string ConfigManager::getConfigFilePath() const { + std::shared_lock lock(m_mutex); + return m_configFilePath; +} \ No newline at end of file diff --git a/src/config/config_manager.h b/src/config/config_manager.h index e5e49af..3c1d4e9 100644 --- a/src/config/config_manager.h +++ b/src/config/config_manager.h @@ -76,6 +76,7 @@ public: // bool getIsVideoServiceEnabled() const; // std::vector getVideoStreamConfigs() const; std::string getVideoConfigPath(); + std::string getConfigFilePath() const; private: ConfigManager() = default; diff --git a/src/web/web_server.cc b/src/web/web_server.cc index 63f18db..d048773 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -44,16 +44,13 @@ void WebServer::stop() { } } -// [新增] set_shutdown_handler 的实现 void WebServer::set_shutdown_handler(std::function handler) { m_shutdown_handler = handler; } -// [新增] validate_video_config 的实现 bool WebServer::validate_video_config(const std::string &json_string, std::string &error_message) { try { - // 我们使用 nlohmann::json 来解析和验证 auto config = nlohmann::json::parse(json_string); if (!config.is_object()) { @@ -140,9 +137,62 @@ bool WebServer::validate_video_config(const std::string &json_string, } } -void WebServer::setup_routes() { +bool WebServer::validate_main_config(const std::string &json_string, + std::string &error_message) { + try { + auto config = nlohmann::json::parse(json_string); - // --- 原始路由 (未省略) --- + if (!config.is_object()) { + error_message = "Root is not an object."; + return false; + } + + // 1. 必需的字符串类型 + for (const char *key : + {"device_id", "config_base_path", "mqtt_broker", + "mqtt_client_id_prefix", "data_storage_db_path", "data_cache_db_path", + "log_level", "alarm_rules_path", "piper_executable_path", + "piper_model_path", "video_config_path"}) { + if (!config.contains(key) || !config[key].is_string() || + config[key].get().empty()) { + error_message = + "Missing or invalid/empty string key: '" + std::string(key) + "'."; + return false; + } + } + + // 2. 必需的整数类型 + if (!config.contains("web_server_port") || + !config["web_server_port"].is_number_integer()) { + error_message = "Missing or invalid 'web_server_port' (must be integer)."; + return false; + } + + // 3. 必需的数组类型 + if (!config.contains("tcp_server_ports") || + !config["tcp_server_ports"].is_array()) { + error_message = "Missing or invalid 'tcp_server_ports' (must be array)."; + return false; + } + for (const auto &port : config["tcp_server_ports"]) { + if (!port.is_number_integer()) { + error_message = "Item in 'tcp_server_ports' is not an integer."; + return false; + } + } + + return true; // 所有检查通过 + + } catch (const nlohmann::json::parse_error &e) { + error_message = "Invalid JSON: " + std::string(e.what()); + return false; + } catch (const std::exception &e) { + error_message = "Validation error: " + std::string(e.what()); + return false; + } +} + +void WebServer::setup_routes() { CROW_ROUTE((*this), "/api/system/id").methods("GET"_method)([this] { auto deviceID = ConfigManager::getInstance().getDeviceID(); @@ -354,8 +404,6 @@ void WebServer::setup_routes() { } }); - // --- [!! 原始路由: 设备配置管理 !!] --- - /** * @brief GET /api/devices/config * 获取设备配置 (devices.json) 的原始 JSON 字符串 @@ -431,10 +479,6 @@ void WebServer::setup_routes() { } }); - // ----------------------------------------------------------------- - // [!! 新增: 视频配置路由 (完整版) !!] - // ----------------------------------------------------------------- - /** * @brief GET /api/video_config * 获取 video_config.json 的原始 JSON 字符串 @@ -442,7 +486,7 @@ void WebServer::setup_routes() { CROW_ROUTE((*this), "/api/video_config").methods("GET"_method)([this]() { std::string config_path; try { - // 从 ConfigManager 获取 video_config.json 的路径 + config_path = ConfigManager::getInstance().getVideoConfigPath(); } catch (const std::exception &e) { spdlog::error("Failed to get video config path from ConfigManager: {}", @@ -463,7 +507,6 @@ void WebServer::setup_routes() { return res; } - // 读取文件全部内容 std::string content((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); @@ -481,7 +524,6 @@ void WebServer::setup_routes() { const std::string &new_config_content = req.body; std::string error_msg; - // 1. [!! 验证 !!] if (!validate_video_config(new_config_content, error_msg)) { spdlog::warn("Web API: Failed to save video_config: {}", error_msg); crow::json::wvalue error_json; @@ -492,11 +534,9 @@ void WebServer::setup_routes() { return res; } - // 2. 获取路径 std::string config_path = ConfigManager::getInstance().getVideoConfigPath(); - // 3. 写入文件 std::ofstream ofs(config_path); if (!ofs.is_open()) { spdlog::error("Failed to open video config file for writing: {}", @@ -507,8 +547,6 @@ void WebServer::setup_routes() { return res; } - // 我们将 nlohmann::json 解析后的内容重新 dump 写入 - // 这样可以确保写入的 JSON 格式是规范的 (例如,格式化) auto json_data = nlohmann::json::parse(new_config_content); ofs << json_data.dump(4); // 格式化写入 ofs.close(); @@ -525,7 +563,6 @@ void WebServer::setup_routes() { return res; }); - CROW_ROUTE((*this), "/api/service/reload").methods("POST"_method)([this]() { if (m_shutdown_handler) { spdlog::info("Web API: Received request to reload service..."); @@ -545,4 +582,109 @@ void WebServer::setup_routes() { res.set_header("Content-Type", "application/json"); return res; }); + + + /** + * @brief GET /api/config + * 获取主 config.json 的原始 JSON 字符串 + */ + CROW_ROUTE((*this), "/api/config").methods("GET"_method)([this]() { + std::string config_path; + try { + // 1. 从 ConfigManager 获取路径 + config_path = ConfigManager::getInstance().getConfigFilePath(); + if (config_path.empty()) { + throw std::runtime_error("ConfigManager returned an empty path."); + } + } catch (const std::exception &e) { + spdlog::error("Failed to get main config path from ConfigManager: {}", + e.what()); + auto res = crow::response(500, "{\"error\":\"Server configuration error: " + "cannot determine config path.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + // 2. 读取文件 + std::ifstream ifs(config_path); + if (!ifs.is_open()) { + spdlog::error("Failed to open main config file for reading: {}", + config_path); + auto res = + crow::response(404, "{\"error\":\"Main config file not found.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + // 3. 返回内容 + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + + auto res = crow::response(200, content); + res.set_header("Content-Type", "application/json"); + return res; + }); + + /** + * @brief POST /api/config + * 验证并保存主 config.json + */ + CROW_ROUTE((*this), "/api/config") + .methods("POST"_method)([this](const crow::request &req) { + const std::string &new_config_content = req.body; + std::string error_msg; + + if (!validate_main_config(new_config_content, error_msg)) { + spdlog::warn("Web API: Failed to save main config: {}", error_msg); + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = "Invalid JSON format or schema: " + error_msg; + auto res = crow::response(400, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string config_path = + ConfigManager::getInstance().getConfigFilePath(); + if (config_path.empty()) { + auto res = crow::response( + 500, "{\"error\":\"Failed to get config file path on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::ofstream ofs(config_path); + if (!ofs.is_open()) { + spdlog::error("Failed to open main config file for writing: {}", + config_path); + auto res = crow::response( + 500, "{\"error\":\"Failed to write config file on server.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + try { + auto json_data = nlohmann::json::parse(new_config_content); + ofs << json_data.dump(4); + ofs.close(); + } catch (const std::exception &e) { + spdlog::error("Failed to re-parse and dump main config: {}", + e.what()); + auto res = crow::response( + 500, "{\"error\":\"Failed to serialize config for writing.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + spdlog::info("Main config successfully updated via API at {}", + config_path); + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = + "Main config saved. A full service restart (e.g., via " + "/api/service/reload) is required to apply changes."; + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + }); } \ No newline at end of file diff --git a/src/web/web_server.h b/src/web/web_server.h index 87e666c..fe4baf6 100644 --- a/src/web/web_server.h +++ b/src/web/web_server.h @@ -34,6 +34,9 @@ private: bool validate_video_config(const std::string &json_string, std::string &error_message); + bool validate_main_config(const std::string &json_string, + std::string &error_message); + SystemMonitor::SystemMonitor &m_monitor; DeviceManager &m_device_manager; LiveDataCache &m_live_data_cache;