数据库不存在则创建

This commit is contained in:
2026-08-10 14:51:03 +08:00
parent c268ae23c4
commit 18e92d3631
5 changed files with 153 additions and 50 deletions
+3
View File
@@ -2,3 +2,6 @@
/data/
/.idea
/third_party/Adminive/verification/
/third_party/eacp_webapp/.playwright-cli/
/.playwright-cli/
/output/
+143 -48
View File
@@ -333,6 +333,7 @@ Data_Topology_View_Config read_topology_view(Json input, Global* global) {
};
Data_Topology_View_Config result;
result.initialized = true;
result.layout_revision = input.value("layout_revision", 0U);
result.zoom = finite_number("zoom");
result.pan_x = finite_number("pan_x");
result.pan_y = finite_number("pan_y");
@@ -637,6 +638,25 @@ Json data_topology_view(Global* global) {
result["protocol_version"] = 1;
return result;
}
Json data_topology_connection_status(Global* global, std::uint64_t id) {
const auto feed = item_by_id(global->mode_acs.data_feed_config.map.list(), id);
const auto source = global->mode_acs.data_source_config.map.get(feed->source_key);
Json result{{"连接", feed->source_key + "" + feed->key}, {"数据馈送状态", Psc::to_string(feed->state)}, {"数据馈送启用", feed->enabled()}};
if(!source) {
result["连接状态"] = "数据源不存在";
result["已在数据源注册"] = false;
return result;
}
result["数据源状态"] = Psc::to_string(source.value()->state);
result["数据源启用"] = source.value()->enabled();
const auto all_status = adminive::parse_json<Json>(source.value()->get_all_connect_feed_status().to_json_string());
const auto status = all_status.find(feed->key);
result["已在数据源注册"] = status != all_status.end();
result["连接状态"] = status != all_status.end() && source.value()->enabled() && feed->enabled() ? "运行中" : "未运行";
if(status != all_status.end())
result["缓存统计"] = *status;
return result;
}
Json data_topology(Global* global) {
const auto sources = global->mode_acs.data_source_config.map.list();
const auto feeds = global->mode_acs.data_feed_config.map.list();
@@ -647,21 +667,65 @@ Json data_topology(Global* global) {
const auto& source = sources[index];
const auto id = "data_source:" + source->key;
source_by_key.emplace(source->key, source);
auto config = adminive::to_frontend_json<Json>(source_row(source));
config["id"] = index + 1;
Json details = Json::array();
details.push_back(Json{{"label", "运行状态"}, {"value", Psc::to_string(source->state)}});
if(const auto value = dynamic_cast<TCP_Client_Data_Source*>(source.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "目标"}, {"value", config.ip + ":" + std::to_string(config.port)}}); });
} else if(const auto value = dynamic_cast<Serial_Data_Source*>(source.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "串口"}, {"value", config.port_name}}); });
} else if(const auto value = dynamic_cast<File_Data_Source*>(source.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "文件"}, {"value", config.file_path}}); });
} else if(const auto value = dynamic_cast<Dll_Data_Source*>(source.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "动态库"}, {"value", config.library_path}}); });
} else if(const auto value = dynamic_cast<Shared_Memory_Data_Source*>(source.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "共享内存"}, {"value", config.shared_memory_name}}); });
}
nodes.push_back(Json{{"id", id}, {"kind", "data_source"}, {"entity_id", index + 1},
{"key", source->key}, {"label", source->key}, {"type", source->type},
{"enabled", source->enabled()}, {"status_api", data_topology_path + "/status/data_source/" + std::to_string(index + 1)}});
{"key", source->key}, {"label", source->key}, {"type", source->type},
{"enabled", source->enabled()}, {"config", std::move(config)}, {"details", std::move(details)}, {"status_api", data_topology_path + "/status/data_source/" + std::to_string(index + 1)}});
}
for(std::size_t index = 0; index < feeds.size(); ++index) {
const auto& feed = feeds[index];
const auto id = "data_feed:" + feed->key;
auto config = adminive::to_frontend_json<Json>(feed_row(feed));
config["id"] = index + 1;
Json details = Json::array({Json{{"label", "运行状态"}, {"value", Psc::to_string(feed->state)}}});
feed->output_format.read([&](const auto& value) { details.push_back(Json{{"label", "数据格式"}, {"value", std::string(magic_enum::enum_name(value.type))}}); });
if(const auto value = dynamic_cast<Data_Feed_TCP_Server*>(feed.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "监听端口"}, {"value", config.port}}); });
} else if(const auto value = dynamic_cast<Data_Feed_UDP_Server*>(feed.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "监听端口"}, {"value", config.port}}); });
} else if(const auto value = dynamic_cast<Data_Feed_TCP_Client*>(feed.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "目标"}, {"value", config.url + ":" + std::to_string(config.port)}}); });
} else if(const auto value = dynamic_cast<Data_Feed_UDP_Client*>(feed.get())) {
value->specific.read([&](const auto& config) { details.push_back(Json{{"label", "目标"}, {"value", config.url + ":" + std::to_string(config.port)}}); });
}
nodes.push_back(Json{{"id", id}, {"kind", "data_feed"}, {"entity_id", index + 1},
{"key", feed->key}, {"label", feed->key}, {"type", feed->type},
{"enabled", feed->enabled()}, {"status_api", data_topology_path + "/status/data_feed/" + std::to_string(index + 1)}});
{"key", feed->key}, {"label", feed->key}, {"type", feed->type},
{"enabled", feed->enabled()}, {"config", std::move(config)}, {"details", std::move(details)}, {"status_api", data_topology_path + "/status/data_feed/" + std::to_string(index + 1)}});
const auto source = source_by_key.find(feed->source_key);
if(source != source_by_key.end()) {
edges.push_back(Json{{"id", "source-feed:" + feed->key},
edges.push_back(Json{{"id", "source-feed:" + feed->key}, {"kind", "configuration"},
{"source", "data_source:" + feed->source_key}, {"target", id},
{"active", source->second->enabled() && feed->enabled()}});
{"active", source->second->enabled() && feed->enabled()},
{"status_api", data_topology_path + "/status/connection/" + std::to_string(index + 1)}});
}
if(const auto value = dynamic_cast<Data_Feed_TCP_Server*>(feed.get())) {
for(const auto& client : value->svr.get_all_clients()) {
const auto client_id = "client:tcp:" + feed->key + ":" + std::to_string(client->info.fd);
const auto endpoint = client->info.sockaddr.to_string();
nodes.push_back(Json{{"id", client_id}, {"kind", "client"}, {"entity_id", 0}, {"key", endpoint}, {"label", endpoint}, {"type", "TCP 客户端"}, {"enabled", true}, {"details", Json::array({Json{{"label", "文件描述符"}, {"value", client->info.fd}}, Json{{"label", "发送缓冲区"}, {"value", client->send_buffer.state_str()}}})}});
edges.push_back(Json{{"id", "feed-client:" + feed->key + ":" + std::to_string(client->info.fd)}, {"kind", "runtime"}, {"source", id}, {"target", client_id}, {"active", true}, {"details", Json::array({Json{{"label", "连接状态"}, {"value", "已连接"}}, Json{{"label", "远端"}, {"value", endpoint}}, Json{{"label", "发送缓冲区"}, {"value", client->send_buffer.state_str()}}})}});
}
} else if(const auto value = dynamic_cast<Data_Feed_UDP_Server*>(feed.get())) {
for(const auto& client : value->svr.clients) {
const auto endpoint = client.to_string();
const auto client_id = "client:udp:" + feed->key + ":" + endpoint;
nodes.push_back(Json{{"id", client_id}, {"kind", "client"}, {"entity_id", 0}, {"key", endpoint}, {"label", endpoint}, {"type", "UDP 客户端"}, {"enabled", true}, {"details", Json::array()}});
edges.push_back(Json{{"id", "feed-client:" + feed->key + ":" + endpoint}, {"kind", "runtime"}, {"source", id}, {"target", client_id}, {"active", true}, {"details", Json::array({Json{{"label", "连接状态"}, {"value", "已连接"}}, Json{{"label", "远端"}, {"value", endpoint}}})}});
}
}
}
return Json{{"protocol", "ecap.data-topology"}, {"protocol_version", 1},
@@ -724,27 +788,51 @@ Json feed_amis_schema(Global* global) {
set_select_options(schema, "source_key", options);
return schema;
}
Json topology_editor(std::string kind, std::string title, std::string base_api, Json create_data, Json collection_schema) {
const auto& crud = collection_schema["body"][0];
Json create_schema;
for(const auto& item : crud["headerToolbar"]) {
if(item.is_object() && item.value("actionType", std::string{}) == "dialog") {
create_schema = item["dialog"]["body"];
break;
}
}
const auto& columns = crud["columns"];
Json edit_schema = columns.back()["buttons"][0]["dialog"]["body"];
create_schema.erase("reload");
edit_schema.erase("reload");
edit_schema["initApi"] = Json{{"method", "get"}, {"url", base_api + "/${id}"}};
return Json{{"kind", std::move(kind)}, {"title", std::move(title)}, {"create_data", std::move(create_data)}, {"create_schema", std::move(create_schema)}, {"edit_schema", std::move(edit_schema)}};
}
Json data_topology_schema(Global* global) {
auto source_create_data = adminive::to_frontend_json<Json>(Data_Source_Row{});
source_create_data.erase("state");
auto feed_create_data = adminive::to_frontend_json<Json>(Data_Feed_Row{});
feed_create_data.erase("format");
feed_create_data.erase("state");
return Json{
{"title", "数据源与数据馈送拓扑"},
{"data_api", data_topology_path},
{"view_api", data_topology_path + "/view"},
{"graph", Json{
{"height", 620},
{"layout", Json{{"type", "dagre"}, {"rankdir", "LR"}, {"align", "UL"}, {"nodesep", 34}, {"ranksep", 100}}},
{"layout", Json{{"type", "dagre"}, {"rankdir", "TB"}, {"align", "UL"}, {"nodesep", 42}, {"ranksep", 92}}},
{"layout_revision", 2},
{"poll_interval_ms", 2000},
{"behaviors", Json::array({"drag-canvas", "zoom-canvas", "drag-element"})},
{"active_edge_color", "#52c41a"},
{"inactive_edge_color", "#bfbfbf"}
}},
{"node_kinds", Json::array({
Json{{"kind", "data_source"}, {"title", "数据源"}, {"fill", "#e6f4ff"}, {"stroke", "#1677ff"}},
Json{{"kind", "data_feed"}, {"title", "数据馈送"}, {"fill", "#f6ffed"}, {"stroke", "#52c41a"}}
Json{{"kind", "data_feed"}, {"title", "数据馈送"}, {"fill", "#f6ffed"}, {"stroke", "#52c41a"}},
Json{{"kind", "client"}, {"title", "已连接客户端"}, {"fill", "#fff7e6"}, {"stroke", "#fa8c16"}}
})},
{"status", Json{{"title", "运行状态"}, {"poll_interval_ms", 1000},
{"collapsed_hint", "展开后才向后台轮询,收起立即停止"}}},
{"editors", Json::array({
Json{{"kind", "data_source"}, {"title", "数据源管理"}, {"button_label", "管理数据源"}, {"schema", source_amis_schema()}},
Json{{"kind", "data_feed"}, {"title", "数据馈送管理"}, {"button_label", "管理数据馈送"}, {"schema", feed_amis_schema(global)}}
topology_editor("data_source", "数据源", "/api/adminive/config/data_sources", std::move(source_create_data), source_amis_schema()),
topology_editor("data_feed", "数据馈送", "/api/adminive/config/data_feeds", std::move(feed_create_data), feed_amis_schema(global))
})}
};
}
@@ -920,18 +1008,21 @@ private:
}, put);
app.registerHandler(data_topology_path + "/status/data_source/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
const auto source = item_by_id(global_->mode_acs.data_source_config.map.list(), id);
return adminive::make_http_success<Json>(adminive::parse_json<Json>(source->get_state().to_json_string()));
});
}, get);
app.registerHandler(data_topology_path + "/status/data_feed/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
const auto feed = item_by_id(global_->mode_acs.data_feed_config.map.list(), id);
return adminive::make_http_success<Json>(adminive::parse_json<Json>(feed->get_state_json().to_json_string()));
});
}, get);
app.registerHandler(data_topology_path + "/status/connection/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
return adminive::make_http_success<Json>(data_topology_connection_status(global_, id));
});
}, get);
}
void bind_backend_config(drogon::HttpAppFramework& app) {
const auto get = adminive::make_drogon_constraints(drogon::Get, {});
@@ -972,24 +1063,26 @@ private:
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), "created");
});
}, post);
const auto update = [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
const auto list = global_->mode_acs.data_source_config.map.list();
auto source = item_by_id(list, id);
auto input = read_request_body(body);
auto row = source_row(source);
const auto result = adminive::apply_frontend_patch<Json>(row, input);
if(!result.success)
return adminive::make_http_error<Json>(422, result);
apply_source_row(source, row);
Data_feed_Config::set_connections_need_refresh();
save_data_sources(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), result.message);
});
const auto make_update = [this] {
return [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
const auto list = global_->mode_acs.data_source_config.map.list();
auto source = item_by_id(list, id);
auto input = read_request_body(body);
auto row = source_row(source);
const auto result = adminive::apply_frontend_patch<Json>(row, input);
if(!result.success)
return adminive::make_http_error<Json>(422, result);
apply_source_row(source, row);
Data_feed_Config::set_connections_need_refresh();
save_data_sources(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(source_row(source)), result.message);
});
};
};
app.registerHandler(data_sources_path + "/{1:id}", update, put);
app.registerHandler(data_sources_path + "/{1:id}", update, patch);
app.registerHandler(data_sources_path + "/{1:id}", make_update(), put);
app.registerHandler(data_sources_path + "/{1:id}", make_update(), patch);
app.registerHandler(data_sources_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
@@ -1047,26 +1140,28 @@ private:
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), "created");
});
}, post);
const auto update = [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
const auto list = global_->mode_acs.data_feed_config.map.list();
auto feed = item_by_id(list, id);
auto input = read_request_body(body);
auto row = feed_row(feed);
const auto result = adminive::apply_frontend_patch<Json>(row, input);
if(!result.success)
return adminive::make_http_error<Json>(422, result);
if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key))
throw std::invalid_argument("连接的数据源不存在: " + row.source_key);
apply_feed_row(feed, row);
Data_feed_Config::set_connections_need_refresh();
save_data_feeds(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), result.message);
});
const auto make_update = [this] {
return [this](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id, body = std::string(request->getBody())] {
std::lock_guard lock(topology_mutex_);
const auto list = global_->mode_acs.data_feed_config.map.list();
auto feed = item_by_id(list, id);
auto input = read_request_body(body);
auto row = feed_row(feed);
const auto result = adminive::apply_frontend_patch<Json>(row, input);
if(!result.success)
return adminive::make_http_error<Json>(422, result);
if(!row.source_key.empty() && !global_->mode_acs.data_source_config.map.get(row.source_key))
throw std::invalid_argument("连接的数据源不存在: " + row.source_key);
apply_feed_row(feed, row);
Data_feed_Config::set_connections_need_refresh();
save_data_feeds(global_);
return adminive::make_http_success<Json>(adminive::to_frontend_json<Json>(feed_row(feed)), result.message);
});
};
};
app.registerHandler(data_feeds_path + "/{1:id}", update, put);
app.registerHandler(data_feeds_path + "/{1:id}", update, patch);
app.registerHandler(data_feeds_path + "/{1:id}", make_update(), put);
app.registerHandler(data_feeds_path + "/{1:id}", make_update(), patch);
app.registerHandler(data_feeds_path + "/{1:id}", [this](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback, std::uint64_t id) {
complete_business_request(callback, [this, id] {
std::lock_guard lock(topology_mutex_);
+3 -1
View File
@@ -183,6 +183,8 @@ void Data_Topology_View_Config::from_json(const Psc::JSON* that_json) {
return;
if (const auto* value = that_json->get("initialized"); value != nullptr && value->valueType == Psc::Bool)
initialized = value->bool_val();
if (const auto value = that_json->try_get_number<std::uint32_t>("layout_revision"); value.has_value())
layout_revision = value.value();
if (const auto value = that_json->try_get_number<double>("zoom"); value.has_value() && std::isfinite(value.value()) && value.value() > 0.0)
zoom = value.value();
if (const auto value = that_json->try_get_number<double>("pan_x"); value.has_value() && std::isfinite(value.value()))
@@ -206,7 +208,7 @@ Psc::JSON Data_Topology_View_Config::to_json() const {
auto positions = Psc::JSON::object();
for (const auto& [id, position] : node_positions)
positions.append({id, Psc::JSON::object({{"x", position.x}, {"y", position.y}})});
return Psc::JSON::object({{"initialized", initialized}, {"zoom", zoom}, {"pan_x", pan_x}, {"pan_y", pan_y},
return Psc::JSON::object({{"initialized", initialized}, {"layout_revision", layout_revision}, {"zoom", zoom}, {"pan_x", pan_x}, {"pan_y", pan_y},
{"node_positions", positions}});
}
Psc::JSON Mode_ACS_Config::to_base_json() const {
+2
View File
@@ -96,6 +96,7 @@ struct Data_Topology_Node_Position {
};
struct Data_Topology_View_Config {
bool initialized{};
std::uint32_t layout_revision{};
double zoom = 1.0;
double pan_x{};
double pan_y{};
@@ -119,6 +120,7 @@ struct Type_Descriptor<Data_Topology_View_Config> {
using T = Data_Topology_View_Config;
return object<T>("data_topology_view", "数据拓扑视图",
ADMINIVE_FIELD_LABEL(T, initialized, "已初始化"),
ADMINIVE_FIELD_LABEL(T, layout_revision, "布局版本"),
ADMINIVE_FIELD_LABEL(T, zoom, "缩放"),
ADMINIVE_FIELD_LABEL(T, pan_x, "平移 X"),
ADMINIVE_FIELD_LABEL(T, pan_y, "平移 Y"),
+2 -1
View File
@@ -173,6 +173,7 @@ void Global::init_web_server() {
add_path(R"(^/map)");
add_path(R"(^/map3d)");
add_path(R"(^/aircraftlist)");
add_path(R"(^/topology)");
drogon::app().registerHandlerViaRegex(
R"(^/ui(/.*)?$)",
[return_file](const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
@@ -184,7 +185,7 @@ void Global::init_web_server() {
}
auto is_spa_path = [](std::string_view value) {
return value == "/" || value == "/map" || value == "/map3d" || value == "/settings" ||
value == "/aircraftlist";
value == "/aircraftlist" || value == "/topology";
};
if (is_spa_path(rp)) {
path += "/index.html";