视频配置完成
This commit is contained in:
parent
78b51534ea
commit
b14e2cba8e
|
|
@ -86,7 +86,6 @@ void poll_system_metrics(boost::asio::steady_timer &timer,
|
|||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// ... (ConfigManager 加载保持不变) ...
|
||||
const std::string config_path = "/app/config/config.json";
|
||||
if (!ConfigManager::getInstance().load(config_path)) {
|
||||
std::cerr << "Failed to load configuration from " << config_path
|
||||
|
|
@ -96,7 +95,6 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
auto &config = ConfigManager::getInstance();
|
||||
|
||||
// ... (spdlog, DataStorage 初始化保持不变) ...
|
||||
try {
|
||||
spdlog::set_level(spdlog::level::from_str(config.getLogLevel()));
|
||||
spdlog::info("Edge Proxy starting up...");
|
||||
|
|
@ -191,6 +189,10 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
WebServer web_server(monitor, device_manager, live_data_cache,
|
||||
alarm_service, config.getWebServerPort());
|
||||
web_server.set_shutdown_handler([&]() {
|
||||
spdlog::warn("Received shutdown command from Web API. Shutting down.");
|
||||
g_io_context.stop(); // <-- 这将触发 signals.async_wait 下方的所有清理逻辑
|
||||
});
|
||||
web_server.start();
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
|
@ -240,7 +242,7 @@ int main(int argc, char *argv[]) {
|
|||
video_manager.stop_all();
|
||||
|
||||
spdlog::info("[Shutdown] F. De-initializing postprocess library...");
|
||||
deinitPostProcess();
|
||||
deinitPostProcess();
|
||||
|
||||
// f. 最后,安全地停止io_context
|
||||
spdlog::info("[Shutdown] G. Stopping main event loop...");
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ bool VideoService::start() {
|
|||
"video/x-raw,format=BGR ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=NV12 ! "
|
||||
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! "
|
||||
"h265parse ! "
|
||||
"mpph264enc gop=25 rc-mode=fixqp qp-init=26 ! "
|
||||
"h264parse ! "
|
||||
"rtspclientsink location=" +
|
||||
output_rtsp_url_ + " latency=0 protocols=tcp";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// 文件名: src/web/web_server.cc
|
||||
#include "web_server.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "config/config_manager.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
WebServer::WebServer(SystemMonitor::SystemMonitor &monitor,
|
||||
|
|
@ -44,7 +44,106 @@ void WebServer::stop() {
|
|||
}
|
||||
}
|
||||
|
||||
// [新增] set_shutdown_handler 的实现
|
||||
void WebServer::set_shutdown_handler(std::function<void()> 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()) {
|
||||
error_message = "Root is not an object.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 验证 'video_service'
|
||||
if (!config.contains("video_service") ||
|
||||
!config["video_service"].is_object()) {
|
||||
error_message = "Missing 'video_service' object.";
|
||||
return false;
|
||||
}
|
||||
if (!config["video_service"].contains("enabled") ||
|
||||
!config["video_service"]["enabled"].is_boolean()) {
|
||||
error_message = "'video_service' must have 'enabled' (boolean).";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 验证 'video_streams'
|
||||
if (!config.contains("video_streams") ||
|
||||
!config["video_streams"].is_array()) {
|
||||
error_message = "Missing 'video_streams' array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 验证 'video_streams' 中的每个元素
|
||||
for (const auto &stream : config["video_streams"]) {
|
||||
if (!stream.is_object()) {
|
||||
error_message = "Item in 'video_streams' is not an object.";
|
||||
return false;
|
||||
}
|
||||
// 检查必需的键
|
||||
for (const char *key : {"id", "input_url", "module_type"}) {
|
||||
if (!stream.contains(key) || !stream[key].is_string() ||
|
||||
stream[key].get<std::string>().empty()) {
|
||||
error_message =
|
||||
"Stream missing or invalid key: '" + std::string(key) + "'.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!stream.contains("enabled") || !stream["enabled"].is_boolean()) {
|
||||
error_message = "Stream missing or invalid key: 'enabled' (boolean).";
|
||||
return false;
|
||||
}
|
||||
if (!stream.contains("module_config") ||
|
||||
!stream["module_config"].is_object()) {
|
||||
error_message = "Stream missing 'module_config' (object).";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 验证 'module_config' 的关键字段
|
||||
const auto &mod_cfg = stream["module_config"];
|
||||
for (const char *key : {"model_path", "label_path"}) {
|
||||
if (!mod_cfg.contains(key) || !mod_cfg[key].is_string() ||
|
||||
mod_cfg[key].get<std::string>().empty()) {
|
||||
error_message =
|
||||
"module_config missing or empty key: '" + std::string(key) + "'.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!mod_cfg.contains("class_num") ||
|
||||
!mod_cfg["class_num"].is_number_integer() ||
|
||||
mod_cfg["class_num"].get<int>() <= 0) {
|
||||
error_message = "module_config missing or invalid 'class_num' (must be "
|
||||
"integer > 0).";
|
||||
return false;
|
||||
}
|
||||
if (!mod_cfg.contains("rknn_thread_num") ||
|
||||
!mod_cfg["rknn_thread_num"].is_number_integer()) {
|
||||
error_message = "module_config missing 'rknn_thread_num' (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();
|
||||
crow::json::wvalue response;
|
||||
|
|
@ -101,11 +200,8 @@ void WebServer::setup_routes() {
|
|||
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;
|
||||
});
|
||||
|
||||
|
|
@ -258,7 +354,7 @@ void WebServer::setup_routes() {
|
|||
}
|
||||
});
|
||||
|
||||
// --- [!! 新增路由: 设备配置管理 !!] ---
|
||||
// --- [!! 原始路由: 设备配置管理 !!] ---
|
||||
|
||||
/**
|
||||
* @brief GET /api/devices/config
|
||||
|
|
@ -334,4 +430,119 @@ void WebServer::setup_routes() {
|
|||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// [!! 新增: 视频配置路由 (完整版) !!]
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief GET /api/video_config
|
||||
* 获取 video_config.json 的原始 JSON 字符串
|
||||
*/
|
||||
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: {}",
|
||||
e.what());
|
||||
auto res = crow::response(500, "{\"error\":\"Server configuration error: "
|
||||
"cannot determine config path.\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
std::ifstream ifs(config_path);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::error("Failed to open video config file for reading: {}",
|
||||
config_path);
|
||||
auto res =
|
||||
crow::response(404, "{\"error\":\"Video config file not found.\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
// 读取文件全部内容
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
|
||||
auto res = crow::response(200, content);
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
});
|
||||
|
||||
/**
|
||||
* @brief POST /api/video_config
|
||||
* 验证并保存 video_config.json
|
||||
*/
|
||||
CROW_ROUTE((*this), "/api/video_config")
|
||||
.methods("POST"_method)([this](const crow::request &req) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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: {}",
|
||||
config_path);
|
||||
auto res = crow::response(
|
||||
500, "{\"error\":\"Failed to write config file on server.\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
// 我们将 nlohmann::json 解析后的内容重新 dump 写入
|
||||
// 这样可以确保写入的 JSON 格式是规范的 (例如,格式化)
|
||||
auto json_data = nlohmann::json::parse(new_config_content);
|
||||
ofs << json_data.dump(4); // 格式化写入
|
||||
ofs.close();
|
||||
|
||||
// 4. 成功响应
|
||||
spdlog::info("Video config successfully updated via API at {}",
|
||||
config_path);
|
||||
crow::json::wvalue response_json;
|
||||
response_json["status"] = "success";
|
||||
response_json["message"] =
|
||||
"Video config saved. Send POST to /api/service/reload to apply.";
|
||||
auto res = crow::response(200, response_json.dump());
|
||||
res.set_header("Content-Type", "application/json");
|
||||
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...");
|
||||
m_shutdown_handler();
|
||||
|
||||
auto res =
|
||||
crow::response(202, "{\"status\":\"restarting\", "
|
||||
"\"message\":\"Service is restarting...\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
}
|
||||
|
||||
spdlog::error("Web API: /api/service/reload called, but shutdown handler "
|
||||
"is not set!");
|
||||
auto res = crow::response(
|
||||
500, "{\"error\":\"Shutdown handler not configured on server.\"}");
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
// 文件名: src/web/web_server.h
|
||||
#ifndef WEB_SERVER_H
|
||||
#define WEB_SERVER_H
|
||||
#pragma once
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/cors.h"
|
||||
#include "crow/middlewares/cors.h"
|
||||
|
||||
#include "systemMonitor/system_monitor.h"
|
||||
#include "deviceManager/device_manager.h"
|
||||
#include "dataCache/live_data_cache.h"
|
||||
#include "alarm/alarm_service.h"
|
||||
#include "dataCache/live_data_cache.h"
|
||||
#include "deviceManager/device_manager.h"
|
||||
#include "systemMonitor/system_monitor.h"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
|
||||
class WebServer : public crow::Crow<crow::CORSHandler> {
|
||||
|
||||
public:
|
||||
WebServer(SystemMonitor::SystemMonitor& monitor,
|
||||
DeviceManager& deviceManager,
|
||||
LiveDataCache& liveDataCache,
|
||||
AlarmService& alarm_service,
|
||||
uint16_t port = 8080
|
||||
);
|
||||
~WebServer();
|
||||
WebServer(SystemMonitor::SystemMonitor &monitor, DeviceManager &deviceManager,
|
||||
LiveDataCache &liveDataCache, AlarmService &alarm_service,
|
||||
uint16_t port = 8080);
|
||||
~WebServer();
|
||||
|
||||
WebServer(const WebServer&) = delete;
|
||||
WebServer& operator=(const WebServer&) = delete;
|
||||
WebServer(const WebServer &) = delete;
|
||||
WebServer &operator=(const WebServer &) = delete;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void set_shutdown_handler(std::function<void()> handler);
|
||||
|
||||
private:
|
||||
void setup_routes();
|
||||
void setup_routes();
|
||||
|
||||
SystemMonitor::SystemMonitor& m_monitor;
|
||||
DeviceManager& m_device_manager;
|
||||
LiveDataCache& m_live_data_cache;
|
||||
AlarmService& m_alarm_service;
|
||||
|
||||
bool validate_video_config(const std::string &json_string,
|
||||
std::string &error_message);
|
||||
|
||||
uint16_t m_port;
|
||||
|
||||
std::thread m_thread;
|
||||
SystemMonitor::SystemMonitor &m_monitor;
|
||||
DeviceManager &m_device_manager;
|
||||
LiveDataCache &m_live_data_cache;
|
||||
AlarmService &m_alarm_service;
|
||||
|
||||
uint16_t m_port;
|
||||
|
||||
std::thread m_thread;
|
||||
|
||||
std::function<void()> m_shutdown_handler;
|
||||
};
|
||||
|
||||
#endif // WEB_SERVER_H
|
||||
Loading…
Reference in New Issue