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:
@@ -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
|
||||
Reference in New Issue
Block a user