257 lines
7.0 KiB
C++
257 lines
7.0 KiB
C++
|
|
#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;
|
|||
|
|
}
|