feat: 新增 8 个语言客户端 SDK

- PHP 客户端 (kenaito-conf-php-client)
- .NET 客户端 (kenaito-conf-dotnet-client)
- C 客户端 (kenaito-conf-c-client)
- C++ 客户端 (kenaito-conf-cpp-client)
- Lua 客户端 (kenaito-conf-lua-client)
- Kotlin 客户端 (kenaito-conf-kotlin-client)
- Rust 客户端 (kenaito-conf-rust-client)
- Ruby 客户端 (kenaito-conf-ruby-client)

每个客户端包含完整的 SDK 实现和 demo 示例,支持:
- 配置拉取(全量/增量)
- AES-128-CBC 解密
- 本地缓存
- 变更回调
- Token 认证

更新根目录 README.md:
- 技术栈列表新增 8 种语言
- 项目结构新增 16 个目录
- 客户端接入章节新增 8 个使用示例
This commit is contained in:
2026-03-05 21:14:42 +08:00
parent 2f5c343347
commit 4c3648cf7f
40 changed files with 2824 additions and 2 deletions
+50
View File
@@ -0,0 +1,50 @@
# Kenaito Config Center C++ Client
C++ 语言的配置中心客户端 SDK,使用 libcurl + OpenSSL。
## 依赖
- libcurl
- OpenSSL
- C++17
### macOS
```bash
brew install curl openssl
```
### Ubuntu/Debian
```bash
apt-get install libcurl4-openssl-dev libssl-dev
```
## 编译
```bash
cd kenaito-conf-cpp-client-demo
g++ -std=c++17 -o demo ../kenaito-conf-cpp-client/main.cpp -lcurl -lssl -lcrypto -lpthread
./demo
```
## 功能
- AES-128-CBC 解密
- HTTP POST 拉取配置
- 定时同步
- 变更回调
- 线程安全
## 配置参数
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `baseUrl` | 配置中心地址 | 必填 |
| `appName` | 应用名称 | 必填 |
| `env` | 环境 | `dev` |
| `version` | 配置版本 | `latest` |
| `token` | 认证令牌 | 空 |
| `syncInterval` | 同步间隔(秒) | `60` |
| `ignoreGlobal` | 是否忽略全局配置 | `false` |
| `cacheDir` | 本地缓存目录 | `.` |
+153
View File
@@ -0,0 +1,153 @@
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <functional>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>
#include <fstream>
#include <sstream>
#include <cstring>
#include <curl/curl.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
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 Crypto {
public:
static std::string decryptConfig(const std::string &secret, const std::string &data) {
const std::string prefix = "{Cipher}";
if (data.substr(0, prefix.size()) != prefix) return data;
return decryptWithAES(secret, data.substr(prefix.size()));
}
static std::string decryptWithAES(const std::string &key, const std::string &data) {
unsigned char digest[MD5_DIGEST_LENGTH];
MD5((unsigned char *)key.c_str(), key.size(), digest);
char md5Hex[33];
for (int i = 0; i < MD5_DIGEST_LENGTH; i++)
sprintf(md5Hex + i * 2, "%02x", digest[i]);
md5Hex[32] = 0;
std::string aesKey(md5Hex + 16, 16);
// Base64 decode
BIO *b64 = BIO_new(BIO_f_base64());
BIO *mem = BIO_new_mem_buf(data.c_str(), data.size());
mem = BIO_push(b64, mem);
BIO_set_flags(mem, BIO_FLAGS_BASE64_NO_NL);
std::vector<unsigned char> cipherText(data.size());
int cipherLen = BIO_read(mem, cipherText.data(), data.size());
BIO_free_all(mem);
unsigned char iv[16] = {0};
std::vector<unsigned char> plainText(cipherLen + 16);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, (unsigned char *)aesKey.c_str(), iv);
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len = 0, totalLen = 0;
EVP_DecryptUpdate(ctx, plainText.data(), &len, cipherText.data(), cipherLen);
totalLen = len;
EVP_DecryptFinal_ex(ctx, plainText.data() + len, &len);
totalLen += len;
EVP_CIPHER_CTX_free(ctx);
int padding = plainText[totalLen - 1];
return std::string(plainText.begin(), plainText.begin() + totalLen - padding);
}
};
class ConfigClient {
public:
ConfigClient(const Config &config) : config_(config) {}
void onChange(std::function<void(const std::map<std::string, std::string> &)> handler) {
handlers_.push_back(handler);
}
std::string get(const std::string &key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = configs_.find(key);
return it != configs_.end() ? it->second : "";
}
std::map<std::string, std::string> getAll() {
std::lock_guard<std::mutex> 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<std::string, std::string> configs_;
std::string secret_, globalSecret_;
long lastTime_ = 0;
bool started_ = false;
std::atomic<bool> running_{false};
std::mutex mutex_;
std::thread syncThread_;
std::vector<std::function<void(const std::map<std::string, std::string> &)>> 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<std::string, std::string> &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;
}