视频配置完成

This commit is contained in:
GuanYuankai 2025-11-04 11:46:01 +00:00
parent 78b51534ea
commit b14e2cba8e
4 changed files with 253 additions and 40 deletions

View File

@ -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...");

View File

@ -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";

View File

@ -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;
});
}

View File

@ -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