#include #include #include #include #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 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(); } else if (value.is_string()) { out[section][key] = std::stoi(value.get()); } else { return "字段 [" + title + "] 必须为整数"; } } else if (type == "bool") { if (value.is_boolean()) { out[section][key] = value.get(); } else if (value.is_string()) { std::string v = value.get(); 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(); } } 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; }