feat(perl-client): 新增 Perl 客户端 SDK 及 demo,各客户端版本统一为 1.0.3
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM perl:5.40 AS build
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy client library
|
||||||
|
COPY kenaito-conf-perl-client/ ./kenaito-conf-perl-client/
|
||||||
|
|
||||||
|
# Install client dependencies
|
||||||
|
WORKDIR /src/kenaito-conf-perl-client
|
||||||
|
RUN cpanm --notest HTTP::Tiny JSON::PP
|
||||||
|
|
||||||
|
# Copy demo project
|
||||||
|
WORKDIR /src
|
||||||
|
COPY kenaito-conf-perl-client-demo/ ./kenaito-conf-perl-client-demo/
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM perl:5.40-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /src/kenaito-conf-perl-client/lib /app/kenaito-conf-perl-client/lib
|
||||||
|
COPY --from=build /src/kenaito-conf-perl-client-demo/app.pl .
|
||||||
|
|
||||||
|
ENV PERL5LIB=/app/kenaito-conf-perl-client/lib
|
||||||
|
|
||||||
|
ENTRYPOINT ["perl", "app.pl"]
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
- **后端**: Go + Gin + xorm + MySQL
|
- **后端**: Go + Gin + xorm + MySQL
|
||||||
- **前端**: Vue 2 + Vite 5 + Element UI 2
|
- **前端**: Vue 2 + Vite 5 + Element UI 2
|
||||||
- **客户端**: Go / Java(Spring Boot 2.x & 3.x)/ Python / Node.js / PHP / .NET / C / C++ / Lua / Kotlin / Rust / Ruby
|
- **客户端**: Go / Java(Spring Boot 2.x & 3.x)/ Python / Node.js / Perl / PHP / .NET / C / C++ / Lua / Kotlin / Rust /
|
||||||
|
Ruby
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ kenaito-conf/
|
|||||||
├── kenaito-conf-python-client-demo/ # Python 客户端示例(端口 8082)
|
├── kenaito-conf-python-client-demo/ # Python 客户端示例(端口 8082)
|
||||||
├── kenaito-conf-nodejs-client/ # Node.js 客户端 SDK(Node.js 18+)
|
├── kenaito-conf-nodejs-client/ # Node.js 客户端 SDK(Node.js 18+)
|
||||||
├── kenaito-conf-nodejs-client-demo/ # Node.js 客户端示例(端口 8083)
|
├── kenaito-conf-nodejs-client-demo/ # Node.js 客户端示例(端口 8083)
|
||||||
|
├── kenaito-conf-perl-client/ # Perl 客户端 SDK(Perl 5.16+)
|
||||||
|
├── kenaito-conf-perl-client-demo/ # Perl 客户端示例
|
||||||
├── kenaito-conf-php-client/ # (未支持)PHP 客户端 SDK(PHP 8.0+)
|
├── kenaito-conf-php-client/ # (未支持)PHP 客户端 SDK(PHP 8.0+)
|
||||||
├── kenaito-conf-php-client-demo/ # (未支持)PHP 客户端示例
|
├── kenaito-conf-php-client-demo/ # (未支持)PHP 客户端示例
|
||||||
├── kenaito-conf-dotnet-client/ # .NET 客户端 SDK(.NET 6.0+)
|
├── kenaito-conf-dotnet-client/ # .NET 客户端 SDK(.NET 6.0+)
|
||||||
@@ -264,6 +267,60 @@ const allConfigs = client.getAll();
|
|||||||
client.stop();
|
client.stop();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Perl 客户端
|
||||||
|
|
||||||
|
适用于 Perl 5.16+ 项目。
|
||||||
|
|
||||||
|
**1. 安装**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd kenaito-conf-perl-client
|
||||||
|
cpanm .
|
||||||
|
```
|
||||||
|
|
||||||
|
或在项目中直接引入 lib 路径:
|
||||||
|
|
||||||
|
```perl
|
||||||
|
use lib '/path/to/kenaito-conf-perl-client/lib';
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 使用**
|
||||||
|
|
||||||
|
```perl
|
||||||
|
use Kenaito::Conf::Config;
|
||||||
|
use Kenaito::Conf::Client;
|
||||||
|
|
||||||
|
my $cfg = Kenaito::Conf::Config->new(
|
||||||
|
base_url => 'http://localhost:8080',
|
||||||
|
app_name => 'my-app',
|
||||||
|
env => 'daily',
|
||||||
|
sync_interval => 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
my $client = Kenaito::Conf::Client->new(config => $cfg);
|
||||||
|
|
||||||
|
# 监听配置变更
|
||||||
|
$client->on_change(sub {
|
||||||
|
my ($changed) = @_;
|
||||||
|
for my $k (keys %$changed) {
|
||||||
|
print "config changed: $k = $changed->{$k}\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# 启动客户端(拉取配置)
|
||||||
|
$client->start();
|
||||||
|
|
||||||
|
# 获取配置
|
||||||
|
my $value = $client->get('database.host');
|
||||||
|
my $all = $client->get_all();
|
||||||
|
|
||||||
|
# 手动同步一次
|
||||||
|
$client->sync_once();
|
||||||
|
|
||||||
|
# 停止
|
||||||
|
$client->stop();
|
||||||
|
```
|
||||||
|
|
||||||
## API 概览
|
## API 概览
|
||||||
|
|
||||||
### 管理端 API(需 JWT 认证)
|
### 管理端 API(需 JWT 认证)
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ docker run -it --rm \
|
|||||||
kenaito-conf-dotnet-demo
|
kenaito-conf-dotnet-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Perl 客户端(测试通过)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.perl -t kenaito-conf-perl-demo .
|
||||||
|
|
||||||
|
docker run -it --rm \
|
||||||
|
-e KENAITO_BASE_URL=http://host.docker.internal:8080 \
|
||||||
|
-e KENAITO_APP_NAME=kenaito-springboot-demo \
|
||||||
|
-e KENAITO_ENV=daily \
|
||||||
|
kenaito-conf-perl-demo
|
||||||
|
```
|
||||||
|
|
||||||
## Spring Boot 2 客户端(测试通过)
|
## Spring Boot 2 客户端(测试通过)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PackageId>Kenaito.Conf.Client</PackageId>
|
<PackageId>Kenaito.Conf.Client</PackageId>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.3</Version>
|
||||||
<Authors>odboy</Authors>
|
<Authors>odboy</Authors>
|
||||||
<Company>www.odboy.cn</Company>
|
<Company>www.odboy.cn</Company>
|
||||||
<Description>Kenaito Config Center .NET Client</Description>
|
<Description>Kenaito Config Center .NET Client</Description>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kenaito-conf",
|
"name": "kenaito-conf",
|
||||||
"version": "1.0.0",
|
"version": "1.0.3",
|
||||||
"description": "Kenaito Config Center Node.js Client",
|
"description": "Kenaito Config Center Node.js Client",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use FindBin;
|
||||||
|
use lib "$FindBin::Bin/../../kenaito-conf-perl-client/lib";
|
||||||
|
use JSON::PP;
|
||||||
|
use Kenaito::Conf::Config;
|
||||||
|
use Kenaito::Conf::Client;
|
||||||
|
|
||||||
|
my %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',
|
||||||
|
);
|
||||||
|
|
||||||
|
sub get_env {
|
||||||
|
my ($key, $default) = @_;
|
||||||
|
return defined $ENV{$key} && $ENV{$key} ne '' ? $ENV{$key} : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cfg = Kenaito::Conf::Config->new(
|
||||||
|
base_url => get_env('KENAITO_BASE_URL', 'http://localhost:8080'),
|
||||||
|
app_name => get_env('KENAITO_APP_NAME', 'kenaito-springboot-demo'),
|
||||||
|
env => get_env('KENAITO_ENV', 'daily'),
|
||||||
|
version => get_env('KENAITO_VERSION', 'latest'),
|
||||||
|
token => get_env('KENAITO_TOKEN', ''),
|
||||||
|
sync_interval => int(get_env('KENAITO_SYNC_INTERVAL', '10')),
|
||||||
|
cache_dir => get_env('KENAITO_CACHE_DIR', '.'),
|
||||||
|
);
|
||||||
|
|
||||||
|
my $client = Kenaito::Conf::Client->new(config => $cfg);
|
||||||
|
|
||||||
|
$client->on_change(sub {
|
||||||
|
my ($changed) = @_;
|
||||||
|
print "Config changed:\n";
|
||||||
|
for my $k (sort keys %$changed) {
|
||||||
|
my $v = $changed->{$k};
|
||||||
|
print " $k = $v\n";
|
||||||
|
if (exists $config_values{$k}) {
|
||||||
|
$config_values{$k} = $v;
|
||||||
|
print " [updated] $k = $v\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eval {
|
||||||
|
$client->start();
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
warn "Failed to init config client, using local defaults: $@\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
my $remote = $client->get_all();
|
||||||
|
for my $k (keys %config_values) {
|
||||||
|
if (exists $remote->{$k}) {
|
||||||
|
$config_values{$k} = $remote->{$k};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $count = scalar keys %$remote;
|
||||||
|
print "loaded $count configs from remote\n\n";
|
||||||
|
|
||||||
|
print "Current configs:\n";
|
||||||
|
for my $k (sort keys %config_values) {
|
||||||
|
print " $k = $config_values{$k}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
print "\nListening for config changes... (press Ctrl+C to stop)\n";
|
||||||
|
|
||||||
|
$SIG{INT} = sub {
|
||||||
|
print "\nShutting down...\n";
|
||||||
|
$client->stop();
|
||||||
|
exit 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
# sync loop
|
||||||
|
while (1) {
|
||||||
|
sleep $cfg->{sync_interval};
|
||||||
|
last if $client->{stop};
|
||||||
|
$client->sync_once();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use ExtUtils::MakeMaker;
|
||||||
|
|
||||||
|
WriteMakefile(
|
||||||
|
NAME => 'Kenaito::Conf',
|
||||||
|
VERSION_FROM => 'lib/Kenaito/Conf.pm',
|
||||||
|
ABSTRACT => 'Kenaito Config Center Perl Client',
|
||||||
|
AUTHOR => 'odboy',
|
||||||
|
LICENSE => 'apache_2_0',
|
||||||
|
PREREQ_PM => {
|
||||||
|
'HTTP::Tiny' => '0.070',
|
||||||
|
'JSON::PP' => '0',
|
||||||
|
},
|
||||||
|
test => { TESTS => 't/*.t' },
|
||||||
|
);
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Kenaito Config Center Perl Client
|
||||||
|
|
||||||
|
Perl 客户端 SDK,适用于 Perl 5.16+ 项目。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 从本地安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd kenaito-conf-perl-client
|
||||||
|
cpanm .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开发模式(符号链接)
|
||||||
|
|
||||||
|
在 demo 项目中添加 lib 路径:
|
||||||
|
|
||||||
|
```perl
|
||||||
|
use lib '/path/to/kenaito-conf-perl-client/lib';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
```perl
|
||||||
|
use Kenaito::Conf::Config;
|
||||||
|
use Kenaito::Conf::Client;
|
||||||
|
|
||||||
|
my $cfg = Kenaito::Conf::Config->new(
|
||||||
|
base_url => 'http://localhost:8080',
|
||||||
|
app_name => 'my-app',
|
||||||
|
env => 'daily',
|
||||||
|
version => 'latest',
|
||||||
|
token => '',
|
||||||
|
sync_interval => 10,
|
||||||
|
cache_dir => '.',
|
||||||
|
);
|
||||||
|
|
||||||
|
my $client = Kenaito::Conf::Client->new(config => $cfg);
|
||||||
|
|
||||||
|
# 监听配置变更
|
||||||
|
$client->on_change(sub {
|
||||||
|
my ($changed) = @_;
|
||||||
|
for my $k (keys %$changed) {
|
||||||
|
print "config changed: $k = $changed->{$k}\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
# 启动客户端(拉取配置)
|
||||||
|
$client->start();
|
||||||
|
|
||||||
|
# 获取配置
|
||||||
|
my $value = $client->get('database.host');
|
||||||
|
my $all = $client->get_all();
|
||||||
|
|
||||||
|
# 手动同步一次
|
||||||
|
$client->sync_once();
|
||||||
|
|
||||||
|
# 停止
|
||||||
|
$client->stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置项
|
||||||
|
|
||||||
|
| 参数 | 说明 | 默认值 |
|
||||||
|
|----------------|------------|-------------------------|
|
||||||
|
| `base_url` | 配置中心地址 | `http://localhost:8080` |
|
||||||
|
| `app_name` | 应用名称 | (必填) |
|
||||||
|
| `env` | 环境标识 | `dev` |
|
||||||
|
| `version` | 配置版本 | `latest` |
|
||||||
|
| `token` | 应用 Token | 空 |
|
||||||
|
| `sync_interval`| 同步间隔(秒) | `60` |
|
||||||
|
| `ignore_global`| 忽略全局配置 | `0` |
|
||||||
|
| `cache_dir` | 缓存目录 | `.` |
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- Perl 5.16+
|
||||||
|
- HTTP::Tiny
|
||||||
|
- JSON::PP
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd kenaito-conf-perl-client-demo
|
||||||
|
KENAITO_BASE_URL=http://localhost:8080 \
|
||||||
|
KENAITO_APP_NAME=kenaito-springboot-demo \
|
||||||
|
KENAITO_ENV=daily \
|
||||||
|
perl app.pl
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package Kenaito::Conf;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Kenaito::Conf::Client;
|
||||||
|
use Kenaito::Conf::Config;
|
||||||
|
|
||||||
|
our $VERSION = '1.0.3';
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package Kenaito::Conf::Cache;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use JSON::PP;
|
||||||
|
use File::Spec;
|
||||||
|
|
||||||
|
use constant CACHE_FILE => 'config-cache.json';
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, $cache_dir) = @_;
|
||||||
|
$cache_dir //= '.';
|
||||||
|
return bless { dir => $cache_dir }, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub write {
|
||||||
|
my ($self, $data) = @_;
|
||||||
|
my $path = File::Spec->catfile($self->{dir}, CACHE_FILE);
|
||||||
|
open my $fh, '>:encoding(UTF-8)', $path or return;
|
||||||
|
print $fh JSON::PP->new->utf8->encode($data);
|
||||||
|
close $fh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $path = File::Spec->catfile($self->{dir}, CACHE_FILE);
|
||||||
|
open my $fh, '<:encoding(UTF-8)', $path or return undef;
|
||||||
|
local $/;
|
||||||
|
my $json = <$fh>;
|
||||||
|
close $fh;
|
||||||
|
return eval { JSON::PP->new->utf8->decode($json) };
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package Kenaito::Conf::Client;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use HTTP::Tiny;
|
||||||
|
use JSON::PP;
|
||||||
|
use Kenaito::Conf::Config;
|
||||||
|
use Kenaito::Conf::Cache;
|
||||||
|
use Kenaito::Conf::Listener;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, %args) = @_;
|
||||||
|
my $config = $args{config};
|
||||||
|
die "config is required" unless $config;
|
||||||
|
|
||||||
|
my $self = bless {
|
||||||
|
config => $config,
|
||||||
|
cache => Kenaito::Conf::Cache->new($config->{cache_dir}),
|
||||||
|
listener => Kenaito::Conf::Listener->new(),
|
||||||
|
configs => {},
|
||||||
|
last_time => 0,
|
||||||
|
started => 0,
|
||||||
|
stop => 0,
|
||||||
|
http => HTTP::Tiny->new(timeout => 10),
|
||||||
|
}, $class;
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub on_change {
|
||||||
|
my ($self, $handler) = @_;
|
||||||
|
$self->{listener}->on_change($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get {
|
||||||
|
my ($self, $key) = @_;
|
||||||
|
return $self->{configs}{$key};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_all {
|
||||||
|
my ($self) = @_;
|
||||||
|
return { %{$self->{configs}} };
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start {
|
||||||
|
my ($self) = @_;
|
||||||
|
return if $self->{started};
|
||||||
|
$self->{started} = 1;
|
||||||
|
|
||||||
|
eval {
|
||||||
|
$self->_fetch_all();
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
my $err = $@;
|
||||||
|
warn "[ConfigClient] fetch all configs error: $err, trying cache\n";
|
||||||
|
my $cached = $self->{cache}->read();
|
||||||
|
if ($cached) {
|
||||||
|
$self->_apply_configs($cached);
|
||||||
|
my $count = scalar keys %{$self->{configs}};
|
||||||
|
print "[ConfigClient] loaded $count configs from cache\n";
|
||||||
|
} else {
|
||||||
|
die "fetch configs failed and no cache: $err";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub stop {
|
||||||
|
my ($self) = @_;
|
||||||
|
$self->{stop} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sync_once {
|
||||||
|
my ($self) = @_;
|
||||||
|
eval {
|
||||||
|
$self->_fetch_changed();
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
warn "[ConfigClient] sync error: $@\n";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _fetch_all {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $url = "$self->{config}{base_url}/api/fetchAllConfigs";
|
||||||
|
my $payload = JSON::PP->new->utf8->encode({
|
||||||
|
appName => $self->{config}{app_name},
|
||||||
|
env => $self->{config}{env},
|
||||||
|
version => $self->{config}{version},
|
||||||
|
ignoreGlobal => $self->{config}{ignore_global} ? \1 : \0,
|
||||||
|
token => $self->{config}{token},
|
||||||
|
});
|
||||||
|
|
||||||
|
my $resp = $self->{http}->post($url, {
|
||||||
|
content => $payload,
|
||||||
|
headers => { 'Content-Type' => 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
die "HTTP error: $resp->{status} $resp->{reason}" unless $resp->{success};
|
||||||
|
|
||||||
|
my $data = JSON::PP->new->utf8->decode($resp->{content});
|
||||||
|
|
||||||
|
if (exists $data->{code} && $data->{code} != 0) {
|
||||||
|
die "server error: " . ($data->{msg} // 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (my $ts = $data->{'latest.fetch.timestamp'}) {
|
||||||
|
$self->{last_time} = int($ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{cache}->write($data);
|
||||||
|
$self->_apply_configs($data);
|
||||||
|
|
||||||
|
my $count = scalar keys %{$self->{configs}};
|
||||||
|
print "[ConfigClient] fetched $count configs\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _fetch_changed {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $url = "$self->{config}{base_url}/api/fetchChangedConfigs";
|
||||||
|
my $payload = JSON::PP->new->utf8->encode({
|
||||||
|
appName => $self->{config}{app_name},
|
||||||
|
env => $self->{config}{env},
|
||||||
|
version => $self->{config}{version},
|
||||||
|
lastTime => $self->{last_time},
|
||||||
|
ignoreGlobal => $self->{config}{ignore_global} ? \1 : \0,
|
||||||
|
token => $self->{config}{token},
|
||||||
|
});
|
||||||
|
|
||||||
|
my $resp = $self->{http}->post($url, {
|
||||||
|
content => $payload,
|
||||||
|
headers => { 'Content-Type' => 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
die "HTTP error: $resp->{status} $resp->{reason}" unless $resp->{success};
|
||||||
|
|
||||||
|
my $result = JSON::PP->new->utf8->decode($resp->{content});
|
||||||
|
|
||||||
|
if (($result->{code} // -1) != 0) {
|
||||||
|
die "server error code: $result->{code}";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $changed_data = $result->{data} || {};
|
||||||
|
|
||||||
|
if (my $ts = $changed_data->{'latest.fetch.timestamp'}) {
|
||||||
|
$self->{last_time} = int($ts);
|
||||||
|
}
|
||||||
|
delete $changed_data->{'latest.fetch.timestamp'};
|
||||||
|
|
||||||
|
# handle deleted keys
|
||||||
|
if (my $deleted_keys_str = delete $changed_data->{deletedKeys}) {
|
||||||
|
my @deleted = split /,/, $deleted_keys_str;
|
||||||
|
for my $key (@deleted) {
|
||||||
|
delete $self->{configs}{$key};
|
||||||
|
}
|
||||||
|
my $count = scalar @deleted;
|
||||||
|
print "[ConfigClient] $count configs deleted: " . join(', ', @deleted) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (%$changed_data) {
|
||||||
|
my %changed;
|
||||||
|
for my $k (keys %$changed_data) {
|
||||||
|
$self->{configs}{$k} = $changed_data->{$k};
|
||||||
|
$changed{$k} = $changed_data->{$k};
|
||||||
|
}
|
||||||
|
$self->{listener}->notify(\%changed);
|
||||||
|
my $count = scalar keys %changed;
|
||||||
|
print "[ConfigClient] $count configs changed: " . join(', ', keys %changed) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _apply_configs {
|
||||||
|
my ($self, $data) = @_;
|
||||||
|
for my $k (keys %$data) {
|
||||||
|
next if $k =~ /^kenaito\./ || $k eq 'latest.fetch.timestamp';
|
||||||
|
next unless defined $data->{$k} && !ref $data->{$k};
|
||||||
|
$self->{configs}{$k} = $data->{$k};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package Kenaito::Conf::Config;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class, %args) = @_;
|
||||||
|
return bless {
|
||||||
|
base_url => $args{base_url} // 'http://localhost:8080',
|
||||||
|
app_name => $args{app_name} // '',
|
||||||
|
env => $args{env} // 'dev',
|
||||||
|
version => $args{version} // 'latest',
|
||||||
|
token => $args{token} // '',
|
||||||
|
sync_interval => $args{sync_interval} // 60,
|
||||||
|
ignore_global => $args{ignore_global} // 0,
|
||||||
|
cache_dir => $args{cache_dir} // '.',
|
||||||
|
}, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package Kenaito::Conf::Listener;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class) = @_;
|
||||||
|
return bless { handlers => [] }, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub on_change {
|
||||||
|
my ($self, $handler) = @_;
|
||||||
|
push @{$self->{handlers}}, $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub notify {
|
||||||
|
my ($self, $changed) = @_;
|
||||||
|
for my $h (@{$self->{handlers}}) {
|
||||||
|
eval { $h->($changed); 1 } or warn "[ConfigClient] change handler error: $@\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -2,4 +2,4 @@ from kenaito_conf.client import ConfigClient
|
|||||||
from kenaito_conf.config import Config
|
from kenaito_conf.config import Config
|
||||||
|
|
||||||
__all__ = ["ConfigClient", "Config"]
|
__all__ = ["ConfigClient", "Config"]
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.3"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "kenaito-conf"
|
name = "kenaito-conf"
|
||||||
version = "1.0.0"
|
version = "1.0.3"
|
||||||
description = "Kenaito Config Center Python Client"
|
description = "Kenaito Config Center Python Client"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
Reference in New Issue
Block a user