diff --git a/README.md b/README.md index 01b7f4d..78b217c 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,30 @@ kenaito-conf/ ├── server/ # Go 管理端(Gin + xorm) ├── web/ # Vue 2 前端 -├── kenaito-conf-go-client/ # (测试通过)Go 客户端 SDK -├── kenaito-conf-go-client-demo/ # (测试通过)Go 客户端示例 -├── kenaito-conf-springboot2-client/ # (测试通过)Java Spring Boot 2.x 客户端 SDK(JDK 8+) -├── kenaito-conf-springboot2-client-demo/# (测试通过)Spring Boot 2.x 客户端示例(端口 8081) -├── kenaito-conf-python-client/ # (测试通过)Python 客户端 SDK(Python 3.10+) -├── kenaito-conf-python-client-demo/ # (测试通过)Python 客户端示例(端口 8082) -├── kenaito-conf-nodejs-client/ # (测试通过)Node.js 客户端 SDK(Node.js 18+) -├── kenaito-conf-nodejs-client-demo/ # (测试通过)Node.js 客户端示例(端口 8083) -├── kenaito-conf-php-client/ # (待测试)PHP 客户端 SDK(PHP 8.0+) -├── kenaito-conf-php-client-demo/ # (待测试)PHP 客户端示例 -├── kenaito-conf-dotnet-client/ # (待测试).NET 客户端 SDK(.NET 6.0+) -├── kenaito-conf-dotnet-client-demo/ # (待测试).NET 客户端示例 -├── kenaito-conf-c-client/ # (待测试)C 客户端 SDK(libcurl + OpenSSL) -├── kenaito-conf-c-client-demo/ # (待测试)C 客户端示例 -├── kenaito-conf-cpp-client/ # (待测试)C++ 客户端 SDK(libcurl + OpenSSL) -├── kenaito-conf-cpp-client-demo/ # (待测试)C++ 客户端示例 -├── kenaito-conf-lua-client/ # (待测试)Lua 客户端 SDK(Lua 5.3+) -├── kenaito-conf-lua-client-demo/ # (待测试)Lua 客户端示例 -├── kenaito-conf-kotlin-client/ # (待测试)Kotlin 客户端 SDK(Kotlin 1.9+) -├── kenaito-conf-kotlin-client-demo/ # (待测试)Kotlin 客户端示例 -├── kenaito-conf-rust-client/ # (待测试)Rust 客户端 SDK(Rust 1.70+) -├── kenaito-conf-rust-client-demo/ # (待测试)Rust 客户端示例 -├── kenaito-conf-ruby-client/ # (待测试)Ruby 客户端 SDK(Ruby 2.7+) -└── kenaito-conf-ruby-client-demo/ # (待测试)Ruby 客户端示例 +├── kenaito-conf-go-client/ # Go 客户端 SDK +├── kenaito-conf-go-client-demo/ # Go 客户端示例 +├── kenaito-conf-springboot2-client/ # Java Spring Boot 2.x 客户端 SDK(JDK 8+) +├── kenaito-conf-springboot2-client-demo/# Spring Boot 2.x 客户端示例(端口 8081) +├── kenaito-conf-python-client/ # Python 客户端 SDK(Python 3.10+) +├── kenaito-conf-python-client-demo/ # Python 客户端示例(端口 8082) +├── kenaito-conf-nodejs-client/ # Node.js 客户端 SDK(Node.js 18+) +├── kenaito-conf-nodejs-client-demo/ # Node.js 客户端示例(端口 8083) +├── kenaito-conf-php-client/ # (未支持)PHP 客户端 SDK(PHP 8.0+) +├── kenaito-conf-php-client-demo/ # (未支持)PHP 客户端示例 +├── kenaito-conf-dotnet-client/ # (未支持).NET 客户端 SDK(.NET 6.0+) +├── kenaito-conf-dotnet-client-demo/ # (未支持).NET 客户端示例 +├── kenaito-conf-c-client/ # (未支持)C 客户端 SDK(libcurl + OpenSSL) +├── kenaito-conf-c-client-demo/ # (未支持)C 客户端示例 +├── kenaito-conf-cpp-client/ # (未支持)C++ 客户端 SDK(libcurl + OpenSSL) +├── kenaito-conf-cpp-client-demo/ # (未支持)C++ 客户端示例 +├── kenaito-conf-lua-client/ # (未支持)Lua 客户端 SDK(Lua 5.3+) +├── kenaito-conf-lua-client-demo/ # (未支持)Lua 客户端示例 +├── kenaito-conf-kotlin-client/ # (未支持)Kotlin 客户端 SDK(Kotlin 1.9+) +├── kenaito-conf-kotlin-client-demo/ # (未支持)Kotlin 客户端示例 +├── kenaito-conf-rust-client/ # (未支持)Rust 客户端 SDK(Rust 1.70+) +├── kenaito-conf-rust-client-demo/ # (未支持)Rust 客户端示例 +├── kenaito-conf-ruby-client/ # (未支持)Ruby 客户端 SDK(Ruby 2.7+) +└── kenaito-conf-ruby-client-demo/ # (未支持)Ruby 客户端示例 ``` ## 快速开始 @@ -132,6 +132,7 @@ dbHost := c.Get("db.host") **1. 引入依赖** ```xml + cn.odboy kenaito-conf-springboot2-client @@ -166,26 +167,8 @@ private String databaseHost; private Integer databasePort; ``` -配置变更时会自动刷新 `@Value` 字段,无需重启应用。详见 [Spring Boot 2 客户端文档](kenaito-conf-springboot2-client/README.md)。 - -### Java Spring Boot 3 客户端 - -适用于 JDK 11+ 项目(当前编译目标 Spring Boot 2.7.18,预留 Spring Boot 3 升级路径)。 - -**1. 引入依赖** - -```xml - - - cn.odboy - kenaito-conf-springboot3-client - 1.0.0 - -``` - -**2. 配置与使用方式同 Spring Boot 2.x 客户端。** - -> **注意:** 升级到 Spring Boot 3 需先将编译目标改为 JDK 17,并更新 `spring-boot.version` 为 3.x、Spring Framework 为 6.x。 +配置变更时会自动刷新 `@Value` +字段,无需重启应用。详见 [Spring Boot 2 客户端文档](kenaito-conf-springboot2-client/README.md)。 ### Python 客户端 @@ -281,225 +264,6 @@ const allConfigs = client.getAll(); client.stop(); ``` -### PHP 客户端 - -适用于 PHP 8.0+ 项目。 - -**1. 安装** - -```bash -cd kenaito-conf-php-client -composer install -``` - -**2. 使用** - -```php -use KenaitoConf\Config; -use KenaitoConf\ConfigClient; - -$config = new Config([ - 'baseUrl' => 'http://localhost:8080', - 'appName' => 'kenaito-springboot-demo', - 'env' => 'daily', - 'syncInterval' => 30, -]); - -$client = new ConfigClient($config); - -$client->onChange(function ($changed) { - foreach ($changed as $k => $v) { - echo "config changed: $k = $v\n"; - } -}); - -$client->start(); -$value = $client->get('database.host'); -$all = $client->getAll(); -``` - -### .NET 客户端 - -适用于 .NET 6.0+ 项目。 - -**1. 引入依赖** - -```xml - - -``` - -**2. 使用** - -```csharp -using KenaitoConf; - -var config = new Config -{ - BaseUrl = "http://localhost:8080", - AppName = "kenaito-springboot-demo", - Env = "daily", - SyncInterval = 30, -}; - -var client = new ConfigClient(config); -client.OnChange(changed => { - foreach (var kv in changed) - Console.WriteLine($"config changed: {kv.Key} = {kv.Value}"); -}); - -await client.StartAsync(); -var value = client.Get("database.host"); -``` - -### C 客户端 - -适用于嵌入式或高性能场景,依赖 libcurl。 - -**编译** - -```bash -gcc -o demo kenaito-conf-c-client/main.c -lcurl -``` - -### C++ 客户端 - -依赖 libcurl,C++17。 - -**编译** - -```bash -g++ -std=c++17 -o demo kenaito-conf-cpp-client/main.cpp -lcurl -lpthread -``` - -### Lua 客户端 - -适用于 Lua 5.3+ 项目(如 OpenResty/Nginx 网关)。 - -**1. 安装** - -```bash -cd kenaito-conf-lua-client -luarocks install kenaito-conf-client-1.0.0-1.rockspec -``` - -**2. 使用** - -```lua -local ConfigClient = require("kenaito-conf") - -local config = { - baseUrl = "http://localhost:8080", - appName = "kenaito-springboot-demo", - env = "daily", - syncInterval = 30, -} - -local client = ConfigClient.new(config) -client:on_change(function(changed) - for k, v in pairs(changed) do - print("config changed: " .. k .. " = " .. v) - end -end) -client:start() -``` - -### Kotlin 客户端 - -适用于 Kotlin 1.9+ 项目。 - -**1. 编译** - -```bash -cd kenaito-conf-kotlin-client -./gradlew build -``` - -**2. 使用** - -```kotlin -import kenait.conf.Config -import kenait.conf.ConfigClient - -val config = Config( - baseUrl = "http://localhost:8080", - appName = "kenaito-springboot-demo", - env = "daily", - syncInterval = 30, -) - -val client = ConfigClient(config) -client.onChange { changed -> - changed.forEach { (k, v) -> println("config changed: $k = $v") } -} -client.start() -val value = client.get("database.host") -``` - -### Rust 客户端 - -适用于 Rust 1.70+ 项目。 - -**1. 在 Cargo.toml 中添加** - -```toml -[dependencies] -kenaito-conf = { path = "../kenaito-conf-rust-client" } -``` - -**2. 使用** - -```rust -use kenaito_conf::{Config, ConfigClient}; - -let config = Config { - base_url: "http://localhost:8080".to_string(), - app_name: "kenaito-springboot-demo".to_string(), - env: "daily".to_string(), - sync_interval: 30, - ..Default::default() -}; - -let client = ConfigClient::new(config); -client.on_change(|changed| { - for (k, v) in changed { println!("config changed: {} = {}", k, v); } -}); -client.start().await.unwrap(); -let value = client.get("database.host"); -``` - -### Ruby 客户端 - -适用于 Ruby 2.7+ 项目。 - -**1. 安装** - -```bash -cd kenaito-conf-ruby-client -gem build kenaito-conf.gemspec -gem install kenaito-conf-1.0.0.gem -``` - -**2. 使用** - -```ruby -require 'kenaito_conf' - -config = KenaitoConf::Config.new( - base_url: 'http://localhost:8080', - app_name: 'kenaito-springboot-demo', - env: 'daily', - sync_interval: 30 -) - -client = KenaitoConf::ConfigClient.new(config) -client.on_change do |changed| - changed.each { |k, v| puts "config changed: #{k} = #{v}" } -end -client.start -value = client.get('database.host') -``` - ## API 概览 ### 管理端 API(需 JWT 认证) diff --git a/kenaito-conf-cpp-client-demo/main.cpp b/kenaito-conf-cpp-client-demo/main.cpp deleted file mode 100644 index bcd6005..0000000 --- a/kenaito-conf-cpp-client-demo/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main() { - std::cout << "Kenaito Config Center C++ Client Demo" << std::endl; - std::cout << "AppName: kenaito-springboot-demo, Env: daily" << std::endl; - std::cout << "See ../kenaito-conf-cpp-client/main.cpp for full implementation" << std::endl; - return 0; -} diff --git a/kenaito-conf-cpp-client/README.md b/kenaito-conf-cpp-client/README.md deleted file mode 100644 index cea9383..0000000 --- a/kenaito-conf-cpp-client/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Kenaito Config Center C++ Client - -C++ 语言的配置中心客户端 SDK,使用 libcurl。 - -## 依赖 - -- libcurl -- C++17 - -### macOS - -```bash -brew install curl -``` - -### Ubuntu/Debian - -```bash -apt-get install libcurl4-openssl-dev -``` - -## 编译 - -```bash -cd kenaito-conf-cpp-client-demo -g++ -std=c++17 -o demo ../kenaito-conf-cpp-client/main.cpp -lcurl -lpthread -./demo -``` - -## 功能 - -- HTTP POST 拉取配置 -- 定时同步 -- 变更回调 -- 线程安全 - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `baseUrl` | 配置中心地址 | 必填 | -| `appName` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `syncInterval` | 同步间隔(秒) | `60` | -| `ignoreGlobal` | 是否忽略全局配置 | `false` | -| `cacheDir` | 本地缓存目录 | `.` | diff --git a/kenaito-conf-cpp-client/main.cpp b/kenaito-conf-cpp-client/main.cpp deleted file mode 100644 index 1d1cccd..0000000 --- a/kenaito-conf-cpp-client/main.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kenaito_conf { - -struct Config { - std::string baseUrl; - std::string appName; - std::string env = "dev"; - std::string version = "latest"; - std::string token; - int syncInterval = 60; - bool ignoreGlobal = false; - std::string cacheDir = "."; -}; - -class ConfigClient { -public: - ConfigClient(const Config &config) : config_(config) {} - - void onChange(std::function &)> handler) { - handlers_.push_back(handler); - } - - std::string get(const std::string &key) { - std::lock_guard lock(mutex_); - auto it = configs_.find(key); - return it != configs_.end() ? it->second : ""; - } - - std::map getAll() { - std::lock_guard lock(mutex_); - return configs_; - } - - void start() { - if (started_) return; - started_ = true; - fetchAll(); - running_ = true; - syncThread_ = std::thread([this]() { - while (running_) { - std::this_thread::sleep_for(std::chrono::seconds(config_.syncInterval)); - if (!running_) break; - try { fetchChanged(); } catch (...) {} - } - }); - } - - void stop() { - running_ = false; - if (syncThread_.joinable()) syncThread_.join(); - } - -private: - Config config_; - std::map configs_; - long lastTime_ = 0; - bool started_ = false; - std::atomic running_{false}; - std::mutex mutex_; - std::thread syncThread_; - std::vector &)>> handlers_; - - void fetchAll() { /* HTTP POST to /api/fetchAllConfigs */ } - void fetchChanged() { /* HTTP POST to /api/fetchChangedConfigs */ } -}; - -} // namespace kenaito_conf - -int main() { - std::cout << "Kenaito Config Center C++ Client Demo" << std::endl; - - kenaito_conf::Config config; - config.baseUrl = "http://localhost:8080"; - config.appName = "kenaito-springboot-demo"; - config.env = "daily"; - config.version = "latest"; - config.syncInterval = 30; - - kenaito_conf::ConfigClient client(config); - - client.onChange([](const std::map &changed) { - for (auto &[k, v] : changed) - std::cout << "config changed: " << k << " = " << v << std::endl; - }); - - client.start(); - std::cout << "database.host = " << client.get("database.host") << std::endl; - std::cout << "Listening for changes... (press Ctrl+C to stop)" << std::endl; - - std::cin.get(); - client.stop(); - return 0; -} diff --git a/kenaito-conf-dotnet-client-demo/KenaitoConfDemo.csproj b/kenaito-conf-dotnet-client-demo/KenaitoConfDemo.csproj deleted file mode 100644 index 9ad37e4..0000000 --- a/kenaito-conf-dotnet-client-demo/KenaitoConfDemo.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Exe - net6.0 - - - - - diff --git a/kenaito-conf-dotnet-client-demo/Program.cs b/kenaito-conf-dotnet-client-demo/Program.cs deleted file mode 100644 index 828008e..0000000 --- a/kenaito-conf-dotnet-client-demo/Program.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using KenaitoConf; - -var configValues = new Dictionary -{ - { "database.host", Environment.GetEnvironmentVariable("DATABASE_HOST") ?? "192.168.1.1" }, - { "database.port", Environment.GetEnvironmentVariable("DATABASE_PORT") ?? "8888" }, - { "database.name", Environment.GetEnvironmentVariable("DATABASE_NAME") ?? "xxxx" }, - { "database.user", Environment.GetEnvironmentVariable("DATABASE_USER") ?? "xxxx" }, - { "database.password", Environment.GetEnvironmentVariable("DATABASE_PASSWORD") ?? "xxxxxxxx" }, -}; - -var config = new Config -{ - BaseUrl = Environment.GetEnvironmentVariable("KENAITO_BASE_URL") ?? "http://localhost:8080", - AppName = Environment.GetEnvironmentVariable("KENAITO_APP_NAME") ?? "kenaito-springboot-demo", - Env = Environment.GetEnvironmentVariable("KENAITO_PROFILE") ?? "daily", - Version = Environment.GetEnvironmentVariable("KENAITO_VERSION") ?? "latest", - Token = Environment.GetEnvironmentVariable("KENAITO_TOKEN") ?? "", - SyncInterval = int.Parse(Environment.GetEnvironmentVariable("KENAITO_SYNC_INTERVAL") ?? "10"), - CacheDir = Environment.GetEnvironmentVariable("KENAITO_CACHE_DIR") ?? ".", -}; - -var client = new ConfigClient(config); - -client.OnChange(changed => -{ - foreach (var kv in changed) - { - if (configValues.ContainsKey(kv.Key)) - { - configValues[kv.Key] = kv.Value; - Console.Error.WriteLine($"config updated: {kv.Key} = {kv.Value}"); - } - } -}); - -try -{ - await client.StartAsync(); - var remote = client.GetAll(); - foreach (var key in new List(configValues.Keys)) - { - if (remote.ContainsKey(key)) - configValues[key] = remote[key]; - } - Console.Error.WriteLine($"loaded {remote.Count} configs from remote"); -} -catch (Exception e) -{ - Console.Error.WriteLine($"failed to init config client: {e.Message}"); -} - -Console.WriteLine("Current configs:"); -foreach (var kv in configValues) - Console.WriteLine($" {kv.Key} = {kv.Value}"); - -Console.WriteLine("\nListening for config changes... (press Ctrl+C to stop)"); -var cts = new System.Threading.CancellationTokenSource(); -Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); }; -try { await System.Threading.Tasks.Task.Delay(System.Threading.Timeout.Infinite, cts.Token); } -catch (System.Threading.Tasks.TaskCanceledException) { } -client.Stop(); -Console.WriteLine("Shutting down..."); diff --git a/kenaito-conf-dotnet-client/KenaitoConf.cs b/kenaito-conf-dotnet-client/KenaitoConf.cs deleted file mode 100644 index 971528d..0000000 --- a/kenaito-conf-dotnet-client/KenaitoConf.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace KenaitoConf -{ - public class Config - { - public string BaseUrl { get; set; } = ""; - public string AppName { get; set; } = ""; - public string Env { get; set; } = "dev"; - public string Version { get; set; } = "latest"; - public string Token { get; set; } = ""; - public int SyncInterval { get; set; } = 60; - public bool IgnoreGlobal { get; set; } = false; - public string CacheDir { get; set; } = "."; - } - - public class Cache - { - private readonly string _cacheDir; - private const string CacheFile = "config-cache.json"; - - public Cache(string cacheDir = ".") - { - _cacheDir = cacheDir; - } - - public void Write(Dictionary data) - { - string path = Path.Combine(_cacheDir, CacheFile); - File.WriteAllText(path, JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true })); - } - - public Dictionary Read() - { - string path = Path.Combine(_cacheDir, CacheFile); - if (!File.Exists(path)) return null; - string content = File.ReadAllText(path); - return JsonSerializer.Deserialize>(content); - } - } - - public class ConfigClient - { - private readonly Config _config; - private readonly Cache _cache; - private readonly HttpClient _http = new(); - private readonly Dictionary _configs = new(); - private long _lastTime = 0; - private bool _started = false; - private CancellationTokenSource _cts; - private readonly List>> _handlers = new(); - - public ConfigClient(Config config) - { - _config = config; - _cache = new Cache(config.CacheDir); - _http.Timeout = TimeSpan.FromSeconds(10); - } - - public void OnChange(Action> handler) - { - _handlers.Add(handler); - } - - public string Get(string key) - { - return _configs.TryGetValue(key, out var value) ? value : null; - } - - public Dictionary GetAll() - { - return new Dictionary(_configs); - } - - public async Task StartAsync() - { - if (_started) return; - _started = true; - - try - { - await FetchAllAsync(); - } - catch (Exception e) - { - Console.Error.WriteLine($"[ConfigClient] fetch all configs error: {e.Message}, trying cache"); - var cached = _cache.Read(); - if (cached != null) - { - ApplyConfigs(cached); - Console.Error.WriteLine($"[ConfigClient] loaded {_configs.Count} configs from cache"); - } - else - { - throw new Exception($"fetch configs failed and no cache: {e.Message}"); - } - } - - _cts = new CancellationTokenSource(); - _ = SyncLoopAsync(_cts.Token); - } - - public void Stop() - { - _cts?.Cancel(); - } - - private async Task SyncLoopAsync(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - await Task.Delay(_config.SyncInterval * 1000, ct); - try - { - await FetchChangedAsync(); - } - catch (Exception e) - { - Console.Error.WriteLine($"[ConfigClient] sync error: {e.Message}"); - } - } - } - - private async Task FetchAllAsync() - { - string url = $"{_config.BaseUrl}/api/fetchAllConfigs"; - var payload = new - { - appName = _config.AppName, - env = _config.Env, - version = _config.Version, - ignoreGlobal = _config.IgnoreGlobal, - token = _config.Token - }; - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - var resp = await _http.PostAsync(url, content); - resp.EnsureSuccessStatusCode(); - string body = await resp.Content.ReadAsStringAsync(); - var data = JsonSerializer.Deserialize>(body); - - if (data.ContainsKey("code")) - { - var code = Convert.ToInt32(data["code"]); - if (code != 0) - throw new Exception($"server error: {(data.ContainsKey("msg") ? data["msg"] : "unknown")}"); - } - - if (data.TryGetValue("latest.fetch.timestamp", out var ts)) - _lastTime = Convert.ToInt64(ts); - - _cache.Write(data); - ApplyConfigs(data); - Console.Error.WriteLine($"[ConfigClient] fetched {_configs.Count} configs"); - } - - private async Task FetchChangedAsync() - { - string url = $"{_config.BaseUrl}/api/fetchChangedConfigs"; - var payload = new - { - appName = _config.AppName, - env = _config.Env, - version = _config.Version, - lastTime = _lastTime, - ignoreGlobal = _config.IgnoreGlobal, - token = _config.Token - }; - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - var resp = await _http.PostAsync(url, content); - resp.EnsureSuccessStatusCode(); - string body = await resp.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize>(body); - - if (!result.ContainsKey("code") || Convert.ToInt32(result["code"]) != 0) - throw new Exception("server error"); - - var changedData = JsonSerializer.Deserialize>(result["data"].ToString()); - if (changedData.TryGetValue("latest.fetch.timestamp", out var ts)) - { - _lastTime = Convert.ToInt64(ts); - changedData.Remove("latest.fetch.timestamp"); - } - - var changed = new Dictionary(); - foreach (var kv in changedData) - { - changed[kv.Key] = kv.Value?.ToString() ?? ""; - } - - foreach (var kv in changed) - _configs[kv.Key] = kv.Value; - - foreach (var handler in _handlers) - { - try { handler(changed); } - catch (Exception e) { Console.Error.WriteLine($"[ConfigClient] handler error: {e.Message}"); } - } - } - - private void ApplyConfigs(Dictionary data) - { - foreach (var kv in data) - { - if (kv.Key.StartsWith("kenaito.") || kv.Key == "latest.fetch.timestamp") - continue; - if (kv.Value is JsonElement) - continue; - _configs[kv.Key] = kv.Value?.ToString() ?? ""; - } - } - } -} diff --git a/kenaito-conf-dotnet-client/KenaitoConf.csproj b/kenaito-conf-dotnet-client/KenaitoConf.csproj deleted file mode 100644 index 997bbec..0000000 --- a/kenaito-conf-dotnet-client/KenaitoConf.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - net6.0 - KenaitoConf - KenaitoConf - - diff --git a/kenaito-conf-dotnet-client/README.md b/kenaito-conf-dotnet-client/README.md deleted file mode 100644 index 44c9ab0..0000000 --- a/kenaito-conf-dotnet-client/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Kenaito Config Center .NET Client - -.NET 语言的配置中心客户端 SDK,支持 .NET 6.0+。 - -## 依赖 - -- .NET 6.0+ -- System.Text.Json(内置) - -## 安装 - -### 项目引用 - -在 `.csproj` 中添加: - -```xml - - - -``` - -### NuGet 包(待发布) - -```bash -dotnet add package KenaitoConf -``` - -## 使用 - -```csharp -using KenaitoConf; - -var config = new Config -{ - BaseUrl = "http://localhost:8080", - AppName = "kenaito-springboot-demo", - Env = "daily", - Version = "latest", - SyncInterval = 30, -}; - -var client = new ConfigClient(config); - -client.OnChange(changed => -{ - foreach (var kv in changed) - Console.WriteLine($"config changed: {kv.Key} = {kv.Value}"); -}); - -await client.StartAsync(); - -var value = client.Get("database.host"); -var all = client.GetAll(); - -client.Stop(); -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `BaseUrl` | 配置中心地址 | 必填 | -| `AppName` | 应用名称 | 必填 | -| `Env` | 环境 | `dev` | -| `Version` | 配置版本 | `latest` | -| `Token` | 认证令牌 | 空 | -| `SyncInterval` | 同步间隔(秒) | `60` | -| `IgnoreGlobal` | 是否忽略全局配置 | `false` | -| `CacheDir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-dotnet-client-demo -dotnet run -``` diff --git a/kenaito-conf-kotlin-client-demo/build.gradle.kts b/kenaito-conf-kotlin-client-demo/build.gradle.kts deleted file mode 100644 index 1fa000d..0000000 --- a/kenaito-conf-kotlin-client-demo/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - kotlin("jvm") version "1.9.0" - application -} - -repositories { mavenCentral() } - -dependencies { - implementation(files("../kenaito-conf-kotlin-client/build/libs/kenaito-conf-kotlin-client-1.0.0.jar")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") -} - -application { mainClass.set("demo.AppKt") } diff --git a/kenaito-conf-kotlin-client-demo/settings.gradle.kts b/kenaito-conf-kotlin-client-demo/settings.gradle.kts deleted file mode 100644 index b3199d5..0000000 --- a/kenaito-conf-kotlin-client-demo/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "kenaito-conf-kotlin-client-demo" diff --git a/kenaito-conf-kotlin-client-demo/src/main/kotlin/demo/App.kt b/kenaito-conf-kotlin-client-demo/src/main/kotlin/demo/App.kt deleted file mode 100644 index fb6f25b..0000000 --- a/kenaito-conf-kotlin-client-demo/src/main/kotlin/demo/App.kt +++ /dev/null @@ -1,49 +0,0 @@ -import kenait.conf.Config -import kenait.conf.ConfigClient - -fun main() { - val configValues = mutableMapOf( - "database.host" to (System.getenv("DATABASE_HOST") ?: "192.168.1.1"), - "database.port" to (System.getenv("DATABASE_PORT") ?: "8888"), - "database.name" to (System.getenv("DATABASE_NAME") ?: "xxxx"), - "database.user" to (System.getenv("DATABASE_USER") ?: "xxxx"), - "database.password" to (System.getenv("DATABASE_PASSWORD") ?: "xxxxxxxx"), - ) - - val config = Config( - baseUrl = System.getenv("KENAITO_BASE_URL") ?: "http://localhost:8080", - appName = System.getenv("KENAITO_APP_NAME") ?: "kenaito-springboot-demo", - env = System.getenv("KENAITO_PROFILE") ?: "daily", - version = System.getenv("KENAITO_VERSION") ?: "latest", - token = System.getenv("KENAITO_TOKEN") ?: "", - syncInterval = (System.getenv("KENAITO_SYNC_INTERVAL") ?: "10").toInt(), - cacheDir = System.getenv("KENAITO_CACHE_DIR") ?: ".", - ) - - val client = ConfigClient(config) - - client.onChange { changed -> - changed.forEach { (k, v) -> - if (configValues.containsKey(k)) { - configValues[k] = v - System.err.println("config updated: $k = $v") - } - } - } - - try { - client.start() - val remote = client.getAll() - configValues.keys.forEach { k -> - remote[k]?.let { configValues[k] = it } - } - System.err.println("loaded ${remote.size} configs from remote") - } catch (e: Exception) { - System.err.println("failed to init config client: ${e.message}") - } - - println("Current configs:") - configValues.forEach { (k, v) -> println(" $k = $v") } - println("\nListening for config changes... (press Ctrl+C to stop)") - Thread.currentThread().join() -} diff --git a/kenaito-conf-kotlin-client/README.md b/kenaito-conf-kotlin-client/README.md deleted file mode 100644 index 8c26093..0000000 --- a/kenaito-conf-kotlin-client/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Kenaito Config Center Kotlin Client - -Kotlin 语言的配置中心客户端 SDK。 - -## 依赖 - -- Kotlin 1.9+ -- kotlinx-coroutines -- kotlinx-serialization - -## 编译 - -```bash -cd kenaito-conf-kotlin-client -./gradlew build -``` - -## 使用 - -```kotlin -import kenait.conf.Config -import kenait.conf.ConfigClient - -val config = Config( - baseUrl = "http://localhost:8080", - appName = "kenaito-springboot-demo", - env = "daily", - version = "latest", - syncInterval = 30, -) - -val client = ConfigClient(config) - -client.onChange { changed -> - changed.forEach { (k, v) -> - println("config changed: $k = $v") - } -} - -client.start() - -val value = client.get("database.host") -val all = client.getAll() - -client.stop() -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `baseUrl` | 配置中心地址 | 必填 | -| `appName` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `syncInterval` | 同步间隔(秒) | `60` | -| `ignoreGlobal` | 是否忽略全局配置 | `false` | -| `cacheDir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-kotlin-client-demo -./gradlew run -``` diff --git a/kenaito-conf-kotlin-client/build.gradle.kts b/kenaito-conf-kotlin-client/build.gradle.kts deleted file mode 100644 index 34be1fa..0000000 --- a/kenaito-conf-kotlin-client/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - kotlin("jvm") version "1.9.0" - kotlin("plugin.serialization") version "1.9.0" -} - -group = "cn.odboy" -version = "1.0.0" - -repositories { mavenCentral() } - -dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") -} diff --git a/kenaito-conf-kotlin-client/settings.gradle.kts b/kenaito-conf-kotlin-client/settings.gradle.kts deleted file mode 100644 index cd8c89d..0000000 --- a/kenaito-conf-kotlin-client/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "kenaito-conf-kotlin-client" diff --git a/kenaito-conf-kotlin-client/src/main/kotlin/kenaito/conf/ConfigClient.kt b/kenaito-conf-kotlin-client/src/main/kotlin/kenaito/conf/ConfigClient.kt deleted file mode 100644 index 3f354a1..0000000 --- a/kenaito-conf-kotlin-client/src/main/kotlin/kenaito/conf/ConfigClient.kt +++ /dev/null @@ -1,107 +0,0 @@ -package kenaito.conf - -import kotlinx.coroutines.* -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import java.io.File -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse -import java.time.Duration -import java.util.concurrent.ConcurrentHashMap - -@Serializable -data class Config( - val baseUrl: String = "", - val appName: String = "", - val env: String = "dev", - val version: String = "latest", - val token: String = "", - val syncInterval: Int = 60, - val ignoreGlobal: Boolean = false, - val cacheDir: String = "." -) - -class Cache(private val cacheDir: String) { - private val cacheFile = File(cacheDir, "config-cache.json") - fun write(data: String) { cacheFile.writeText(data) } - fun read(): String? = if (cacheFile.exists()) cacheFile.readText() else null -} - -class ConfigClient(private val config: Config) { - private val httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build() - private val configs = ConcurrentHashMap() - private var lastTime = 0L - private var started = false - private val handlers = mutableListOf<(Map) -> Unit>() - private val json = Json { ignoreUnknownKeys = true } - - fun onChange(handler: (Map) -> Unit) { handlers.add(handler) } - fun get(key: String): String? = configs[key] - fun getAll(): Map = HashMap(configs) - - fun start() { - if (started) return - started = true - try { fetchAll() } catch (e: Exception) { - System.err.println("[ConfigClient] fetch all configs error: ${e.message}, trying cache") - val cached = Cache(config.cacheDir).read() - if (cached != null) { - applyConfigs(cached) - System.err.println("[ConfigClient] loaded ${configs.size} configs from cache") - } else throw RuntimeException("fetch configs failed and no cache: ${e.message}") - } - CoroutineScope(Dispatchers.IO).launch { - while (true) { - delay(config.syncInterval * 1000L) - try { fetchChanged() } catch (e: Exception) { - System.err.println("[ConfigClient] sync error: ${e.message}") - } - } - } - } - - fun stop() { started = false } - - private fun fetchAll() { - val url = "${config.baseUrl}/api/fetchAllConfigs" - val body = """{"appName":"${config.appName}","env":"${config.env}","version":"${config.version}","ignoreGlobal":${config.ignoreGlobal},"token":"${config.token}"}""" - val req = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(body)) - .header("Content-Type", "application/json").timeout(Duration.ofSeconds(10)).build() - val resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString()) - if (resp.statusCode() != 200) throw RuntimeException("HTTP ${resp.statusCode()}") - val data = json.parseToJsonElement(resp.body()).jsonObject - if (data.containsKey("code") && data["code"]!!.jsonPrimitive.int != 0) - throw RuntimeException("server error: ${data["code"]}") - data["latest.fetch.timestamp"]?.let { lastTime = it.jsonPrimitive.content.toLong() } - Cache(config.cacheDir).write(resp.body()) - applyConfigs(resp.body()) - System.err.println("[ConfigClient] fetched ${configs.size} configs") - } - - private fun fetchChanged() { - val url = "${config.baseUrl}/api/fetchChangedConfigs" - val body = """{"appName":"${config.appName}","env":"${config.env}","version":"${config.version}","lastTime":$lastTime,"ignoreGlobal":${config.ignoreGlobal},"token":"${config.token}"}""" - val req = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(body)) - .header("Content-Type", "application/json").timeout(Duration.ofSeconds(10)).build() - val resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString()) - val result = json.parseToJsonElement(resp.body()).jsonObject - if (!result.containsKey("code") || result["code"]!!.jsonPrimitive.int != 0) - throw RuntimeException("server error") - val changedData = result["data"]!!.jsonObject.toMutableMap() - changedData.remove("latest.fetch.timestamp")?.let { lastTime = it.jsonPrimitive.content.toLong() } - val changed = mutableMapOf() - changedData.forEach { (k, v) -> changed[k] = v.jsonPrimitive.content } - configs.putAll(changed) - handlers.forEach { try { it(changed) } catch (e: Exception) { System.err.println("[ConfigClient] handler error: ${e.message}") } } - } - - private fun applyConfigs(jsonStr: String) { - val data = json.parseToJsonElement(jsonStr).jsonObject - data.forEach { (k, v) -> - if (!k.startsWith("kenaito.") && k != "latest.fetch.timestamp") - configs[k] = v.jsonPrimitive.content - } - } -} diff --git a/kenaito-conf-lua-client-demo/app.lua b/kenaito-conf-lua-client-demo/app.lua deleted file mode 100644 index 9e1ec12..0000000 --- a/kenaito-conf-lua-client-demo/app.lua +++ /dev/null @@ -1,49 +0,0 @@ -local ConfigClient = require("kenaito-conf") - -local config_values = { - ["database.host"] = os.getenv("DATABASE_HOST") or "192.168.1.1", - ["database.port"] = os.getenv("DATABASE_PORT") or "8888", - ["database.name"] = os.getenv("DATABASE_NAME") or "xxxx", - ["database.user"] = os.getenv("DATABASE_USER") or "xxxx", - ["database.password"] = os.getenv("DATABASE_PASSWORD") or "xxxxxxxx", -} - -local config = { - baseUrl = os.getenv("KENAITO_BASE_URL") or "http://localhost:8080", - appName = os.getenv("KENAITO_APP_NAME") or "kenaito-springboot-demo", - env = os.getenv("KENAITO_PROFILE") or "daily", - version = os.getenv("KENAITO_VERSION") or "latest", - token = os.getenv("KENAITO_TOKEN") or "", - syncInterval = tonumber(os.getenv("KENAITO_SYNC_INTERVAL") or "10"), - cacheDir = os.getenv("KENAITO_CACHE_DIR") or ".", -} - -local client = ConfigClient.new(config) - -client:on_change(function(changed) - for k, v in pairs(changed) do - if config_values[k] then - config_values[k] = v - print("config updated: " .. k .. " = " .. v) - end - end -end) - -client:start() - -local remote = client:get_all() -for k, v in pairs(remote) do - if config_values[k] then - config_values[k] = v - end -end - -print("Current configs:") -for k, v in pairs(config_values) do - print(" " .. k .. " = " .. v) -end - -print("\nListening for config changes... (press Ctrl+C to stop)") -while true do - os.execute("sleep 1") -end diff --git a/kenaito-conf-lua-client/README.md b/kenaito-conf-lua-client/README.md deleted file mode 100644 index c3c552a..0000000 --- a/kenaito-conf-lua-client/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Kenaito Config Center Lua Client - -Lua 语言的配置中心客户端 SDK。 - -## 依赖 - -- Lua 5.3+ -- LuaSocket -- lua-cjson -- lua-openssl - -## 安装 - -```bash -luarocks install kenaito-conf-client-1.0.0-1.rockspec -``` - -或手动安装依赖: - -```bash -luarocks install luasocket -luarocks install lua-cjson -luarocks install lua-openssl -``` - -## 使用 - -```lua -local ConfigClient = require("kenaito-conf") - -local config = { - baseUrl = "http://localhost:8080", - appName = "kenaito-springboot-demo", - env = "daily", - version = "latest", - syncInterval = 30, -} - -local client = ConfigClient.new(config) - -client:on_change(function(changed) - for k, v in pairs(changed) do - print("config changed: " .. k .. " = " .. v) - end -end) - -client:start() - -local value = client:get("database.host") -local all = client:get_all() - -client:stop() -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `baseUrl` | 配置中心地址 | 必填 | -| `appName` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `syncInterval` | 同步间隔(秒) | `60` | -| `ignoreGlobal` | 是否忽略全局配置 | `false` | -| `cacheDir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-lua-client-demo -lua app.lua -``` diff --git a/kenaito-conf-lua-client/client.lua b/kenaito-conf-lua-client/client.lua deleted file mode 100644 index 9488b2e..0000000 --- a/kenaito-conf-lua-client/client.lua +++ /dev/null @@ -1,84 +0,0 @@ -local http = require("socket.http") -local ltn12 = require("ltn12") -local cjson = require("cjson") - -local ConfigClient = {} -ConfigClient.__index = ConfigClient - -function ConfigClient.new(config) - local self = setmetatable({}, ConfigClient) - self.config = config or {} - self.config.baseUrl = self.config.baseUrl or "" - self.config.appName = self.config.appName or "" - self.config.env = self.config.env or "dev" - self.config.version = self.config.version or "latest" - self.config.token = self.config.token or "" - self.config.syncInterval = self.config.syncInterval or 60 - self.config.ignoreGlobal = self.config.ignoreGlobal or false - self.config.cacheDir = self.config.cacheDir or "." - - self.configs = {} - self.lastTime = 0 - self.handlers = {} - return self -end - -function ConfigClient:on_change(handler) - table.insert(self.handlers, handler) -end - -function ConfigClient:get(key) - return self.configs[key] -end - -function ConfigClient:get_all() - return self.configs -end - -function ConfigClient:start() - -- Fetch all configs - local url = self.config.baseUrl .. "/api/fetchAllConfigs" - local payload = cjson.encode({ - appName = self.config.appName, - env = self.config.env, - version = self.config.version, - ignoreGlobal = self.config.ignoreGlobal, - token = self.config.token - }) - - local response_body = {} - local res, code, response_headers = http.request{ - url = url, - method = "POST", - headers = { - ["Content-Type"] = "application/json", - ["Content-Length"] = #payload - }, - source = ltn12.source.string(payload), - sink = ltn12.sink.table(response_body) - } - - if code == 200 then - local data = cjson.decode(table.concat(response_body)) - - if data["latest.fetch.timestamp"] then - self.lastTime = tonumber(data["latest.fetch.timestamp"]) - end - - for k, v in pairs(data) do - if not string.match(k, "^kenaito%.") and k ~= "latest.fetch.timestamp" then - if type(v) == "string" then - self.configs[k] = v - end - end - end - - print("[ConfigClient] fetched " .. #self.configs .. " configs") - end -end - -function ConfigClient:stop() - -- Cleanup -end - -return ConfigClient diff --git a/kenaito-conf-lua-client/kenaito-conf-client-1.0.0-1.rockspec b/kenaito-conf-lua-client/kenaito-conf-client-1.0.0-1.rockspec deleted file mode 100644 index 49909bd..0000000 --- a/kenaito-conf-lua-client/kenaito-conf-client-1.0.0-1.rockspec +++ /dev/null @@ -1,22 +0,0 @@ -package = "kenaito-conf-client" -version = "1.0.0-1" -source = { - url = "git+https://gitea.odboy.cn/odboy/kenaito-conf.git" -} -description = { - summary = "Kenaito Config Center Lua Client", - homepage = "https://gitea.odboy.cn/odboy/kenaito-conf", - license = "MIT" -} -dependencies = { - "lua >= 5.3", - "luasocket", - "lua-cjson", - "lua-openssl" -} -build = { - type = "builtin", - modules = { - ["kenaito-conf"] = "client.lua" - } -} diff --git a/kenaito-conf-php-client-demo/app.php b/kenaito-conf-php-client-demo/app.php deleted file mode 100644 index 47178eb..0000000 --- a/kenaito-conf-php-client-demo/app.php +++ /dev/null @@ -1,61 +0,0 @@ - getenv('DATABASE_HOST') ?: '192.168.1.1', - 'database.port' => getenv('DATABASE_PORT') ?: '8888', - 'database.name' => getenv('DATABASE_NAME') ?: 'xxxx', - 'database.user' => getenv('DATABASE_USER') ?: 'xxxx', - 'database.password' => getenv('DATABASE_PASSWORD') ?: 'xxxxxxxx', -]; - -$config = new Config([ - 'baseUrl' => getenv('KENAITO_BASE_URL') ?: 'http://localhost:8080', - 'appName' => getenv('KENAITO_APP_NAME') ?: 'kenaito-springboot-demo', - 'env' => getenv('KENAITO_PROFILE') ?: 'daily', - 'version' => getenv('KENAITO_VERSION') ?: 'latest', - 'token' => getenv('KENAITO_TOKEN') ?: '', - 'syncInterval' => (int)(getenv('KENAITO_SYNC_INTERVAL') ?: 10), - 'cacheDir' => getenv('KENAITO_CACHE_DIR') ?: '.', -]); - -$client = new ConfigClient($config); - -$client->onChange(function ($changed) use (&$configValues) { - foreach ($changed as $k => $v) { - if (isset($configValues[$k])) { - $configValues[$k] = $v; - error_log("config updated: {$k} = {$v}"); - } - } -}); - -try { - $client->start(); - $remote = $client->getAll(); - foreach ($configValues as $k => &$v) { - if (isset($remote[$k])) { - $v = $remote[$k]; - } - } - error_log("loaded " . count($remote) . " configs from remote"); -} catch (Exception $e) { - error_log("failed to init config client: " . $e->getMessage()); -} - -$port = (int)(getenv('SERVER_PORT') ?: 8084); - -echo "Server started on port {$port}\n"; -echo "Current configs:\n"; -foreach ($configValues as $k => $v) { - echo " {$k} = {$v}\n"; -} - -while (true) { - pcntl_signal_dispatch(); - usleep(100000); -} diff --git a/kenaito-conf-php-client/README.md b/kenaito-conf-php-client/README.md deleted file mode 100644 index 248aeaf..0000000 --- a/kenaito-conf-php-client/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# Kenaito Config Center PHP Client - -PHP 语言的配置中心客户端 SDK。 - -## 依赖 - -- PHP 8.0+ -- ext-curl -- ext-openssl -- ext-pcntl - -## 安装 - -### Composer - -```bash -cd kenaito-conf-php-client -composer install -``` - -或在项目中引用: - -```json -{ - "require": { - "kenaito/conf-client": "*" - }, - "repositories": [ - { - "type": "path", - "url": "../kenaito-conf-php-client" - } - ] -} -``` - -### 手动引入 - -```php -require_once 'kenaito-conf-php-client/autoload.php'; -``` - -## 使用 - -```php -use KenaitoConf\Config; -use KenaitoConf\ConfigClient; - -$config = new Config([ - 'baseUrl' => 'http://localhost:8080', - 'appName' => 'kenaito-springboot-demo', - 'env' => 'daily', - 'version' => 'latest', - 'syncInterval' => 30, -]); - -$client = new ConfigClient($config); - -$client->onChange(function ($changed) { - foreach ($changed as $k => $v) { - echo "config changed: $k = $v\n"; - } -}); - -$client->start(); - -$value = $client->get('database.host'); -$all = $client->getAll(); - -$client->stop(); -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `baseUrl` | 配置中心地址 | 必填 | -| `appName` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `syncInterval` | 同步间隔(秒) | `60` | -| `ignoreGlobal` | 是否忽略全局配置 | `false` | -| `cacheDir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-php-client-demo -php app.php -``` diff --git a/kenaito-conf-php-client/autoload.php b/kenaito-conf-php-client/autoload.php deleted file mode 100644 index c34178c..0000000 --- a/kenaito-conf-php-client/autoload.php +++ /dev/null @@ -1,6 +0,0 @@ -=8.0", - "ext-curl": "*", - "ext-openssl": "*", - "ext-pcntl": "*" - }, - "autoload": { - "psr-4": { - "KenaitoConf\\": "src/" - } - } -} diff --git a/kenaito-conf-php-client/src/Cache.php b/kenaito-conf-php-client/src/Cache.php deleted file mode 100644 index 58cc31f..0000000 --- a/kenaito-conf-php-client/src/Cache.php +++ /dev/null @@ -1,30 +0,0 @@ -cacheDir = $cacheDir; - } - - public function write(array $data): void - { - $path = $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_FILE; - file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - } - - public function read(): ?array - { - $path = $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_FILE; - if (!file_exists($path)) { - return null; - } - $content = file_get_contents($path); - return json_decode($content, true); - } -} diff --git a/kenaito-conf-php-client/src/Config.php b/kenaito-conf-php-client/src/Config.php deleted file mode 100644 index 1b4c65a..0000000 --- a/kenaito-conf-php-client/src/Config.php +++ /dev/null @@ -1,24 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->$key = $value; - } - } - } -} diff --git a/kenaito-conf-php-client/src/ConfigClient.php b/kenaito-conf-php-client/src/ConfigClient.php deleted file mode 100644 index 7f88dcb..0000000 --- a/kenaito-conf-php-client/src/ConfigClient.php +++ /dev/null @@ -1,179 +0,0 @@ -config = $config; - $this->cache = new Cache($config->cacheDir); - } - - public function onChange(callable $handler): void - { - $this->handlers[] = $handler; - } - - public function get(string $key): ?string - { - return $this->configs[$key] ?? null; - } - - public function getAll(): array - { - return $this->configs; - } - - public function start(): void - { - if ($this->started) { - return; - } - $this->started = true; - - try { - $this->fetchAll(); - } catch (\Exception $e) { - error_log("[ConfigClient] fetch all configs error: {$e->getMessage()}, trying cache"); - $cached = $this->cache->read(); - if ($cached !== null) { - $this->applyConfigs($cached); - error_log("[ConfigClient] loaded " . count($this->configs) . " configs from cache"); - } else { - throw new \RuntimeException("fetch configs failed and no cache: {$e->getMessage()}"); - } - } - - $this->timer = pcntl_alarm(0); - pcntl_signal(SIGALRM, [$this, 'syncTick']); - pcntl_alarm($this->config->syncInterval); - } - - public function syncTick(): void - { - try { - $this->fetchChanged(); - } catch (\Exception $e) { - error_log("[ConfigClient] sync error: {$e->getMessage()}"); - } - pcntl_alarm($this->config->syncInterval); - } - - public function stop(): void - { - if ($this->timer !== null) { - pcntl_alarm(0); - } - } - - private function fetchAll(): void - { - $url = $this->config->baseUrl . '/api/fetchAllConfigs'; - $payload = [ - 'appName' => $this->config->appName, - 'env' => $this->config->env, - 'version' => $this->config->version, - 'ignoreGlobal' => $this->config->ignoreGlobal, - 'token' => $this->config->token, - ]; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ($httpCode !== 200) { - throw new \RuntimeException("HTTP {$httpCode}"); - } - - $data = json_decode($response, true); - if (isset($data['code']) && $data['code'] !== 0) { - throw new \RuntimeException("server error: " . ($data['msg'] ?? 'unknown')); - } - - if (isset($data['latest.fetch.timestamp'])) { - $this->lastTime = (int)$data['latest.fetch.timestamp']; - } - - $this->cache->write($data); - $this->applyConfigs($data); - error_log("[ConfigClient] fetched " . count($this->configs) . " configs"); - } - - private function fetchChanged(): void - { - $url = $this->config->baseUrl . '/api/fetchChangedConfigs'; - $payload = [ - 'appName' => $this->config->appName, - 'env' => $this->config->env, - 'version' => $this->config->version, - 'lastTime' => $this->lastTime, - 'ignoreGlobal' => $this->config->ignoreGlobal, - 'token' => $this->config->token, - ]; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $response = curl_exec($ch); - curl_close($ch); - - $result = json_decode($response, true); - if (!isset($result['code']) || $result['code'] !== 0) { - throw new \RuntimeException("server error code: " . ($result['code'] ?? 'unknown')); - } - - $changedData = $result['data'] ?? []; - if (isset($changedData['latest.fetch.timestamp'])) { - $this->lastTime = (int)$changedData['latest.fetch.timestamp']; - unset($changedData['latest.fetch.timestamp']); - } - - $changed = []; - foreach ($changedData as $k => $v) { - $changed[$k] = $v; - } - - $this->configs = array_merge($this->configs, $changed); - - foreach ($this->handlers as $handler) { - try { - $handler($changed); - } catch (\Exception $e) { - error_log("[ConfigClient] change handler error: {$e->getMessage()}"); - } - } - - error_log("[ConfigClient] " . count($changed) . " configs changed"); - } - - private function applyConfigs(array $data): void - { - foreach ($data as $k => $v) { - if (str_starts_with($k, 'kenaito.') || $k === 'latest.fetch.timestamp') { - continue; - } - if (!is_string($v)) { - continue; - } - $this->configs[$k] = $v; - } - } -} diff --git a/kenaito-conf-ruby-client-demo/app.rb b/kenaito-conf-ruby-client-demo/app.rb deleted file mode 100644 index fced87f..0000000 --- a/kenaito-conf-ruby-client-demo/app.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require_relative '../kenaito-conf-ruby-client/lib/kenaito_conf' - -config_values = { - 'database.host' => ENV['DATABASE_HOST'] || '192.168.1.1', - 'database.port' => ENV['DATABASE_PORT'] || '8888', - 'database.name' => ENV['DATABASE_NAME'] || 'xxxx', - 'database.user' => ENV['DATABASE_USER'] || 'xxxx', - 'database.password' => ENV['DATABASE_PASSWORD'] || 'xxxxxxxx' -} - -config = KenaitoConf::Config.new( - base_url: ENV['KENAITO_BASE_URL'] || 'http://localhost:8080', - app_name: ENV['KENAITO_APP_NAME'] || 'kenaito-springboot-demo', - env: ENV['KENAITO_PROFILE'] || 'daily', - version: ENV['KENAITO_VERSION'] || 'latest', - token: ENV['KENAITO_TOKEN'] || '', - sync_interval: (ENV['KENAITO_SYNC_INTERVAL'] || '30').to_i, - cache_dir: ENV['KENAITO_CACHE_DIR'] || '.' -) - -client = KenaitoConf::ConfigClient.new(config) - -client.on_change do |changed| - changed.each do |k, v| - if config_values.key?(k) - config_values[k] = v - warn "config updated: #{k} = #{v}" - end - end -end - -begin - client.start - remote = client.get_all - config_values.each_key do |k| - config_values[k] = remote[k] if remote[k] - end - warn "loaded #{remote.size} configs from remote" -rescue StandardError => e - warn "failed to init config client: #{e.message}" -end - -puts 'Current configs:' -config_values.each do |k, v| - puts " #{k} = #{v}" -end - -puts "\nListening for config changes... (press Ctrl+C to stop)" -sleep diff --git a/kenaito-conf-ruby-client/README.md b/kenaito-conf-ruby-client/README.md deleted file mode 100644 index be69aaa..0000000 --- a/kenaito-conf-ruby-client/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Kenaito Config Center Ruby Client - -Ruby 语言的配置中心客户端 SDK。 - -## 依赖 - -- Ruby 2.7+ -- 标准库(net/http, json, openssl, base64) - -## 安装 - -```bash -gem build kenaito-conf.gemspec -gem install kenaito-conf-1.0.0.gem -``` - -或在 Gemfile 中添加: - -```ruby -gem 'kenaito-conf', path: '../kenaito-conf-ruby-client' -``` - -## 使用 - -```ruby -require 'kenaito_conf' - -config = KenaitoConf::Config.new( - base_url: 'http://localhost:8080', - app_name: 'kenaito-springboot-demo', - env: 'daily', - version: 'latest', - sync_interval: 30 -) - -client = KenaitoConf::ConfigClient.new(config) - -client.on_change do |changed| - changed.each do |k, v| - puts "config changed: #{k} = #{v}" - end -end - -client.start - -value = client.get('database.host') -all = client.get_all - -client.stop -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `base_url` | 配置中心地址 | 必填 | -| `app_name` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `sync_interval` | 同步间隔(秒) | `60` | -| `ignore_global` | 是否忽略全局配置 | `false` | -| `cache_dir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-ruby-client-demo -ruby app.rb -``` diff --git a/kenaito-conf-ruby-client/kenaito-conf.gemspec b/kenaito-conf-ruby-client/kenaito-conf.gemspec deleted file mode 100644 index 3ffcede..0000000 --- a/kenaito-conf-ruby-client/kenaito-conf.gemspec +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -Gem::Specification.new do |spec| - spec.name = 'kenaito-conf' - spec.version = '1.0.0' - spec.authors = ['odboy'] - spec.email = ['tianjun@odboy.cn'] - - spec.summary = 'Kenaito Config Center Ruby Client' - spec.description = 'Ruby client for Kenaito Config Center' - spec.homepage = 'https://gitea.odboy.cn/odboy/kenaito-conf' - spec.license = 'MIT' - - spec.required_ruby_version = '>= 2.7.0' - - spec.files = Dir['lib/**/*'] - spec.require_paths = ['lib'] -end diff --git a/kenaito-conf-ruby-client/lib/kenaito_conf.rb b/kenaito-conf-ruby-client/lib/kenaito_conf.rb deleted file mode 100644 index 7cdde37..0000000 --- a/kenaito-conf-ruby-client/lib/kenaito_conf.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true - -require 'net/http' -require 'json' - -module KenaitoConf - class Config - attr_accessor :base_url, :app_name, :env, :version, :token, :sync_interval, :ignore_global, :cache_dir - - def initialize(options = {}) - @base_url = options[:base_url] || '' - @app_name = options[:app_name] || '' - @env = options[:env] || 'dev' - @version = options[:version] || 'latest' - @token = options[:token] || '' - @sync_interval = options[:sync_interval] || 60 - @ignore_global = options[:ignore_global] || false - @cache_dir = options[:cache_dir] || '.' - end - end - - class Cache - CACHE_FILE = 'config-cache.json' - - def initialize(cache_dir = '.') - @cache_dir = cache_dir - end - - def write(data) - File.write(File.join(@cache_dir, CACHE_FILE), JSON.pretty_generate(data)) - end - - def read - path = File.join(@cache_dir, CACHE_FILE) - return nil unless File.exist?(path) - JSON.parse(File.read(path)) - rescue StandardError - nil - end - end - - class ConfigClient - def initialize(config) - @config = config - @cache = Cache.new(config.cache_dir) - @configs = {} - @last_time = 0 - @handlers = [] - @started = false - end - - def on_change(&handler) - @handlers << handler - end - - def get(key) - @configs[key] - end - - def get_all - @configs.dup - end - - def start - return if @started - @started = true - - begin - fetch_all - rescue StandardError => e - warn "[ConfigClient] fetch all configs error: #{e.message}, trying cache" - cached = @cache.read - if cached - apply_configs(cached) - warn "[ConfigClient] loaded #{@configs.size} configs from cache" - else - raise "fetch configs failed and no cache: #{e.message}" - end - end - - Thread.new do - loop do - sleep @config.sync_interval - begin - fetch_changed - rescue StandardError => e - warn "[ConfigClient] sync error: #{e.message}" - end - end - end - end - - def stop - @started = false - end - - private - - def fetch_all - uri = URI("#{@config.base_url}/api/fetchAllConfigs") - http = Net::HTTP.new(uri.host, uri.port) - http.read_timeout = 10 - - request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json') - request.body = JSON.generate({ - appName: @config.app_name, - env: @config.env, - version: @config.version, - ignoreGlobal: @config.ignore_global, - token: @config.token - }) - - response = http.request(request) - raise "HTTP #{response.code}" unless response.code == '200' - - data = JSON.parse(response.body) - raise "server error: #{data['msg']}" if data['code'] && data['code'] != 0 - - @last_time = data['latest.fetch.timestamp'].to_i if data['latest.fetch.timestamp'] - - @cache.write(data) - apply_configs(data) - warn "[ConfigClient] fetched #{@configs.size} configs" - end - - def fetch_changed - uri = URI("#{@config.base_url}/api/fetchChangedConfigs") - http = Net::HTTP.new(uri.host, uri.port) - http.read_timeout = 10 - - request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json') - request.body = JSON.generate({ - appName: @config.app_name, - env: @config.env, - version: @config.version, - lastTime: @last_time, - ignoreGlobal: @config.ignore_global, - token: @config.token - }) - - response = http.request(request) - result = JSON.parse(response.body) - raise "server error" if !result['code'] || result['code'] != 0 - - changed_data = result['data'] || {} - @last_time = changed_data.delete('latest.fetch.timestamp').to_i if changed_data['latest.fetch.timestamp'] - - changed = {} - changed_data.each do |k, v| - changed[k] = v - end - - @configs.merge!(changed) - @handlers.each { |h| h.call(changed) rescue warn "[ConfigClient] handler error: #{$!.message}" } - end - - def apply_configs(data) - data.each do |k, v| - next if k.start_with?('kenaito.') || k == 'latest.fetch.timestamp' - next unless v.is_a?(String) - @configs[k] = v - end - end - end -end diff --git a/kenaito-conf-rust-client-demo/Cargo.toml b/kenaito-conf-rust-client-demo/Cargo.toml deleted file mode 100644 index a74599f..0000000 --- a/kenaito-conf-rust-client-demo/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "kenaito-conf-demo" -version = "1.0.0" -edition = "2021" - -[dependencies] -kenaito-conf = { path = "../kenaito-conf-rust-client" } -tokio = { version = "1", features = ["full"] } diff --git a/kenaito-conf-rust-client-demo/src/main.rs b/kenaito-conf-rust-client-demo/src/main.rs deleted file mode 100644 index 798db0e..0000000 --- a/kenaito-conf-rust-client-demo/src/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -use kenaito_conf::{Config, ConfigClient}; -use std::collections::HashMap; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let mut config_values: HashMap = HashMap::new(); - config_values.insert("database.host".to_string(), std::env::var("DATABASE_HOST").unwrap_or_else(|_| "192.168.1.1".to_string())); - config_values.insert("database.port".to_string(), std::env::var("DATABASE_PORT").unwrap_or_else(|_| "8888".to_string())); - config_values.insert("database.name".to_string(), std::env::var("DATABASE_NAME").unwrap_or_else(|_| "xxxx".to_string())); - config_values.insert("database.user".to_string(), std::env::var("DATABASE_USER").unwrap_or_else(|_| "xxxx".to_string())); - config_values.insert("database.password".to_string(), std::env::var("DATABASE_PASSWORD").unwrap_or_else(|_| "xxxxxxxx".to_string())); - - let config = Config { - base_url: std::env::var("KENAITO_BASE_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()), - app_name: std::env::var("KENAITO_APP_NAME").unwrap_or_else(|_| "kenaito-springboot-demo".to_string()), - env: std::env::var("KENAITO_PROFILE").unwrap_or_else(|_| "daily".to_string()), - version: std::env::var("KENAITO_VERSION").unwrap_or_else(|_| "latest".to_string()), - token: std::env::var("KENAITO_TOKEN").unwrap_or_else(|_| String::new()), - sync_interval: std::env::var("KENAITO_SYNC_INTERVAL").unwrap_or_else(|_| "30".to_string()).parse().unwrap_or(30), - cache_dir: std::env::var("KENAITO_CACHE_DIR").unwrap_or_else(|_| ".".to_string()), - ..Default::default() - }; - - let client = ConfigClient::new(config); - - client.on_change(move |changed| { - for (k, v) in changed.iter() { - eprintln!("config changed: {} = {}", k, v); - } - }); - - match client.start().await { - Ok(_) => { - let remote = client.get_all(); - eprintln!("loaded {} configs from remote", remote.len()); - } - Err(e) => { - eprintln!("failed to init config client: {}", e); - } - } - - println!("Current configs:"); - for (k, v) in &config_values { - println!(" {} = {}", k, v); - } - - println!("\nListening for config changes... (press Ctrl+C to stop)"); - tokio::signal::ctrl_c().await?; - println!("Shutting down..."); - - Ok(()) -} diff --git a/kenaito-conf-rust-client/Cargo.toml b/kenaito-conf-rust-client/Cargo.toml deleted file mode 100644 index a0f7812..0000000 --- a/kenaito-conf-rust-client/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "kenaito-conf" -version = "1.0.0" -edition = "2021" -authors = ["odboy "] -description = "Kenaito Config Center Rust Client" - -[dependencies] -reqwest = { version = "0.11", features = ["json"] } -tokio = { version = "1", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -md-5 = "0.10" -aes = "0.8" -cbc = "0.1" -base64 = "0.21" diff --git a/kenaito-conf-rust-client/README.md b/kenaito-conf-rust-client/README.md deleted file mode 100644 index 8aafaca..0000000 --- a/kenaito-conf-rust-client/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Kenaito Config Center Rust Client - -Rust 语言的配置中心客户端 SDK。 - -## 依赖 - -- Rust 1.70+ -- reqwest (HTTP 客户端) -- tokio (异步运行时) -- aes + cbc (AES 解密) - -## 安装 - -在 `Cargo.toml` 中添加: - -```toml -[dependencies] -kenaito-conf = { path = "../kenaito-conf-rust-client" } -``` - -## 使用 - -```rust -use kenaito_conf::{Config, ConfigClient}; - -#[tokio::main] -async fn main() { - let config = Config { - base_url: "http://localhost:8080".to_string(), - app_name: "kenaito-springboot-demo".to_string(), - env: "daily".to_string(), - version: "latest".to_string(), - sync_interval: 30, - ..Default::default() - }; - - let client = ConfigClient::new(config); - - client.on_change(|changed| { - for (k, v) in changed.iter() { - println!("config changed: {} = {}", k, v); - } - }); - - client.start().await.unwrap(); - - let value = client.get("database.host"); - let all = client.get_all(); -} -``` - -## 配置参数 - -| 参数 | 说明 | 默认值 | -|------|------|--------| -| `base_url` | 配置中心地址 | 必填 | -| `app_name` | 应用名称 | 必填 | -| `env` | 环境 | `dev` | -| `version` | 配置版本 | `latest` | -| `token` | 认证令牌 | 空 | -| `sync_interval` | 同步间隔(秒) | `60` | -| `ignore_global` | 是否忽略全局配置 | `false` | -| `cache_dir` | 本地缓存目录 | `.` | - -## Demo - -```bash -cd kenaito-conf-rust-client-demo -cargo run -``` diff --git a/kenaito-conf-rust-client/src/lib.rs b/kenaito-conf-rust-client/src/lib.rs deleted file mode 100644 index db628ba..0000000 --- a/kenaito-conf-rust-client/src/lib.rs +++ /dev/null @@ -1,140 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -#[derive(Clone, Debug)] -pub struct Config { - pub base_url: String, - pub app_name: String, - pub env: String, - pub version: String, - pub token: String, - pub sync_interval: u64, - pub ignore_global: bool, - pub cache_dir: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - base_url: String::new(), - app_name: String::new(), - env: "dev".to_string(), - version: "latest".to_string(), - token: String::new(), - sync_interval: 60, - ignore_global: false, - cache_dir: ".".to_string(), - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -struct FetchAllRequest { - app_name: String, - env: String, - version: String, - ignore_global: bool, - token: String, -} - -#[derive(Serialize, Deserialize, Debug)] -struct FetchChangedRequest { - app_name: String, - env: String, - version: String, - last_time: i64, - ignore_global: bool, - token: String, -} - -pub struct ConfigClient { - config: Config, - configs: Arc>>, - last_time: Arc>, - handlers: Arc) + Send + Sync>>>>, -} - -impl ConfigClient { - pub fn new(config: Config) -> Self { - Self { - config, - configs: Arc::new(Mutex::new(HashMap::new())), - last_time: Arc::new(Mutex::new(0)), - handlers: Arc::new(Mutex::new(Vec::new())), - } - } - - pub fn on_change(&self, handler: F) - where - F: Fn(&HashMap) + Send + Sync + 'static, - { - self.handlers.lock().unwrap().push(Box::new(handler)); - } - - pub fn get(&self, key: &str) -> Option { - self.configs.lock().unwrap().get(key).cloned() - } - - pub fn get_all(&self) -> HashMap { - self.configs.lock().unwrap().clone() - } - - pub async fn start(&self) -> Result<(), Box> { - self.fetch_all().await?; - - let configs = self.configs.clone(); - let last_time = self.last_time.clone(); - let handlers = self.handlers.clone(); - let config = self.config.clone(); - - tokio::spawn(async move { - loop { - tokio::time::sleep(Duration::from_secs(config.sync_interval)).await; - // fetch_changed logic here - } - }); - - Ok(()) - } - - async fn fetch_all(&self) -> Result<(), Box> { - let url = format!("{}/api/fetchAllConfigs", self.config.base_url); - let client = reqwest::Client::new(); - - let payload = serde_json::json!({ - "appName": self.config.app_name, - "env": self.config.env, - "version": self.config.version, - "ignoreGlobal": self.config.ignore_global, - "token": self.config.token, - }); - - let resp = client.post(&url).json(&payload).send().await?; - let data: HashMap = resp.json().await?; - - if let Some(code) = data.get("code") { - if code.as_i64() != Some(0) { - return Err("server error".into()); - } - } - - if let Some(ts) = data.get("latest.fetch.timestamp") { - *self.last_time.lock().unwrap() = ts.as_i64().unwrap_or(0); - } - - let mut configs = self.configs.lock().unwrap(); - - for (k, v) in data.iter() { - if k.starts_with("kenaito.") || k == "latest.fetch.timestamp" { - continue; - } - if let Some(s) = v.as_str() { - configs.insert(k.clone(), s.to_string()); - } - } - - Ok(()) - } -}