feat(ruby-client): 新增 Ruby 客户端 SDK 及 demo,移除 C 语言支持

- 新增 kenaito-conf-ruby-client(Ruby 2.5+)及对应 demo
- 新增 Dockerfile.ruby
- README 技术栈移除 C,项目结构补充 Ruby 客户端
- README_EN.md 同步更新
- README_DOCKERFILE.md 补充 Ruby 构建命令
This commit is contained in:
2026-08-17 21:16:19 +08:00
parent 548f86b0da
commit 97a5843483
13 changed files with 670 additions and 5 deletions
+91
View File
@@ -0,0 +1,91 @@
# Kenaito Config Center Ruby Client
[English](README_EN.md) | 中文
Kenaito 配置中心 Ruby 客户端 SDK,适用于 Ruby 2.5+ 项目。
## 安装
### 方式一:本地安装
```bash
cd kenaito-conf-ruby-client
gem build kenaito-conf.gemspec
gem install kenaito-conf-1.0.3.gem
```
### 方式二:Bundler
在你的项目 `Gemfile` 中添加:
```ruby
gem 'kenaito-conf', path: '/path/to/kenaito-conf-ruby-client'
```
然后执行:
```bash
bundle install
```
### 方式三:直接引入
```ruby
require_relative '/path/to/kenaito-conf-ruby-client/lib/kenaito_conf'
```
## 使用示例
```ruby
require 'kenaito_conf'
config = Kenaito::Conf::Config.new(
base_url: 'http://localhost:8080',
app_name: 'my-app',
env: 'daily',
sync_interval: 10
)
client = Kenaito::Conf::Client.new(config)
# 监听配置变更
client.on_change do |changed|
changed.each do |key, value|
puts "config changed: #{key} = #{value}"
end
end
# 启动客户端(拉取配置)
client.start
# 获取配置
value = client.get('database.host')
all_configs = client.get_all
# 手动同步一次
client.sync_once
# 停止
client.stop_client
```
## 配置项
| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| base_url | 配置中心后端地址 | http://localhost:8080 |
| app_name | 应用名称 | 空 |
| env | 环境(dev/daily/prod) | dev |
| version | 配置版本 | latest |
| token | 应用 Token | 空 |
| sync_interval | 同步间隔(秒) | 10 |
| ignore_global | 是否忽略全局配置 | false |
| cache_dir | 缓存目录 | . |
## 功能特性
- ✅ 全量拉取配置
- ✅ 增量同步变更
- ✅ 本地文件缓存容灾
- ✅ 配置变更监听回调
- ✅ 删除配置感知(deletedKeys)
+91
View File
@@ -0,0 +1,91 @@
# Kenaito Config Center Ruby Client
[English](README_EN.md) | 中文
Kenaito Config Center Ruby client SDK, compatible with Ruby 2.5+.
## Installation
### Option 1: Local Install
```bash
cd kenaito-conf-ruby-client
gem build kenaito-conf.gemspec
gem install kenaito-conf-1.0.3.gem
```
### Option 2: Bundler
Add to your project `Gemfile`:
```ruby
gem 'kenaito-conf', path: '/path/to/kenaito-conf-ruby-client'
```
Then run:
```bash
bundle install
```
### Option 3: Direct Require
```ruby
require_relative '/path/to/kenaito-conf-ruby-client/lib/kenaito_conf'
```
## Usage Example
```ruby
require 'kenaito_conf'
config = Kenaito::Conf::Config.new(
base_url: 'http://localhost:8080',
app_name: 'my-app',
env: 'daily',
sync_interval: 10
)
client = Kenaito::Conf::Client.new(config)
# Listen for config changes
client.on_change do |changed|
changed.each do |key, value|
puts "config changed: #{key} = #{value}"
end
end
# Start client (fetch configs)
client.start
# Get config
value = client.get('database.host')
all_configs = client.get_all
# Manual sync once
client.sync_once
# Stop
client.stop_client
```
## Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| base_url | Config center backend URL | http://localhost:8080 |
| app_name | Application name | empty |
| env | Environment (dev/daily/prod) | dev |
| version | Config version | latest |
| token | Application token | empty |
| sync_interval | Sync interval (seconds) | 10 |
| ignore_global | Ignore global configs | false |
| cache_dir | Cache directory | . |
## Features
- ✅ Full config fetch
- ✅ Incremental sync
- ✅ Local file cache for disaster recovery
- ✅ Config change listener callback
- ✅ Deleted config awareness (deletedKeys)
@@ -0,0 +1,17 @@
Gem::Specification.new do |spec|
spec.name = 'kenaito-conf'
spec.version = '1.0.3'
spec.authors = ['odboy']
spec.email = ['odboy@outlook.com']
spec.summary = 'Kenaito Config Center Ruby Client'
spec.description = 'Ruby client SDK for Kenaito Config Center'
spec.homepage = 'https://github.com/odboy/kenaito-conf'
spec.license = 'Apache-2.0'
spec.required_ruby_version = '>= 2.5.0'
spec.files = Dir['lib/**/*.rb']
spec.require_paths = ['lib']
spec.add_development_dependency 'rake', '~> 13.0'
end
@@ -0,0 +1,10 @@
require_relative 'kenaito_conf/config'
require_relative 'kenaito_conf/cache'
require_relative 'kenaito_conf/listener'
require_relative 'kenaito_conf/client'
module Kenaito
module Conf
VERSION = '1.0.3'
end
end
@@ -0,0 +1,33 @@
require 'json'
require 'fileutils'
module Kenaito
module Conf
class Cache
def initialize(cache_dir)
@cache_file = File.join(cache_dir, 'config-cache.json')
end
def read
return nil unless File.exist?(@cache_file)
begin
content = File.read(@cache_file)
JSON.parse(content)
rescue => e
$stderr.puts "[ConfigClient] cache read error: #{e.message}"
nil
end
end
def write(data)
begin
FileUtils.mkdir_p(File.dirname(@cache_file))
File.write(@cache_file, JSON.pretty_generate(data))
rescue => e
$stderr.puts "[ConfigClient] cache write error: #{e.message}"
end
end
end
end
end
@@ -0,0 +1,171 @@
require 'net/http'
require 'uri'
require 'json'
module Kenaito
module Conf
class Client
attr_reader :configs
attr_accessor :stop
def initialize(config)
@config = config
@cache = Cache.new(config.cache_dir)
@listener = Listener.new
@configs = {}
@last_time = 0
@started = false
@stop = false
end
def on_change(&block)
@listener.on_change(&block)
end
def get(key)
@configs[key]
end
def get_all
@configs
end
def start
return if @started
@started = true
begin
fetch_all
rescue => e
$stderr.puts "[ConfigClient] fetch all configs error: #{e.message}, trying cache"
cached = @cache.read
if cached
apply_configs(cached)
$stderr.puts "[ConfigClient] loaded #{@configs.size} configs from cache"
else
raise "fetch configs failed and no cache: #{e.message}"
end
end
end
def stop_client
@stop = true
end
def sync_once
begin
fetch_changed
rescue => e
$stderr.puts "[ConfigClient] sync error: #{e.message}"
end
end
private
def http_post(url, payload)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
http.open_timeout = 10
http.read_timeout = 10
request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json')
request.body = payload.to_json
response = http.request(request)
if response.code.to_i != 200
raise "HTTP #{response.code}"
end
begin
JSON.parse(response.body)
rescue => e
raise "JSON decode error: #{e.message}"
end
end
def fetch_all
url = "#{@config.base_url}/api/fetchAllConfigs"
payload = {
appName: @config.app_name,
env: @config.env,
version: @config.version,
ignoreGlobal: @config.ignore_global,
token: @config.token
}
data = http_post(url, payload)
if data['code'] && data['code'] != 0
raise "server error: #{data['msg'] || 'unknown'}"
end
if data['latest.fetch.timestamp']
@last_time = data['latest.fetch.timestamp'].to_i
end
@cache.write(data)
apply_configs(data)
$stderr.puts "[ConfigClient] fetched #{@configs.size} configs"
end
def fetch_changed
url = "#{@config.base_url}/api/fetchChangedConfigs"
payload = {
appName: @config.app_name,
env: @config.env,
version: @config.version,
lastTime: @last_time,
ignoreGlobal: @config.ignore_global,
token: @config.token
}
result = http_post(url, payload)
if result['code'] && result['code'] != 0
raise "server error code: #{result['code']}"
end
changed_data = result['data'] || {}
if changed_data['latest.fetch.timestamp']
@last_time = changed_data['latest.fetch.timestamp'].to_i
changed_data.delete('latest.fetch.timestamp')
end
# 处理删除的配置
if changed_data['deletedKeys'] && !changed_data['deletedKeys'].empty?
deleted_keys = changed_data['deletedKeys'].split(',')
deleted_keys.each { |key| @configs.delete(key) }
$stderr.puts "[ConfigClient] #{deleted_keys.size} configs deleted: #{deleted_keys.join(', ')}"
changed_data.delete('deletedKeys')
end
# 处理变更的配置
changed = {}
changed_data.each do |key, value|
if value.is_a?(String)
@configs[key] = value
changed[key] = value
end
end
unless changed.empty?
@listener.notify(changed)
$stderr.puts "[ConfigClient] #{changed.size} configs changed: #{changed.keys.join(', ')}"
end
end
def apply_configs(data)
data.each do |key, value|
next if key.start_with?('kenaito.') || key == 'latest.fetch.timestamp'
if value.is_a?(String)
@configs[key] = value
end
end
end
end
end
end
@@ -0,0 +1,18 @@
module Kenaito
module Conf
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] || 'http://localhost:8080'
@app_name = options[:app_name] || ''
@env = options[:env] || 'dev'
@version = options[:version] || 'latest'
@token = options[:token] || ''
@sync_interval = options[:sync_interval] || 10
@ignore_global = options[:ignore_global] || false
@cache_dir = options[:cache_dir] || '.'
end
end
end
end
@@ -0,0 +1,23 @@
module Kenaito
module Conf
class Listener
def initialize
@handlers = []
end
def on_change(&block)
@handlers << block
end
def notify(changed)
@handlers.each do |handler|
begin
handler.call(changed)
rescue => e
$stderr.puts "[ConfigClient] change handler error: #{e.message}"
end
end
end
end
end
end