dommessage

This commit is contained in:
flower_linux
2026-04-28 21:56:56 +08:00
parent 7459cc0f36
commit 7280fca08d
24 changed files with 848 additions and 115 deletions

View File

@@ -58,9 +58,9 @@
| `remove_filter` | `id` | `0` | `4041` | 删除过滤规则 |
| `add_transform_rule` | `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | `0` | `4002` | 添加转换规则 |
| `remove_transform_rule` | `id` | `0` | `4042` | 删除转换规则 |
| `add_mapping_rule` | `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?` | `0` | `4004` | 新增/更新映射规则 |
| `remove_mapping_rule` | `compositeKey` | `0` | `4043` | 删除映射规则 |
| `list_mapping_rules` | 无 | `0` | - | 返回映射规则列表 |
| `add_mapping_rule` | `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?`, `metadataId?`, `pointId?`, `catalogVersion?`, `updatedBy?` | `0` | `4004` / `5002` | 新增/更新映射规则;成功后持久化到 `runtime_mappings.json``5002` 表示持久化失败 |
| `remove_mapping_rule` | `compositeKey` | `0` | `4043` / `5002` | 删除映射规则并持久化 |
| `list_mapping_rules` | 无 | `0` | - | 返回 `items``mappingCatalogVersion` |
| `discovery_start` | `ttlMs?` | `0` | - | 开启嗅探模式 |
| `discovery_stop` | 无 | `0` | - | 关闭嗅探模式 |
| `discovery_clear` | 无 | `0` | - | 清空发现池 |
@@ -123,10 +123,10 @@
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `GET` | `/api/v1/metadata` | 无 | 返回 metadata 字典全量快照 |
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情 |
| `GET` | `/api/v1/profiles` | 无 | 返回全部设备 profile 快照 |
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile |
| `GET` | `/api/v1/metadata` | 无 | 返回 `data`(字典数组)与可选 `catalogVersion`(根级字段) |
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情(含可选 `catalogVersion` |
| `GET` | `/api/v1/profiles` | 无 | 返回 `data` 与可选 `catalogVersion` |
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile(含可选 `catalogVersion` |
### 5.7 Discovery 路由
@@ -142,9 +142,9 @@
| 方法 | 路径 | 请求参数 | 说明 |
|---|---|---|---|
| `POST` | `/api/v1/mapping/bind` | body: `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?` | 绑定或更新映射规则 |
| `POST` | `/api/v1/mapping/unbind` | body: `compositeKey` | 删除映射规则 |
| `GET` | `/api/v1/mapping/list` | 无 | 列出当前映射规则 |
| `POST` | `/api/v1/mapping/bind` | body: `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?`, `metadataId?`, `pointId?`, `catalogVersion?`, `updatedBy?` | 绑定或更新映射规则并写入 `runtime_mappings.json` |
| `POST` | `/api/v1/mapping/unbind` | body: `compositeKey` | 删除映射规则并持久化 |
| `GET` | `/api/v1/mapping/list` | 无 | 返回 `items``mappingCatalogVersion` |
## 6) 通用错误码约定(当前实现)
@@ -160,6 +160,7 @@
| `4041` | `filter_not_found` | 删除过滤规则时未找到 |
| `4042` | `transform_rule_not_found` | 删除转换规则时未找到 |
| `4043` | `mapping_rule_not_found` | 删除映射规则时未找到 |
| `5002` | `persist_mappings_failed` | 映射规则内存更新成功但写入 `runtime_mappings.json` 失败 |
## 7) 维护建议

View File

@@ -3,7 +3,9 @@
#include <algorithm>
#include <array>
#include <mutex>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QHttpServer>
#include <QHttpHeaders>
@@ -15,6 +17,7 @@
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QSaveFile>
#include <QUrlQuery>
#include <utility>
@@ -176,6 +179,17 @@ QHttpServerResponse okDataResponse(const QJsonValue& data,
return jsonResponse(QJsonObject{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}}, code);
}
QHttpServerResponse okDataResponseWithCatalog(const QJsonValue& data,
const QString& catalogVersion,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
{
QJsonObject o{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}};
if (!catalogVersion.isEmpty()) {
o.insert(QStringLiteral("catalogVersion"), catalogVersion);
}
return jsonResponse(o, code);
}
QHttpServerResponse errorResponse(const QString& error,
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::BadRequest)
{
@@ -212,6 +226,10 @@ QJsonArray devicesToJson(const std::vector<softbus::devices::DeviceInfo>& device
QString resolveConfigBaseDir()
{
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
if (!daemonConfigPath.isEmpty()) {
return QFileInfo(daemonConfigPath).dir().absolutePath() + QChar('/');
}
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
if (!configBase.isEmpty()) {
return configBase.endsWith(QChar('/')) ? configBase : configBase + QChar('/');
@@ -219,10 +237,71 @@ QString resolveConfigBaseDir()
return QDir::homePath() + QStringLiteral("/softbus/config/");
}
QString resolveConfigFile(const QString& fileName, const QString& fallbackRelative)
QString resolveConfigFile(const QString& fileName)
{
const QString homePath = resolveConfigBaseDir() + fileName;
return QFileInfo::exists(homePath) ? homePath : fallbackRelative;
return resolveConfigBaseDir() + fileName;
}
bool ensureConfigFileInitialized(const QString& targetPath,
const QString& fallbackPath,
const QByteArray& defaultContent)
{
if (QFile::exists(targetPath)) {
return true;
}
const QFileInfo fi(targetPath);
if (!QDir().mkpath(fi.dir().absolutePath())) {
return false;
}
QByteArray initBytes = defaultContent;
QFile fallback(fallbackPath);
if (!fallbackPath.isEmpty() && fallback.exists() && fallback.open(QIODevice::ReadOnly)) {
initBytes = fallback.readAll();
}
QSaveFile initFile(targetPath);
if (!initFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
if (initFile.write(initBytes) != initBytes.size()) {
initFile.cancelWriting();
return false;
}
return initFile.commit();
}
void ensureRestApiConfigFilesInitialized()
{
static std::once_flag once;
std::call_once(once, []() {
const QByteArray defaultRules = QJsonDocument(QJsonObject{
{QStringLiteral("filter"), QJsonArray{}},
{QStringLiteral("transform"), QJsonArray{}},
}).toJson(QJsonDocument::Indented);
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_rules.json")),
QStringLiteral(),
defaultRules);
const QByteArray defaultMappings = QStringLiteral(
"{\n \"catalogVersion\": 1,\n \"items\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_mappings.json")),
QStringLiteral(),
defaultMappings);
const QByteArray defaultProfiles = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"profiles\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("device_profiles.json")),
QStringLiteral("config/device_profiles.json"),
defaultProfiles);
const QByteArray defaultMetadata = QStringLiteral(
"{\n \"catalogVersion\": \"\",\n \"metadatas\": []\n}\n")
.toUtf8();
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("metadata_dictionary.json")),
QStringLiteral("config/metadata_dictionary.json"),
defaultMetadata);
});
}
bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, QString* error)
@@ -232,10 +311,9 @@ bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, Q
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
if (!attempted) {
ensureRestApiConfigFilesInitialized();
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("metadata_dictionary.json"),
QStringLiteral("config/metadata_dictionary.json")));
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("metadata_dictionary.json")));
}
if (!loaded && error) {
*error = registry.lastError();
@@ -249,17 +327,17 @@ softbus::core::metadata::MetadataRegistry& metadataRegistry()
return registry;
}
bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QString* error)
bool ensureProfileLoaded(QString* error)
{
static std::mutex mtx;
static bool attempted = false;
static bool loaded = false;
std::lock_guard<std::mutex> lk(mtx);
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
if (!attempted) {
ensureRestApiConfigFilesInitialized();
attempted = true;
loaded = registry.loadFromFile(
resolveConfigFile(QStringLiteral("device_profiles.json"),
QStringLiteral("config/device_profiles.json")));
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("device_profiles.json")));
}
if (!loaded && error) {
*error = registry.lastError();
@@ -267,12 +345,6 @@ bool ensureProfileLoaded(softbus::core::metadata::ProfileRegistry& registry, QSt
return loaded;
}
softbus::core::metadata::ProfileRegistry& profileRegistry()
{
static softbus::core::metadata::ProfileRegistry registry;
return registry;
}
bool isProtectedNodeKey(const QString& key)
{
for (const auto* value : kProtectedNodeKeys) {
@@ -400,6 +472,11 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
rule.scale = payload.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = payload.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = payload.value(QStringLiteral("unit")).toString();
rule.metadataId = payload.value(QStringLiteral("metadataId")).toString();
rule.pointId = payload.value(QStringLiteral("pointId")).toString();
rule.catalogVersion = payload.value(QStringLiteral("catalogVersion")).toString();
rule.updatedBy = payload.value(QStringLiteral("updatedBy")).toString(QStringLiteral("http"));
rule.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
if (!mappingReg.upsert(rule)) {
@@ -407,10 +484,15 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}},
QHttpServerResponse::StatusCode::BadRequest));
}
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"),
QString::number(rule.compositeKey)}},
QHttpServerResponse::StatusCode::Ok));
ensureRestApiConfigFilesInitialized();
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
(void)mappingReg.savePersistedFile(mapPath);
return withCorsHeaders(QHttpServerResponse(
QJsonObject{{QStringLiteral("ok"), true},
{QStringLiteral("compositeKey"), QString::number(rule.compositeKey)},
{QStringLiteral("mappingCatalogVersion"),
QString::number(mappingReg.mappingCatalogVersion())}},
QHttpServerResponse::StatusCode::Ok));
});
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext,
@@ -420,6 +502,11 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
const bool ok = mappingReg.remove(compositeKey);
if (ok) {
ensureRestApiConfigFilesInitialized();
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
(void)mappingReg.savePersistedFile(mapPath);
}
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
{QStringLiteral("compositeKey"),
QString::number(compositeKey)}},
@@ -429,9 +516,10 @@ void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
return withCorsHeaders(
QHttpServerResponse(QJsonObject{{QStringLiteral("items"), mappingReg.list()}},
QHttpServerResponse::StatusCode::Ok));
QJsonObject o;
o.insert(QStringLiteral("items"), mappingReg.list());
o.insert(QStringLiteral("mappingCatalogVersion"), mappingReg.mappingCatalogVersion());
return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
});
}
@@ -631,7 +719,7 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
});
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Get, routeContext,
@@ -646,7 +734,7 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
for (const auto& item : items) {
const auto obj = item.toObject();
if (obj.value(QStringLiteral("metadataId")).toString() == metadataId) {
return okDataResponse(obj);
return okDataResponseWithCatalog(obj, registry.catalogVersion());
}
}
return errorResponse(QStringLiteral("metadata_not_found"),
@@ -657,24 +745,24 @@ void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
void registerProfileRoutes(QHttpServer* server, QObject* routeContext)
{
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Get, routeContext, []() {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
if (!ensureProfileLoaded(&error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.list());
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
});
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Get, routeContext,
[](const QString& deviceId) {
auto& registry = profileRegistry();
QString error;
if (!ensureProfileLoaded(registry, &error)) {
if (!ensureProfileLoaded(&error)) {
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
QHttpServerResponse::StatusCode::ServiceUnavailable);
}
return okDataResponse(registry.listByDeviceId(deviceId));
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
return okDataResponseWithCatalog(registry.listByDeviceId(deviceId), registry.catalogVersion());
});
}

View File

@@ -1,5 +1,6 @@
#include "api/ipc_dbus/CommandDispatcher.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
@@ -29,6 +30,35 @@ QString rulesConfigPath()
return QDir::homePath() + QStringLiteral("/softbus/config/runtime_rules.json");
}
QString runtimeMappingsConfigPath()
{
const QFileInfo fi(rulesConfigPath());
return fi.dir().filePath(QStringLiteral("runtime_mappings.json"));
}
bool persistMappingsToDedicatedConfig(const softbus::message_bus::pipeline::MappingRegistry& reg)
{
const QString path = runtimeMappingsConfigPath();
const QFileInfo fi(path);
if (!QDir().mkpath(fi.dir().absolutePath())) {
LOG_ERROR() << "CommandDispatcher: failed to create mappings directory:" << fi.dir().absolutePath();
return false;
}
return reg.savePersistedFile(path);
}
void appendMappingAuditLine(const QString& line)
{
const QFileInfo fi(runtimeMappingsConfigPath());
const QString auditPath = fi.dir().filePath(QStringLiteral("mapping_audit.log"));
QFile f(auditPath);
if (!f.open(QIODevice::Append | QIODevice::Text)) {
return;
}
f.write(line.toUtf8());
f.write("\n");
}
softbus::message_bus::pipeline::RuleScope parseScope(const QJsonObject& params)
{
softbus::message_bus::pipeline::RuleScope scope;
@@ -188,14 +218,38 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
rule.scale = params.value(QStringLiteral("scale")).toDouble(1.0);
rule.offset = params.value(QStringLiteral("offset")).toDouble(0.0);
rule.unit = params.value(QStringLiteral("unit")).toString();
rule.metadataId = params.value(QStringLiteral("metadataId")).toString();
rule.pointId = params.value(QStringLiteral("pointId")).toString();
rule.catalogVersion = params.value(QStringLiteral("catalogVersion")).toString();
rule.updatedBy = params.value(QStringLiteral("updatedBy")).toString();
rule.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
if (!mappingReg.upsert(rule)) {
return {4004, QStringLiteral("invalid_mapping_rule"), {}};
}
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("compositeKey"), keyText}}};
if (!persistMappingsToDedicatedConfig(mappingReg)) {
return {5002, QStringLiteral("persist_mappings_failed"), {}};
}
appendMappingAuditLine(QStringLiteral("upsert compositeKey=%1 domPath=%2 at %3")
.arg(keyText)
.arg(rule.domPath)
.arg(rule.updatedAtMs));
return {0,
QStringLiteral("ok"),
QJsonObject{{QStringLiteral("compositeKey"), keyText},
{QStringLiteral("mappingCatalogVersion"),
QString::number(mappingReg.mappingCatalogVersion())}}};
}
if (action == QStringLiteral("remove_mapping_rule")) {
const QString keyText = params.value(QStringLiteral("compositeKey")).toString();
const bool ok = mappingReg.remove(keyText.toULongLong());
if (ok) {
if (!persistMappingsToDedicatedConfig(mappingReg)) {
return {5002, QStringLiteral("persist_mappings_failed"), {}};
}
appendMappingAuditLine(QStringLiteral("remove compositeKey=%1 at %2")
.arg(keyText)
.arg(QDateTime::currentMSecsSinceEpoch()));
}
return {ok ? 0 : 4043, ok ? QStringLiteral("ok") : QStringLiteral("mapping_rule_not_found"),
QJsonObject{{QStringLiteral("compositeKey"), keyText}}};
}