kunlang_config_server/src/config_server.cpp
2025-12-12 09:02:06 +08:00

257 lines
7.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}