kunlang_config_server/src/config_server.cpp

257 lines
7.0 KiB
C++
Raw Normal View History

2025-12-12 09:02:06 +08:00
#include <iostream>
#include <fstream>
#include <sstream>
#include <optional>
#include "httplib.h"
#include "json.hpp"
using ordered_json = nlohmann::ordered_json; // 关键:保留 JSON 字段顺序
ordered_json schema;
ordered_json config;
// ---------------------------
// 读取文件为字符串
// ---------------------------
std::string read_file(const std::string &path)
{
std::ifstream ifs(path);
if (!ifs.is_open())
return "";
std::stringstream ss;
ss << ifs.rdbuf();
return ss.str();
}
// ---------------------------
// 写文件
// ---------------------------
void write_file(const std::string &path, const std::string &data)
{
std::ofstream ofs(path);
ofs << data;
}
// ---------------------------
// 加载 schema.json
// ---------------------------
void load_schema()
{
std::string txt = read_file("config_schema.json");
if (txt.empty())
{
std::cerr << "ERROR: config_schema.json not found!\n";
exit(1);
}
schema = ordered_json::parse(txt);
}
// ---------------------------
// 加载 config.json如果不存在则创建
// ---------------------------
void load_or_create_config()
{
std::string txt = read_file("config.json");
if (txt.empty())
{
std::cout << "config.json not found, creating default.\n";
// 自动根据 schema 生成一份初始配置
for (auto &[section, items] : schema.items())
{
for (auto &[key, meta] : items.items())
{
config[section][key] = meta["default"];
}
}
write_file("config.json", config.dump(4));
return;
}
try
{
config = ordered_json::parse(txt);
}
catch (...)
{
std::cerr << "config.json corrupted, recreating.\n";
config.clear();
}
// 自动修复(补全缺失字段)
for (auto &[section, items] : schema.items())
{
for (auto &[key, meta] : items.items())
{
if (!config[section].contains(key))
{
config[section][key] = meta["default"];
}
}
}
write_file("config.json", config.dump(4));
}
// ---------------------------
// 保存配置(含自动类型校验)
// ---------------------------
std::optional<std::string> validate_config(
const ordered_json &incoming, ordered_json &out)
{
for (auto &[section, items] : schema.items())
{
if (!incoming.contains(section))
return "缺少配置段:" + section;
out[section] = ordered_json::object();
for (auto &[key, meta] : items.items())
{
if (!incoming[section].contains(key))
return "字段 [" + meta.value("title", key) + "] 缺失";
auto &value = incoming[section][key];
if (value.is_null())
return "字段 [" + meta.value("title", key) + "] 不能为空";
std::string type = meta["type"];
std::string title = meta.value("title", key);
try
{
if (type == "int")
{
if (value.is_number_integer())
{
out[section][key] = value.get<int>();
}
else if (value.is_string())
{
out[section][key] = std::stoi(value.get<std::string>());
}
else
{
return "字段 [" + title + "] 必须为整数";
}
}
else if (type == "bool")
{
if (value.is_boolean())
{
out[section][key] = value.get<bool>();
}
else if (value.is_string())
{
std::string v = value.get<std::string>();
if (v == "1" || v == "true")
out[section][key] = true;
else if (v == "0" || v == "false")
out[section][key] = false;
else
return "字段 [" + title + "] 必须为布尔值";
}
else
{
return "字段 [" + title + "] 必须为布尔值";
}
}
else
{ // string
if (!value.is_string())
return "字段 [" + title + "] 必须为字符串";
out[section][key] = value.get<std::string>();
}
}
catch (...)
{
return "字段 [" + title + "] 格式无效";
}
}
}
return std::nullopt;
}
int main()
{
load_schema();
load_or_create_config();
httplib::Server svr;
// 返回页面 HTML
svr.Get("/config", [](const httplib::Request &, httplib::Response &res)
{ res.set_content(read_file("config.html"), "text/html"); });
// 返回 schema + config
svr.Get("/config/data", [](const httplib::Request &, httplib::Response &res)
{
// ---- 动态加载 schema.json (热更新)----
ordered_json fresh_schema;
try {
fresh_schema = ordered_json::parse(read_file("config_schema.json"));
} catch(...) {
// schema 文件损坏或读取失败
fresh_schema = schema; // fallback 用旧 schema
}
// ---- 自动修复 config补齐 schema 里新增的字段 ----
for (auto &[section, items] : fresh_schema.items())
{
if (!config.contains(section))
config[section] = ordered_json::object();
for (auto &[key, meta] : items.items())
{
if (!config[section].contains(key))
config[section][key] = meta["default"];
}
}
// ---- 覆盖全局 schema ----
schema = fresh_schema;
// ---- 返回 schema + config ----
ordered_json out;
out["schema"] = schema;
out["config"] = config;
res.set_content(out.dump(), "application/json");
});
// 保存配置
svr.Post("/config/save", [](const httplib::Request &req, httplib::Response &res)
{
ordered_json incoming = ordered_json::parse(req.body);
ordered_json validated;
auto err = validate_config(incoming, validated);
if (err.has_value()) {
ordered_json r;
r["status"] = "error";
r["message"] = err.value();
res.set_content(r.dump(), "application/json");
return;
}
config = validated;
write_file("config.json", config.dump(4));
// ★★★ 写入 mDNS 更新请求 ★★★
std::string new_devno = config["vehicle"]["device_no"];
write_file("/run/update_mdns_request", new_devno);
res.set_content("{\"status\":\"ok\"}", "application/json"); });
std::cout << "Config server running at http://0.0.0.0:18080/config\n";
svr.listen("0.0.0.0", 18080);
return 0;
}