- 新增 kenaito-conf-bash-client(Bash 4.0+,依赖 curl、jq)及对应 demo - 新增 kenaito-conf-dart-client(Dart 2.12+,空安全)及对应 demo - 新增 Dockerfile.bash(debian:bookworm-slim)和 Dockerfile.dart(dart:3.5) - README 技术栈和项目结构补充 Bash、Dart 客户端 - README_EN.md 同步更新 - README_DOCKERFILE.md 补充 Bash、Dart 构建命令 - 修复 Bash 客户端 curl 2>&1 混入 stderr 导致响应解析失败 - 修复 Dart 客户端 latest.fetch.timestamp 类型转换兼容 string/num
265 lines
7.3 KiB
Bash
265 lines
7.3 KiB
Bash
#!/usr/bin/env bash
|
|
# Kenaito Config Center Bash Client
|
|
# 依赖: curl, jq
|
|
# 用法: source kenaito_conf.sh 后调用函数
|
|
|
|
KENAITO_CONF_VERSION="1.0.3"
|
|
|
|
# 配置变量(使用前需设置)
|
|
KENAITO_BASE_URL="${KENAITO_BASE_URL:-http://localhost:8080}"
|
|
KENAITO_APP_NAME="${KENAITO_APP_NAME:-}"
|
|
KENAITO_ENV="${KENAITO_ENV:-dev}"
|
|
KENAITO_VERSION="${KENAITO_VERSION:-latest}"
|
|
KENAITO_TOKEN="${KENAITO_TOKEN:-}"
|
|
KENAITO_SYNC_INTERVAL="${KENAITO_SYNC_INTERVAL:-10}"
|
|
KENAITO_IGNORE_GLOBAL="${KENAITO_IGNORE_GLOBAL:-false}"
|
|
KENAITO_CACHE_DIR="${KENAITO_CACHE_DIR:-.}"
|
|
|
|
# 内部状态
|
|
_KENAITO_LAST_TIME=0
|
|
_KENAITO_STARTED=false
|
|
declare -A _KENAITO_CONFIGS
|
|
|
|
# 获取缓存文件路径
|
|
_kenaito_cache_file() {
|
|
echo "${KENAITO_CACHE_DIR}/config-cache.json"
|
|
}
|
|
|
|
# 从缓存读取
|
|
_kenaito_cache_read() {
|
|
local cache_file
|
|
cache_file=$(_kenaito_cache_file)
|
|
if [ -f "$cache_file" ]; then
|
|
cat "$cache_file"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# 写入缓存
|
|
_kenaito_cache_write() {
|
|
local data="$1"
|
|
local cache_file
|
|
cache_file=$(_kenaito_cache_file)
|
|
mkdir -p "$(dirname "$cache_file")"
|
|
echo "$data" > "$cache_file"
|
|
}
|
|
|
|
# HTTP POST 请求
|
|
_kenaito_http_post() {
|
|
local url="$1"
|
|
local payload="$2"
|
|
local response_file
|
|
local http_code_file
|
|
local http_code
|
|
|
|
response_file=$(mktemp)
|
|
http_code_file=$(mktemp)
|
|
|
|
curl -s -o "$response_file" -w "%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
--connect-timeout 10 \
|
|
--max-time 10 \
|
|
"$url" > "$http_code_file" 2>/dev/null
|
|
|
|
local curl_exit=$?
|
|
http_code=$(cat "$http_code_file")
|
|
|
|
if [ $curl_exit -ne 0 ]; then
|
|
echo "ERROR: curl failed with exit code $curl_exit" >&2
|
|
rm -f "$response_file" "$http_code_file"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$http_code" != "200" ]; then
|
|
echo "ERROR: HTTP $http_code" >&2
|
|
rm -f "$response_file" "$http_code_file"
|
|
return 1
|
|
fi
|
|
|
|
cat "$response_file"
|
|
rm -f "$response_file" "$http_code_file"
|
|
}
|
|
|
|
# 应用配置到本地存储
|
|
_kenaito_apply_configs() {
|
|
local data="$1"
|
|
_KENAITO_CONFIGS=()
|
|
|
|
local keys
|
|
keys=$(echo "$data" | jq -r 'keys[]' 2>/dev/null)
|
|
|
|
for key in $keys; do
|
|
if [[ "$key" == kenaito.* ]] || [[ "$key" == "latest.fetch.timestamp" ]]; then
|
|
continue
|
|
fi
|
|
local value
|
|
value=$(echo "$data" | jq -r --arg k "$key" '.[$k] // empty' 2>/dev/null)
|
|
if [ -n "$value" ]; then
|
|
_KENAITO_CONFIGS["$key"]="$value"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# 获取配置值
|
|
kenaito_get() {
|
|
local key="$1"
|
|
echo "${_KENAITO_CONFIGS[$key]:-}"
|
|
}
|
|
|
|
# 获取所有配置(输出为 key=value 格式)
|
|
kenaito_get_all() {
|
|
for key in "${!_KENAITO_CONFIGS[@]}"; do
|
|
echo "$key=${_KENAITO_CONFIGS[$key]}"
|
|
done
|
|
}
|
|
|
|
# 全量拉取配置
|
|
kenaito_fetch_all() {
|
|
local url="${KENAITO_BASE_URL}/api/fetchAllConfigs"
|
|
local payload
|
|
payload=$(jq -c -n \
|
|
--arg appName "$KENAITO_APP_NAME" \
|
|
--arg env "$KENAITO_ENV" \
|
|
--arg version "$KENAITO_VERSION" \
|
|
--argjson ignoreGlobal "$KENAITO_IGNORE_GLOBAL" \
|
|
--arg token "$KENAITO_TOKEN" \
|
|
'{appName: $appName, env: $env, version: $version, ignoreGlobal: $ignoreGlobal, token: $token}')
|
|
|
|
local data
|
|
data=$(_kenaito_http_post "$url" "$payload")
|
|
local exit_code=$?
|
|
if [ $exit_code -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
local code
|
|
code=$(echo "$data" | jq -r '.code // empty' 2>/dev/null)
|
|
if [ -n "$code" ] && [ "$code" != "0" ]; then
|
|
local msg
|
|
msg=$(echo "$data" | jq -r '.msg // "unknown"' 2>/dev/null)
|
|
echo "ERROR: server error: $msg" >&2
|
|
return 1
|
|
fi
|
|
|
|
local timestamp
|
|
timestamp=$(echo "$data" | jq -r '."latest.fetch.timestamp" // empty' 2>/dev/null)
|
|
if [ -n "$timestamp" ]; then
|
|
_KENAITO_LAST_TIME=$timestamp
|
|
fi
|
|
|
|
_kenaito_cache_write "$data"
|
|
_kenaito_apply_configs "$data"
|
|
|
|
local count=${#_KENAITO_CONFIGS[@]}
|
|
echo "[ConfigClient] fetched $count configs" >&2
|
|
}
|
|
|
|
# 增量拉取变更
|
|
kenaito_fetch_changed() {
|
|
local url="${KENAITO_BASE_URL}/api/fetchChangedConfigs"
|
|
local payload
|
|
payload=$(jq -c -n \
|
|
--arg appName "$KENAITO_APP_NAME" \
|
|
--arg env "$KENAITO_ENV" \
|
|
--arg version "$KENAITO_VERSION" \
|
|
--argjson lastTime "$_KENAITO_LAST_TIME" \
|
|
--argjson ignoreGlobal "$KENAITO_IGNORE_GLOBAL" \
|
|
--arg token "$KENAITO_TOKEN" \
|
|
'{appName: $appName, env: $env, version: $version, lastTime: $lastTime, ignoreGlobal: $ignoreGlobal, token: $token}')
|
|
|
|
local result
|
|
result=$(_kenaito_http_post "$url" "$payload")
|
|
local exit_code=$?
|
|
if [ $exit_code -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
local code
|
|
code=$(echo "$result" | jq -r '.code // empty' 2>/dev/null)
|
|
if [ -n "$code" ] && [ "$code" != "0" ]; then
|
|
echo "ERROR: server error code: $code" >&2
|
|
return 1
|
|
fi
|
|
|
|
local changed_data
|
|
changed_data=$(echo "$result" | jq -c '.data // {}' 2>/dev/null)
|
|
|
|
local timestamp
|
|
timestamp=$(echo "$changed_data" | jq -r '."latest.fetch.timestamp" // empty' 2>/dev/null)
|
|
if [ -n "$timestamp" ]; then
|
|
_KENAITO_LAST_TIME=$timestamp
|
|
fi
|
|
|
|
# 处理删除的配置
|
|
local deleted_keys
|
|
deleted_keys=$(echo "$changed_data" | jq -r '.deletedKeys // empty' 2>/dev/null)
|
|
if [ -n "$deleted_keys" ]; then
|
|
IFS=',' read -ra keys <<< "$deleted_keys"
|
|
for key in "${keys[@]}"; do
|
|
unset "_KENAITO_CONFIGS[$key]"
|
|
done
|
|
local count=${#keys[@]}
|
|
echo "[ConfigClient] $count configs deleted: $deleted_keys" >&2
|
|
fi
|
|
|
|
# 处理变更的配置
|
|
local changed_count=0
|
|
local changed_keys_list=""
|
|
local keys
|
|
keys=$(echo "$changed_data" | jq -r 'keys[]' 2>/dev/null)
|
|
|
|
for key in $keys; do
|
|
if [[ "$key" == "latest.fetch.timestamp" ]] || [[ "$key" == "deletedKeys" ]]; then
|
|
continue
|
|
fi
|
|
local value
|
|
value=$(echo "$changed_data" | jq -r --arg k "$key" '.[$k] // empty' 2>/dev/null)
|
|
if [ -n "$value" ]; then
|
|
_KENAITO_CONFIGS["$key"]="$value"
|
|
if [ -n "$changed_keys_list" ]; then
|
|
changed_keys_list="$changed_keys_list, $key"
|
|
else
|
|
changed_keys_list="$key"
|
|
fi
|
|
changed_count=$((changed_count + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $changed_count -gt 0 ]; then
|
|
if type kenaito_on_change &>/dev/null; then
|
|
kenaito_on_change "$changed_keys_list"
|
|
fi
|
|
echo "[ConfigClient] $changed_count configs changed: $changed_keys_list" >&2
|
|
fi
|
|
}
|
|
|
|
# 启动客户端
|
|
kenaito_start() {
|
|
if [ "$_KENAITO_STARTED" = true ]; then
|
|
return
|
|
fi
|
|
_KENAITO_STARTED=true
|
|
|
|
if ! kenaito_fetch_all; then
|
|
echo "[ConfigClient] fetch all configs error, trying cache" >&2
|
|
local cached
|
|
if cached=$(_kenaito_cache_read); then
|
|
_kenaito_apply_configs "$cached"
|
|
local count=${#_KENAITO_CONFIGS[@]}
|
|
echo "[ConfigClient] loaded $count configs from cache" >&2
|
|
else
|
|
echo "[ConfigClient] fetch configs failed and no cache" >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# 同步一次
|
|
kenaito_sync_once() {
|
|
if ! kenaito_fetch_changed; then
|
|
echo "[ConfigClient] sync error" >&2
|
|
fi
|
|
}
|