【跨平台C++开发框架 Zeus 】 -- 配置管理系统

1,583 阅读6分钟

R-C.jpg

背景

传送门:github.com/zeus-cpp/ze…

客户端开发应用时,你可能会遇到这些问题或需求:

  • 应用需要为不同客户或渠道提供定制版本,而应用的差异往往是GUI文案、字体或者颜色
  • 应用需要支持多环境部署(例如:开发、测试、生产环境),不同的环境有不一样的配置项
  • 应用需支持分层的信息获取(优先从内存获取,内存中没有时,从本地配置文件中获取),业务运行参数的动态调整,以满足业务的灵活性与扩展性

面对这些问题,如何解决呢?你可能需要一个灵活且强大的配置管理系统。

例如:应用预定义配置,支持多环境部署

// 开发环境配置
devConfig->Set(KEY, "4d4c66603a2");
devConfig->Set(SECRET, "92cedec1bfb16ba6");
// 测试环境配置
testConfig->Set(KEY, "ac6ccdb4fa4");
testConfig->Set(SECRET, "a1f31a9af4e23d8c");
// 生产环境配置
releaseConfig->Set(KEY, "1_76f4349916d");
releaseConfig->Set(SECRET, "aa214899962a9b7e");

// 添加配置
modeConfig.AddConfig(devConfig, "dev");
modeConfig.AddConfig(testConfig, "test");
modeConfig.AddConfig(releaseConfig, "pro");

... 
// 根据环境变量切换配置
modeConfig->Switch("test");

例如:应用分层获取配置信息(文件配置比内存配置优先级高)

auto configPath = (fs::u8path("D:\\cpp\test.ini");

auto configSerializer = std::make_shared<Zeus::FileSerializer>(configPath);
auto config1 = std::make_shared<Zeus::GeneralConfig>(configSerializer);
layeredConfig->AddConfig(config1, "config1", -1);

auto config2 = std::make_shared<Zeus::GeneralConfig>();
layeredConfig->AddConfig(freezeConfig, "config2", 0);

const auto value = layeredConfig->GetConfigValue("key1");

以上的代码使用 Zeus 客户端开发框架实现。 Zeus 提供了灵活、强大、易用的配置管理能力。让我们看看 Zeus 具体是如何实现的。

Zeus 配置管理

Zeus 提供了多种配置管理能力,具体可以分为:

image.png

general_config

general_config 是 Zeus 配置能力的核心,支持按(key,value)的格式进行增、删、改、查。为了支持跨平台与存储复杂的数据结构,内部使用了 nlohmann::json 作为value的类型。

// 增加 or 修改 key 对应的value
zeus::expected<void, ConfigError>  ret = config.SetConfigValue("key");

// 查询是否存在key
bool ret = config.HasConfigValue("key");

// 查询key对应的value
zeus::expected<ConfigValue, ConfigError> ret = config.GetConfigValue("key");

// 删除key对应的value
zeus::expected<void, ConfigError> ret = config.RemoveConfigValue("key");

general_config 使用起来很简单,但是可能你已经发现了问题了:你必须手动将 ConfigValue(nlohmann::json)转换为预期的类型。

为了提高使用者的开发效率,Zeus实现了 config_view 作为配置的 wrapper,你可以这样使用:

auto config = std::make_shared<Zeus::GeneralConfig>();
auto configView = std::make_shared<Zeus::ConfigView>(*config);

configView->Set<std::string>(KEY1, "string");
configView->Set<int>(KEY2, 1);
configView->Set<unsigned int>(KEY3, 3);
configView->Set<unsigned long long>(KEY4, 4);

auto value1 = configView->Get<std::string>(KEY1, "defaultString");
auto value2 = configView->Get<int>(KEY2, 1);
auto value3 = configView->Get<unsigned int>(KEY3, 3);
auto value4 = configView->Get<unsigned long long>(KEY4, 4);

注意,你需要保证 config 的生命周期比 configView 要长。

general_config 的构造函数中,支持自定义的序列化方式,你可以根据自己的需要进行实现。

当然,Zeus 内部也提供了基础的文件序列化方法(file_serializer),你可以很轻松地实现读写 json 格式的配置文件,以达到配置持久化的效果:

/*
test.json内容
{
    "numString":"123"
}
*/

auto configPath = std::filesystem::u8path(R"(D:/test.json)");
auto configSerializer = std::make_shared<Zeus::FileSerializer>(configPath);
auto config = std::make_shared<Zeus::GeneralConfig>(configSerializer);
auto configView = std::make_shared<Zeus::ConfigView>(*config);

auto value = configView->Get<std::string>("numString", ""); // value = "123"
auto value = configView->Set<int>("num", 1);
/*
test.json内容变更
{
    "numString":"123",
    "num":1
}
*/

layered_config

layered_config,提供配置的分层管理能力,你可以将不同的 config 添加至 layered_config 中,并指定 config 的优先级,以及是否支持写操作。layered_config 内部使用链表存储不同的 config:

image.png

layered_config 按照 order 从小到大排列 config(即 order 越小,优先级越高),并使用 writeable 表示layered_config 对 config 的可写性。

读操作时,layered_config 遍历 config,并返回第一个有效值。写操作时,如果某个可写入的高优先级config存在指定的 key,则只更新该 config,如果可写入的 config 中不存在指定 key 时,则更新所有可写入的 config。

layered_config 使 Zeus 配置管理中心的灵活性得到较大的提升,试想该场景:

应用的运行时配置需时常进行读写操作,其包含一个调试开关的配置(key:debug,value:true/false)。

调试开关日常为关闭状态,但当需要调试时,你只需在配置文件中新增 debug 标识,即可达到目的,无需修改代码。

/*
debug.json内容
{
    "debug":true
}
*/

// 文件配置
auto configPath = std::filesystem::u8path(R"(D:/debug.json)");
auto configSerializer = std::make_shared<Zeus::FileSerializer>(configPath);
auto fileConfig = std::make_shared<Zeus::GeneralConfig>(configSerializer);
// 内存配置
auto memoryConfig = std::make_share<Zeus::GeneralConfig>();
// 将文件配置、内存配置,添加到配置分层中
auto layeredConfig = std::make_unique<LayeredConfig>();
layeredConfig->AddConfig(memoryConfig, "memory", true, -100);
layeredConfig->AddConfig(fileConfig, "file", false, 0);
auto layeredConfigView = std::make_shared<Zeus::ConfigView>(*layeredConfig);
...
// 判断是否需要进行debug操作
if (layeredConfigView->Get("debug", false))
{
    //debug
}

backup_config

backup_config 即备份配置,backup_config 与 layered_config 有一定的相似度,但是应用的场景则完全不一样。 你同样可以将不同的 config 添加至 backup_config 中,并指定 config 的优先级,但是 backup_config 中默认config 全部为可写状态。

读操作时,backup_config 遍历 config,并返回第一个有效值。写操作时,backup_config 将key:value 更新至所有的 config 中。

你可能会怀疑 backup_config 存在的必要性,但如果你遇到以下的需求,backup_config 是一个不错的解决方案:

  1. 本地配置文件需要多备份,以确保配置可用性
  2. 系统某情况下,原配置文件会失效,你需要根据系统状态新增一个可用的配置

backup_config 的使用方式如下:


 auto backupConfig = std::make_shared<Zeus::BackupConfig>();
 std::error_code ec;
 if (fs::exists("F:\\", ec))
 {
     auto fConfigPath = (fs::u8path("F:\\") / "backup.json";
     auto fConfigSerializer = std::make_shared<Zeus::FileSerializer>(fConfigPath);
     auto fConfig = std::make_shared<Zeus::GeneralConfig>(fConfigSerializer);
     backupConfig->AddConfig(fConfig, "f", -1);
 }
 
 auto localConfigPath = (Zeus::ApplicationBase::Instance().AppDataDir() / "backup.json";
 auto localConfigSerializer = std::make_shared<Zeus::FileSerializer>(localConfigPath);
 auto localConfig           = std::make_shared<Zeus::GeneralConfig>(localConfigSerializer);
 backupConfig->AddConfig(localConfig, "local", 0);
 
 auto backupConfigView = std::make_shared<Zeus::ConfigView>(*backupConfig);
...
// 获取可用的配置项
if (backupConfigView->Get("flag", false))
{
    // do something
}

switch_config

switch_config 即可切换配置,其与 layered_config、backup_config 一样,是 config 的容器,支持根据 label 动态切换预定义的 config。

回想文章开始时的问题之一,应用需要支持多环境部署(例如:开发、测试、生产环境),不同的环境有不一样的配置项,该如何实现呢?

首先,你可能需要在系统中设置一个环境标识,例如:windows 系统中可以利用注册表来设置。 再次,你可能需要在应用中预定义多环境的配置,并使用 switch_config 进行管理。 最后,应用运行时,可以根据注册表项,动态切换正确的环境配置。

总结

好的配置管理不仅使应用程序更容易使用和维护,还能提高整体用户体验。

灵活运用Zeus的配置管理能力,你可以为自己的应用创建一个强大、灵活且高扩展性的配置系统。