chore: 删除 C 客户端和 demo
移除 kenaito-conf-c-client 和 kenaito-conf-c-client-demo 模块。
This commit is contained in:
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* Kenaito Config Center C Client Demo
|
|
||||||
*
|
|
||||||
* 演示如何使用 C 客户端 SDK
|
|
||||||
*
|
|
||||||
* 编译:
|
|
||||||
* gcc -o demo main.c ../kenaito-conf-c-client/main.c -I/usr/local/opt/cjson/include -L/usr/local/opt/cjson/lib -lcurl -lcjson -lpthread
|
|
||||||
*
|
|
||||||
* 运行:
|
|
||||||
* ./demo
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
/* 声明 SDK 类型和函数 */
|
|
||||||
typedef struct {
|
|
||||||
char baseUrl[512];
|
|
||||||
char appName[128];
|
|
||||||
char env[32];
|
|
||||||
char version[32];
|
|
||||||
char token[256];
|
|
||||||
int syncInterval;
|
|
||||||
int ignoreGlobal;
|
|
||||||
char cacheDir[256];
|
|
||||||
} Config;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Config config;
|
|
||||||
void *configs;
|
|
||||||
long lastTime;
|
|
||||||
void (*onChange)(const char *key, const char *value);
|
|
||||||
void *curl;
|
|
||||||
int running;
|
|
||||||
void *syncThread;
|
|
||||||
} ConfigClient;
|
|
||||||
|
|
||||||
extern void config_client_init(ConfigClient *client, Config *config);
|
|
||||||
extern const char *config_client_get(ConfigClient *client, const char *key);
|
|
||||||
extern void config_client_start(ConfigClient *client);
|
|
||||||
extern void config_client_stop(ConfigClient *client);
|
|
||||||
|
|
||||||
/* 全局客户端实例,用于信号处理 */
|
|
||||||
static ConfigClient *g_demo_client = NULL;
|
|
||||||
static int g_running = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置变更回调示例
|
|
||||||
*/
|
|
||||||
void on_config_change(const char *key, const char *value) {
|
|
||||||
printf("\n[Callback] Config changed: %s = %s\n", key, value);
|
|
||||||
|
|
||||||
if (strcmp(key, "log.level") == 0) {
|
|
||||||
printf(" -> Log level changed to: %s\n", value);
|
|
||||||
} else if (strcmp(key, "database.host") == 0) {
|
|
||||||
printf(" -> Database host changed to: %s\n", value);
|
|
||||||
} else if (strcmp(key, "database.port") == 0) {
|
|
||||||
printf(" -> Database port changed to: %s\n", value);
|
|
||||||
} else if (strcmp(key, "feature.enabled") == 0) {
|
|
||||||
printf(" -> Feature flag changed: %s\n", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 信号处理 - 优雅退出
|
|
||||||
*/
|
|
||||||
static void signal_handler(int sig) {
|
|
||||||
printf("\n[Signal] Received signal %d, shutting down...\n", sig);
|
|
||||||
g_running = 0;
|
|
||||||
if (g_demo_client) {
|
|
||||||
g_demo_client->running = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
printf("========================================\n");
|
|
||||||
printf(" Kenaito Config Center C Client Demo\n");
|
|
||||||
printf("========================================\n\n");
|
|
||||||
|
|
||||||
/* 注册信号处理 */
|
|
||||||
signal(SIGINT, signal_handler);
|
|
||||||
signal(SIGTERM, signal_handler);
|
|
||||||
|
|
||||||
/* 配置客户端 */
|
|
||||||
Config config = {
|
|
||||||
.baseUrl = "http://localhost:8080",
|
|
||||||
.appName = "kenaito-springboot-demo",
|
|
||||||
.env = "daily",
|
|
||||||
.version = "latest",
|
|
||||||
.token = "",
|
|
||||||
.syncInterval = 10,
|
|
||||||
.ignoreGlobal = 0,
|
|
||||||
.cacheDir = "."
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigClient client;
|
|
||||||
config_client_init(&client, &config);
|
|
||||||
client.onChange = on_config_change;
|
|
||||||
|
|
||||||
g_demo_client = &client;
|
|
||||||
|
|
||||||
/* 启动客户端 */
|
|
||||||
config_client_start(&client);
|
|
||||||
|
|
||||||
/* 主循环:演示配置读取 */
|
|
||||||
printf("\n[Demo] Running... Press Ctrl+C to stop\n\n");
|
|
||||||
|
|
||||||
int iterations = 0;
|
|
||||||
while (g_running && iterations < 10) {
|
|
||||||
sleep(5);
|
|
||||||
|
|
||||||
/* 演示:定期读取并打印配置 */
|
|
||||||
const char *dbHost = config_client_get(&client, "database.host");
|
|
||||||
const char *dbPort = config_client_get(&client, "database.port");
|
|
||||||
const char *logLevel = config_client_get(&client, "log.level");
|
|
||||||
const char *featureEnabled = config_client_get(&client, "feature.enabled");
|
|
||||||
|
|
||||||
printf("\n[Current Configs @ %ld]\n", time(NULL));
|
|
||||||
printf(" database.host = %s\n", dbHost ? dbHost : "(not set)");
|
|
||||||
printf(" database.port = %s\n", dbPort ? dbPort : "(not set)");
|
|
||||||
printf(" log.level = %s\n", logLevel ? logLevel : "(not set)");
|
|
||||||
printf(" feature.enabled = %s\n", featureEnabled ? featureEnabled : "(not set)");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
iterations++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 停止客户端 */
|
|
||||||
config_client_stop(&client);
|
|
||||||
|
|
||||||
printf("\n[Demo] Finished\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
# Kenaito Config Center C Client
|
|
||||||
|
|
||||||
C 语言的配置中心客户端 SDK,适用于嵌入式设备或高性能场景,依赖 libcurl。
|
|
||||||
|
|
||||||
## 依赖
|
|
||||||
|
|
||||||
- libcurl
|
|
||||||
|
|
||||||
### Ubuntu/Debian
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get install libcurl4-openssl-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### CentOS/RHEL
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yum install libcurl-devel
|
|
||||||
```
|
|
||||||
|
|
||||||
> **macOS**:系统自带 libcurl,无需额外安装。
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 1. 安装依赖
|
|
||||||
|
|
||||||
#### Ubuntu/Debian
|
|
||||||
|
|
||||||
```bash
|
|
||||||
apt-get install libcurl4-openssl-dev libcjson-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
#### CentOS/RHEL
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yum install libcurl-devel libcjson-devel
|
|
||||||
```
|
|
||||||
|
|
||||||
#### macOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install cJSON
|
|
||||||
```
|
|
||||||
|
|
||||||
> **macOS**:系统自带 libcurl,无需额外安装。
|
|
||||||
|
|
||||||
### 2. 编译 SDK
|
|
||||||
|
|
||||||
**macOS:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd kenaito-conf-c-client
|
|
||||||
gcc -c main.c -o kenaito_conf.o -I/usr/local/opt/cjson/include -lcurl
|
|
||||||
ar rcs libkenaito_conf.a kenaito_conf.o
|
|
||||||
```
|
|
||||||
|
|
||||||
**Ubuntu/Debian:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd kenaito-conf-c-client
|
|
||||||
gcc -c main.c -o kenaito_conf.o $(pkg-config --cflags libcjson) -lcurl
|
|
||||||
ar rcs libkenaito_conf.a kenaito_conf.o
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 编译 Demo
|
|
||||||
|
|
||||||
**macOS:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd kenaito-conf-c-client-demo
|
|
||||||
gcc -o demo main.c ../kenaito-conf-c-client/main.c -I/usr/local/opt/cjson/include -lcurl -lpthread
|
|
||||||
./demo
|
|
||||||
```
|
|
||||||
|
|
||||||
**Ubuntu/Debian:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd kenaito-conf-c-client-demo
|
|
||||||
gcc -o demo main.c ../kenaito-conf-c-client/main.c $(pkg-config --cflags libcjson) -lcurl -lpthread
|
|
||||||
./demo
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 使用 SDK
|
|
||||||
|
|
||||||
在你的项目中:
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "kenaito-conf-c-client/main.c"
|
|
||||||
|
|
||||||
// 或者链接静态库
|
|
||||||
// gcc -o myapp myapp.c -lkenaito_conf -lcurl -lcjson -lpthread
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 完整示例
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "../kenaito-conf-c-client/main.c"
|
|
||||||
|
|
||||||
void on_change(const char *key, const char *value) {
|
|
||||||
printf("config changed: %s = %s\n", key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
Config config = {
|
|
||||||
.baseUrl = "http://localhost:8080",
|
|
||||||
.appName = "my-app",
|
|
||||||
.env = "daily",
|
|
||||||
.version = "latest",
|
|
||||||
.token = "",
|
|
||||||
.syncInterval = 10,
|
|
||||||
.ignoreGlobal = 0,
|
|
||||||
.cacheDir = "."
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigClient client;
|
|
||||||
config_client_init(&client, &config);
|
|
||||||
client.onChange = on_change;
|
|
||||||
|
|
||||||
// 启动客户端(拉取全量配置 + 开启定时同步)
|
|
||||||
config_client_start(&client);
|
|
||||||
|
|
||||||
// 获取配置
|
|
||||||
const char *dbHost = config_client_get(&client, "database.host");
|
|
||||||
const char *dbPort = config_client_get(&client, "database.port");
|
|
||||||
|
|
||||||
printf("database.host = %s\n", dbHost ? dbHost : "not found");
|
|
||||||
printf("database.port = %s\n", dbPort ? dbPort : "not found");
|
|
||||||
|
|
||||||
// 等待配置变更
|
|
||||||
sleep(30);
|
|
||||||
|
|
||||||
// 停止客户端
|
|
||||||
config_client_stop(&client);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置变更监听
|
|
||||||
|
|
||||||
```c
|
|
||||||
void on_config_change(const char *key, const char *value) {
|
|
||||||
printf("[ConfigChange] %s = %s\n", key, value);
|
|
||||||
// 在此处处理配置更新逻辑
|
|
||||||
// 例如:重新初始化数据库连接、更新日志级别等
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册回调
|
|
||||||
client.onChange = on_config_change;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置参数
|
|
||||||
|
|
||||||
| 参数 | 说明 | 默认值 | 必填 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| `baseUrl` | 配置中心地址 | - | 是 |
|
|
||||||
| `appName` | 应用名称 | - | 是 |
|
|
||||||
| `env` | 环境(dev/daily/prod) | `dev` | 否 |
|
|
||||||
| `version` | 配置版本 | `latest` | 否 |
|
|
||||||
| `token` | 认证令牌 | 空 | 否 |
|
|
||||||
| `syncInterval` | 同步间隔(秒) | `10` | 否 |
|
|
||||||
| `ignoreGlobal` | 是否忽略全局配置 | `0` | 否 |
|
|
||||||
| `cacheDir` | 本地缓存目录 | `.` | 否 |
|
|
||||||
|
|
||||||
## API 参考
|
|
||||||
|
|
||||||
### 初始化客户端
|
|
||||||
|
|
||||||
```c
|
|
||||||
void config_client_init(ConfigClient *client, Config *config);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取配置值
|
|
||||||
|
|
||||||
```c
|
|
||||||
const char *config_client_get(ConfigClient *client, const char *key);
|
|
||||||
```
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `client`:客户端实例
|
|
||||||
- `key`:配置键名
|
|
||||||
|
|
||||||
**返回值:**
|
|
||||||
- 成功:配置值字符串
|
|
||||||
- 失败:`NULL`
|
|
||||||
|
|
||||||
### 停止客户端
|
|
||||||
|
|
||||||
```c
|
|
||||||
void config_client_stop(ConfigClient *client);
|
|
||||||
```
|
|
||||||
|
|
||||||
**说明:** 释放 libcurl 资源,停止定时同步。
|
|
||||||
|
|
||||||
### 注册变更回调
|
|
||||||
|
|
||||||
```c
|
|
||||||
client.onChange = on_config_change;
|
|
||||||
```
|
|
||||||
|
|
||||||
**回调签名:**
|
|
||||||
```c
|
|
||||||
void on_config_change(const char *key, const char *value);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 完整示例代码
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
// 此处引入 kenaito-conf-c-client/main.c 的完整实现
|
|
||||||
// 或使用编译好的库文件
|
|
||||||
|
|
||||||
void on_change(const char *key, const char *value) {
|
|
||||||
printf("[ConfigChange] %s = %s\n", key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
printf("=== Kenaito Config Center C Client Demo ===\n");
|
|
||||||
|
|
||||||
Config config = {
|
|
||||||
.baseUrl = "http://localhost:8080",
|
|
||||||
.appName = "kenaito-springboot-demo",
|
|
||||||
.env = "daily",
|
|
||||||
.version = "latest",
|
|
||||||
.token = "",
|
|
||||||
.syncInterval = 10,
|
|
||||||
.ignoreGlobal = 0,
|
|
||||||
.cacheDir = "."
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigClient client;
|
|
||||||
config_client_init(&client, &config);
|
|
||||||
client.onChange = on_change;
|
|
||||||
|
|
||||||
// 获取配置
|
|
||||||
const char *value = config_client_get(&client, "database.host");
|
|
||||||
if (value) {
|
|
||||||
printf("database.host = %s\n", value);
|
|
||||||
} else {
|
|
||||||
printf("database.host not found\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待配置变更(实际应用中应在此处运行业务逻辑)
|
|
||||||
printf("Listening for config changes...\n");
|
|
||||||
sleep(30);
|
|
||||||
|
|
||||||
config_client_stop(&client);
|
|
||||||
printf("Client stopped.\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
运行示例项目:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd kenaito-conf-c-client-demo
|
|
||||||
gcc -o demo ../kenaito-conf-c-client/main.c -lcurl
|
|
||||||
./demo
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo 默认配置:
|
|
||||||
- 应用名称:`kenaito-springboot-demo`
|
|
||||||
- 环境:`daily`
|
|
||||||
- 配置中心地址:`http://localhost:8080`
|
|
||||||
- 同步间隔:10 秒
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **JSON 解析**:当前示例代码仅演示 HTTP 请求,实际使用需集成 JSON 解析库(如 cJSON)
|
|
||||||
2. **配置缓存**:建议实现本地文件缓存,避免频繁请求配置中心
|
|
||||||
3. **线程安全**:多线程环境下需自行添加锁机制保护配置读取
|
|
||||||
4. **错误处理**:生产环境应完善网络异常、超时、重试等错误处理逻辑
|
|
||||||
5. **内存管理**:使用 `config_client_stop()` 释放资源前不要访问配置
|
|
||||||
|
|
||||||
## 与其他客户端对比
|
|
||||||
|
|
||||||
| 特性 | C 客户端 | Go 客户端 | Java 客户端 |
|
|
||||||
|------|---------|----------|------------|
|
|
||||||
| 编译方式 | 手动 gcc | go build | Maven |
|
|
||||||
| 依赖管理 | 手动链接 | go mod | Maven |
|
|
||||||
| JSON 解析 | 需自行集成 | 标准库 | FastJSON2 |
|
|
||||||
| 定时同步 | 需自行实现 | ✅ | ✅ |
|
|
||||||
| 配置缓存 | 需自行实现 | ✅ | ✅ |
|
|
||||||
| 变更回调 | ✅ | ✅ | ✅ |
|
|
||||||
| 线程安全 | 需自行实现 | ✅ | ✅ |
|
|
||||||
|
|
||||||
## 版本历史
|
|
||||||
|
|
||||||
- **1.0.0** - 初始版本
|
|
||||||
- 基础 HTTP 拉取配置功能
|
|
||||||
- 配置变更回调接口
|
|
||||||
- 简化示例代码
|
|
||||||
|
|
||||||
## 后续开发计划
|
|
||||||
|
|
||||||
- [ ] 集成 cJSON 实现 JSON 解析
|
|
||||||
- [ ] 实现定时轮询同步机制
|
|
||||||
- [ ] 添加本地文件缓存支持
|
|
||||||
- [ ] 完善错误处理和重试机制
|
|
||||||
- [ ] 添加线程安全保护
|
|
||||||
@@ -1,447 +0,0 @@
|
|||||||
/**
|
|
||||||
* Kenaito Config Center C Client SDK
|
|
||||||
*
|
|
||||||
* 完整的配置中心客户端,支持:
|
|
||||||
* - JSON 解析(cJSON)
|
|
||||||
* - 配置缓存到本地文件
|
|
||||||
* - 定时同步配置变更
|
|
||||||
* - 配置变更回调
|
|
||||||
* - 全量/增量拉取
|
|
||||||
*
|
|
||||||
* 依赖:
|
|
||||||
* - libcurl
|
|
||||||
* - cJSON
|
|
||||||
* - pthread
|
|
||||||
*
|
|
||||||
* 编译:
|
|
||||||
* gcc -c main.c -o kenaito_conf.o -I/usr/local/opt/cjson/include -lcurl
|
|
||||||
*
|
|
||||||
* @author odboy
|
|
||||||
* @version 1.0.0
|
|
||||||
* @date 2026-10-16
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KENAITO_CONF_CLIENT_IMPL
|
|
||||||
#define KENAITO_CONF_CLIENT_IMPL
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <cjson/cJSON.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#define MAX_CONFIGS 256
|
|
||||||
#define MAX_KEY_LEN 256
|
|
||||||
#define MAX_VAL_LEN 4096
|
|
||||||
#define CACHE_FILE ".kenaito-config-cache.json"
|
|
||||||
|
|
||||||
/* ============ 配置结构 ============ */
|
|
||||||
typedef struct {
|
|
||||||
char baseUrl[512];
|
|
||||||
char appName[128];
|
|
||||||
char env[32];
|
|
||||||
char version[32];
|
|
||||||
char token[256];
|
|
||||||
int syncInterval;
|
|
||||||
int ignoreGlobal;
|
|
||||||
char cacheDir[256];
|
|
||||||
} Config;
|
|
||||||
|
|
||||||
/* ============ 配置条目 ============ */
|
|
||||||
typedef struct {
|
|
||||||
char key[MAX_KEY_LEN];
|
|
||||||
char value[MAX_VAL_LEN];
|
|
||||||
} ConfigEntry;
|
|
||||||
|
|
||||||
/* ============ 配置映射表 ============ */
|
|
||||||
typedef struct {
|
|
||||||
ConfigEntry entries[MAX_CONFIGS];
|
|
||||||
int count;
|
|
||||||
} ConfigMap;
|
|
||||||
|
|
||||||
/* ============ 客户端实例 ============ */
|
|
||||||
typedef struct {
|
|
||||||
Config config;
|
|
||||||
ConfigMap configs;
|
|
||||||
long lastTime;
|
|
||||||
void (*onChange)(const char *key, const char *value);
|
|
||||||
CURL *curl;
|
|
||||||
int running;
|
|
||||||
pthread_t syncThread;
|
|
||||||
} ConfigClient;
|
|
||||||
|
|
||||||
/* ============ HTTP 响应缓冲区 ============ */
|
|
||||||
typedef struct {
|
|
||||||
char *data;
|
|
||||||
size_t size;
|
|
||||||
} ResponseBuffer;
|
|
||||||
|
|
||||||
/* 全局客户端实例 */
|
|
||||||
static ConfigClient *g_client = NULL;
|
|
||||||
|
|
||||||
/* ============ 工具函数 ============ */
|
|
||||||
|
|
||||||
/* HTTP 响应回调 */
|
|
||||||
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
|
||||||
size_t totalSize = size * nmemb;
|
|
||||||
ResponseBuffer *buf = (ResponseBuffer *)userp;
|
|
||||||
char *ptr = realloc(buf->data, buf->size + totalSize + 1);
|
|
||||||
if (!ptr) return 0;
|
|
||||||
buf->data = ptr;
|
|
||||||
memcpy(&(buf->data[buf->size]), contents, totalSize);
|
|
||||||
buf->size += totalSize;
|
|
||||||
buf->data[buf->size] = 0;
|
|
||||||
return totalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HTTP POST 请求 */
|
|
||||||
static char *http_post(ConfigClient *client, const char *url, const char *jsonBody) {
|
|
||||||
ResponseBuffer buf = {0};
|
|
||||||
struct curl_slist *headers = NULL;
|
|
||||||
|
|
||||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
||||||
headers = curl_slist_append(headers, "Expect:");
|
|
||||||
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_POSTFIELDS, jsonBody);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_HTTPHEADER, headers);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_WRITEFUNCTION, write_callback);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_WRITEDATA, &buf);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_TIMEOUT, 10L);
|
|
||||||
curl_easy_setopt(client->curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(client->curl);
|
|
||||||
curl_slist_free_all(headers);
|
|
||||||
|
|
||||||
if (res != CURLE_OK) {
|
|
||||||
fprintf(stderr, "HTTP request failed: %s\n", curl_easy_strerror(res));
|
|
||||||
free(buf.data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
long httpCode = 0;
|
|
||||||
curl_easy_getinfo(client->curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
|
||||||
if (httpCode != 200) {
|
|
||||||
fprintf(stderr, "HTTP %ld: %s\n", httpCode, buf.data ? buf.data : "empty response");
|
|
||||||
free(buf.data);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 从缓存加载配置 */
|
|
||||||
static int load_from_cache(ConfigClient *client) {
|
|
||||||
FILE *f = fopen(CACHE_FILE, "r");
|
|
||||||
if (!f) return 0;
|
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
long fsize = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
char *jsonStr = malloc(fsize + 1);
|
|
||||||
fread(jsonStr, 1, fsize, f);
|
|
||||||
fclose(f);
|
|
||||||
jsonStr[fsize] = 0;
|
|
||||||
|
|
||||||
cJSON *root = cJSON_Parse(jsonStr);
|
|
||||||
free(jsonStr);
|
|
||||||
|
|
||||||
if (!root) return 0;
|
|
||||||
|
|
||||||
cJSON *lastTimeObj = cJSON_GetObjectItem(root, "lastTime");
|
|
||||||
if (lastTimeObj) {
|
|
||||||
client->lastTime = lastTimeObj->valueint;
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON *configsObj = cJSON_GetObjectItem(root, "configs");
|
|
||||||
if (configsObj && cJSON_IsArray(configsObj)) {
|
|
||||||
client->configs.count = 0;
|
|
||||||
int arrSize = cJSON_GetArraySize(configsObj);
|
|
||||||
for (int i = 0; i < arrSize && client->configs.count < MAX_CONFIGS; i++) {
|
|
||||||
cJSON *item = cJSON_GetArrayItem(configsObj, i);
|
|
||||||
cJSON *keyObj = cJSON_GetObjectItem(item, "key");
|
|
||||||
cJSON *valObj = cJSON_GetObjectItem(item, "value");
|
|
||||||
|
|
||||||
if (keyObj && valObj && cJSON_IsString(keyObj) && cJSON_IsString(valObj)) {
|
|
||||||
strncpy(client->configs.entries[client->configs.count].key,
|
|
||||||
keyObj->valuestring, MAX_KEY_LEN - 1);
|
|
||||||
strncpy(client->configs.entries[client->configs.count].value,
|
|
||||||
valObj->valuestring, MAX_VAL_LEN - 1);
|
|
||||||
client->configs.count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON_Delete(root);
|
|
||||||
printf("[Cache] Loaded %d configs from %s\n", client->configs.count, CACHE_FILE);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 保存配置到缓存 */
|
|
||||||
static void save_to_cache(ConfigClient *client) {
|
|
||||||
cJSON *root = cJSON_CreateObject();
|
|
||||||
cJSON_AddNumberToObject(root, "lastTime", client->lastTime);
|
|
||||||
|
|
||||||
cJSON *configsArr = cJSON_CreateArray();
|
|
||||||
for (int i = 0; i < client->configs.count; i++) {
|
|
||||||
cJSON *item = cJSON_CreateObject();
|
|
||||||
cJSON_AddStringToObject(item, "key", client->configs.entries[i].key);
|
|
||||||
cJSON_AddStringToObject(item, "value", client->configs.entries[i].value);
|
|
||||||
cJSON_AddItemToArray(configsArr, item);
|
|
||||||
}
|
|
||||||
cJSON_AddItemToObject(root, "configs", configsArr);
|
|
||||||
|
|
||||||
char *jsonStr = cJSON_PrintUnformatted(root);
|
|
||||||
FILE *f = fopen(CACHE_FILE, "w");
|
|
||||||
if (f) {
|
|
||||||
fprintf(f, "%s", jsonStr);
|
|
||||||
fclose(f);
|
|
||||||
printf("[Cache] Saved %d configs to %s\n", client->configs.count, CACHE_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(jsonStr);
|
|
||||||
cJSON_Delete(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取配置值 */
|
|
||||||
const char *config_client_get(ConfigClient *client, const char *key) {
|
|
||||||
for (int i = 0; i < client->configs.count; i++) {
|
|
||||||
if (strcmp(client->configs.entries[i].key, key) == 0)
|
|
||||||
return client->configs.entries[i].value;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 解析 JSON 响应 */
|
|
||||||
static int parse_json_response(const char *jsonStr, ConfigClient *client) {
|
|
||||||
cJSON *root = cJSON_Parse(jsonStr);
|
|
||||||
if (!root) {
|
|
||||||
fprintf(stderr, "Failed to parse JSON: %s\n", cJSON_GetErrorPtr());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON *successObj = cJSON_GetObjectItem(root, "success");
|
|
||||||
if (successObj && cJSON_IsBool(successObj) && !successObj->valueint) {
|
|
||||||
cJSON *msgObj = cJSON_GetObjectItem(root, "message");
|
|
||||||
fprintf(stderr, "Server error: %s\n", msgObj ? msgObj->valuestring : "unknown");
|
|
||||||
cJSON_Delete(root);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON *dataObj = cJSON_GetObjectItem(root, "data");
|
|
||||||
if (!dataObj) {
|
|
||||||
fprintf(stderr, "No data in response\n");
|
|
||||||
cJSON_Delete(root);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 全量配置 */
|
|
||||||
cJSON *configsObj = cJSON_GetObjectItem(dataObj, "configs");
|
|
||||||
if (configsObj && cJSON_IsArray(configsObj)) {
|
|
||||||
client->configs.count = 0;
|
|
||||||
int arrSize = cJSON_GetArraySize(configsObj);
|
|
||||||
for (int i = 0; i < arrSize && client->configs.count < MAX_CONFIGS; i++) {
|
|
||||||
cJSON *item = cJSON_GetArrayItem(configsObj, i);
|
|
||||||
cJSON *keyObj = cJSON_GetObjectItem(item, "key");
|
|
||||||
cJSON *valObj = cJSON_GetObjectItem(item, "value");
|
|
||||||
|
|
||||||
if (keyObj && valObj && cJSON_IsString(keyObj) && cJSON_IsString(valObj)) {
|
|
||||||
strncpy(client->configs.entries[client->configs.count].key,
|
|
||||||
keyObj->valuestring, MAX_KEY_LEN - 1);
|
|
||||||
|
|
||||||
const char *oldVal = config_client_get(client, keyObj->valuestring);
|
|
||||||
|
|
||||||
strncpy(client->configs.entries[client->configs.count].value,
|
|
||||||
valObj->valuestring, MAX_VAL_LEN - 1);
|
|
||||||
|
|
||||||
/* 检测变更并触发回调 */
|
|
||||||
if (client->onChange && oldVal && strcmp(oldVal, valObj->valuestring) != 0) {
|
|
||||||
printf("[Change] %s = %s -> %s\n",
|
|
||||||
keyObj->valuestring, oldVal, valObj->valuestring);
|
|
||||||
client->onChange(keyObj->valuestring, valObj->valuestring);
|
|
||||||
}
|
|
||||||
|
|
||||||
client->configs.count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 增量变更 */
|
|
||||||
cJSON *changesObj = cJSON_GetObjectItem(dataObj, "changes");
|
|
||||||
if (changesObj && cJSON_IsArray(changesObj)) {
|
|
||||||
int arrSize = cJSON_GetArraySize(changesObj);
|
|
||||||
for (int i = 0; i < arrSize; i++) {
|
|
||||||
cJSON *item = cJSON_GetArrayItem(changesObj, i);
|
|
||||||
cJSON *keyObj = cJSON_GetObjectItem(item, "key");
|
|
||||||
cJSON *valObj = cJSON_GetObjectItem(item, "value");
|
|
||||||
|
|
||||||
if (keyObj && valObj && cJSON_IsString(keyObj) && cJSON_IsString(valObj)) {
|
|
||||||
/* 更新或添加配置 */
|
|
||||||
int found = 0;
|
|
||||||
for (int j = 0; j < client->configs.count; j++) {
|
|
||||||
if (strcmp(client->configs.entries[j].key, keyObj->valuestring) == 0) {
|
|
||||||
const char *oldVal = client->configs.entries[j].value;
|
|
||||||
strncpy(client->configs.entries[j].value,
|
|
||||||
valObj->valuestring, MAX_VAL_LEN - 1);
|
|
||||||
|
|
||||||
if (client->onChange && strcmp(oldVal, valObj->valuestring) != 0) {
|
|
||||||
printf("[Change] %s = %s -> %s\n",
|
|
||||||
keyObj->valuestring, oldVal, valObj->valuestring);
|
|
||||||
client->onChange(keyObj->valuestring, valObj->valuestring);
|
|
||||||
}
|
|
||||||
found = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found && client->configs.count < MAX_CONFIGS) {
|
|
||||||
strncpy(client->configs.entries[client->configs.count].key,
|
|
||||||
keyObj->valuestring, MAX_KEY_LEN - 1);
|
|
||||||
strncpy(client->configs.entries[client->configs.count].value,
|
|
||||||
valObj->valuestring, MAX_VAL_LEN - 1);
|
|
||||||
client->configs.count++;
|
|
||||||
|
|
||||||
if (client->onChange) {
|
|
||||||
printf("[New] %s = %s\n", keyObj->valuestring, valObj->valuestring);
|
|
||||||
client->onChange(keyObj->valuestring, valObj->valuestring);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 更新 lastTime */
|
|
||||||
cJSON *lastTimeObj = cJSON_GetObjectItem(dataObj, "lastTime");
|
|
||||||
if (lastTimeObj && cJSON_IsNumber(lastTimeObj)) {
|
|
||||||
client->lastTime = lastTimeObj->valueint;
|
|
||||||
}
|
|
||||||
|
|
||||||
cJSON_Delete(root);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============ 客户端 API ============ */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化客户端
|
|
||||||
* @param client 客户端实例
|
|
||||||
* @param config 配置参数
|
|
||||||
*/
|
|
||||||
void config_client_init(ConfigClient *client, Config *config) {
|
|
||||||
client->config = *config;
|
|
||||||
client->configs.count = 0;
|
|
||||||
client->lastTime = 0;
|
|
||||||
client->onChange = NULL;
|
|
||||||
client->running = 0;
|
|
||||||
client->curl = curl_easy_init();
|
|
||||||
|
|
||||||
g_client = client;
|
|
||||||
|
|
||||||
/* 尝试从缓存加载 */
|
|
||||||
if (!load_from_cache(client)) {
|
|
||||||
printf("[Cache] No cache found, will fetch from server\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉取配置(全量或增量)
|
|
||||||
* @param client 客户端实例
|
|
||||||
* @param incremental 是否增量拉取
|
|
||||||
* @return 0 成功,-1 失败
|
|
||||||
*/
|
|
||||||
static int fetch_configs(ConfigClient *client, int incremental) {
|
|
||||||
char url[1024];
|
|
||||||
char payload[1024];
|
|
||||||
|
|
||||||
if (incremental && client->lastTime > 0) {
|
|
||||||
snprintf(url, sizeof(url), "%s/api/fetchChangedConfigs", client->config.baseUrl);
|
|
||||||
snprintf(payload, sizeof(payload),
|
|
||||||
"{\"appName\":\"%s\",\"env\":\"%s\",\"version\":\"%s\","
|
|
||||||
"\"ignoreGlobal\":%s,\"token\":\"%s\",\"lastTime\":%ld}",
|
|
||||||
client->config.appName, client->config.env, client->config.version,
|
|
||||||
client->config.ignoreGlobal ? "true" : "false",
|
|
||||||
client->config.token, client->lastTime);
|
|
||||||
} else {
|
|
||||||
snprintf(url, sizeof(url), "%s/api/fetchAllConfigs", client->config.baseUrl);
|
|
||||||
snprintf(payload, sizeof(payload),
|
|
||||||
"{\"appName\":\"%s\",\"env\":\"%s\",\"version\":\"%s\","
|
|
||||||
"\"ignoreGlobal\":%s,\"token\":\"%s\"}",
|
|
||||||
client->config.appName, client->config.env, client->config.version,
|
|
||||||
client->config.ignoreGlobal ? "true" : "false",
|
|
||||||
client->config.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("[%s] Fetching from %s...\n", incremental ? "Incremental" : "Full", url);
|
|
||||||
char *response = http_post(client, url, payload);
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
fprintf(stderr, "Failed to fetch configs\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = parse_json_response(response, client);
|
|
||||||
free(response);
|
|
||||||
|
|
||||||
if (ret == 0) {
|
|
||||||
save_to_cache(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 定时同步线程 */
|
|
||||||
static void *sync_thread_func(void *arg) {
|
|
||||||
ConfigClient *client = (ConfigClient *)arg;
|
|
||||||
|
|
||||||
while (client->running) {
|
|
||||||
sleep(client->config.syncInterval);
|
|
||||||
|
|
||||||
if (!client->running) break;
|
|
||||||
|
|
||||||
fetch_configs(client, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动客户端
|
|
||||||
* @param client 客户端实例
|
|
||||||
*/
|
|
||||||
void config_client_start(ConfigClient *client) {
|
|
||||||
/* 首次全量拉取 */
|
|
||||||
fetch_configs(client, 0);
|
|
||||||
|
|
||||||
/* 启动定时同步线程 */
|
|
||||||
client->running = 1;
|
|
||||||
pthread_create(&client->syncThread, NULL, sync_thread_func, client);
|
|
||||||
printf("[Sync] Started background sync thread (interval: %ds)\n",
|
|
||||||
client->config.syncInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止客户端
|
|
||||||
* @param client 客户端实例
|
|
||||||
*/
|
|
||||||
void config_client_stop(ConfigClient *client) {
|
|
||||||
client->running = 0;
|
|
||||||
|
|
||||||
if (client->syncThread) {
|
|
||||||
pthread_join(client->syncThread, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
save_to_cache(client);
|
|
||||||
|
|
||||||
if (client->curl) {
|
|
||||||
curl_easy_cleanup(client->curl);
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_global_cleanup();
|
|
||||||
printf("[Client] Stopped\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* KENAITO_CONF_CLIENT_IMPL */
|
|
||||||
Reference in New Issue
Block a user