feat(clients): 各客户端支持环境变量配置,Spring Boot 2 客户端优化选择性刷新
- Dockerfile.springboot2: 更换基础镜像为阿里云私有镜像 - .NET/SpringBoot2 demo: 配置项改为从环境变量读取,支持 Docker 传参 - Spring Boot 2 客户端: @Value/@ConfigurationProperties 刷新优化为只刷新受变更 key 影响的字段 - README_DOCKERFILE.md: 标注 .NET 客户端 Docker 测试通过
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM maven:3.9-eclipse-temurin-8 AS build
|
FROM registry.cn-shanghai.aliyuncs.com/odboy/kenaito-cicd:runtime-maven3-jdk21 AS build
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -9,12 +9,13 @@ WORKDIR /src/kenaito-conf-springboot2-client
|
|||||||
RUN mvn clean install -DskipTests
|
RUN mvn clean install -DskipTests
|
||||||
|
|
||||||
# Copy demo project and build
|
# Copy demo project and build
|
||||||
|
WORKDIR /src
|
||||||
COPY kenaito-conf-springboot2-client-demo/ ./kenaito-conf-springboot2-client-demo/
|
COPY kenaito-conf-springboot2-client-demo/ ./kenaito-conf-springboot2-client-demo/
|
||||||
WORKDIR /src/kenaito-conf-springboot2-client-demo
|
WORKDIR /src/kenaito-conf-springboot2-client-demo
|
||||||
RUN mvn clean package -DskipTests
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
FROM eclipse-temurin:8-jre
|
FROM registry.cn-shanghai.aliyuncs.com/odboy/kenaito-cicd:runtime-jdk8
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /src/kenaito-conf-springboot2-client-demo/target/*.jar app.jar
|
COPY --from=build /src/kenaito-conf-springboot2-client-demo/target/*.jar app.jar
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ curl http://localhost:8082/health
|
|||||||
docker stop python-demo
|
docker stop python-demo
|
||||||
```
|
```
|
||||||
|
|
||||||
## .NET 客户端
|
## .NET 客户端(测试通过)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -f Dockerfile.dotnet -t kenaito-conf-dotnet-demo .
|
docker build -f Dockerfile.dotnet -t kenaito-conf-dotnet-demo .
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ using Kenaito.Conf.Client;
|
|||||||
|
|
||||||
var config = new Config
|
var config = new Config
|
||||||
{
|
{
|
||||||
BaseUrl = "http://localhost:8080",
|
BaseUrl = Environment.GetEnvironmentVariable("KENAITO_BASE_URL") ?? "http://localhost:8080",
|
||||||
AppName = "kenaito-springboot-demo",
|
AppName = Environment.GetEnvironmentVariable("KENAITO_APP_NAME") ?? "kenaito-springboot-demo",
|
||||||
Env = "daily",
|
Env = Environment.GetEnvironmentVariable("KENAITO_ENV") ?? "daily",
|
||||||
Version = "latest",
|
Version = Environment.GetEnvironmentVariable("KENAITO_VERSION") ?? "latest",
|
||||||
SyncInterval = 10,
|
SyncInterval = int.TryParse(Environment.GetEnvironmentVariable("KENAITO_SYNC_INTERVAL"), out var si) ? si : 10,
|
||||||
CacheDir = "."
|
CacheDir = Environment.GetEnvironmentVariable("KENAITO_CACHE_DIR") ?? ".",
|
||||||
|
Token = Environment.GetEnvironmentVariable("KENAITO_TOKEN")
|
||||||
};
|
};
|
||||||
|
|
||||||
var client = new Client(config);
|
var client = new Client(config);
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ app.name=kenaito-conf-springboot2-client-demo
|
|||||||
app.version=1.0.0
|
app.version=1.0.0
|
||||||
|
|
||||||
# Kenaito Config Center
|
# Kenaito Config Center
|
||||||
kenaito.config.baseUrl=http://localhost:8080
|
kenaito.config.baseUrl=${KENAITO_BASE_URL:http://localhost:8080}
|
||||||
kenaito.config.appName=kenaito-springboot-demo
|
kenaito.config.appName=${KENAITO_APP_NAME:kenaito-springboot-demo}
|
||||||
kenaito.config.profile=daily
|
kenaito.config.profile=${KENAITO_ENV:daily}
|
||||||
kenaito.config.version=latest
|
kenaito.config.version=${KENAITO_VERSION:latest}
|
||||||
kenaito.config.token=
|
kenaito.config.token=${KENAITO_TOKEN:}
|
||||||
kenaito.config.sync-interval-seconds=10
|
kenaito.config.sync-interval-seconds=${KENAITO_SYNC_INTERVAL:10}
|
||||||
|
|
||||||
# Local configs (will be overridden by remote if exists)
|
# Local configs (will be overridden by remote if exists)
|
||||||
database.host=192.168.1.1
|
database.host=192.168.1.1
|
||||||
|
|||||||
+8
-6
@@ -333,6 +333,8 @@ public class ConfigcenterContext {
|
|||||||
result = LocalCacheUtils.read();
|
result = LocalCacheUtils.read();
|
||||||
} else {
|
} else {
|
||||||
LocalCacheUtils.write(result);
|
LocalCacheUtils.write(result);
|
||||||
|
int configCount = result.size();
|
||||||
|
logger.info("[ConfigClient] fetched {} configs", configCount);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -410,7 +412,7 @@ public class ConfigcenterContext {
|
|||||||
|
|
||||||
// Refresh @Value and @ConfigurationProperties fields
|
// Refresh @Value and @ConfigurationProperties fields
|
||||||
logger.info("[RefreshScope] Calling refreshDynamicConfigFields()...");
|
logger.info("[RefreshScope] Calling refreshDynamicConfigFields()...");
|
||||||
refreshDynamicConfigFields();
|
refreshDynamicConfigFields(updateConfig.keySet());
|
||||||
logger.info("[RefreshScope] refreshDynamicConfigFields() completed");
|
logger.info("[RefreshScope] refreshDynamicConfigFields() completed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,13 +450,13 @@ public class ConfigcenterContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Refresh @Value and @ConfigurationProperties fields
|
// Refresh @Value and @ConfigurationProperties fields
|
||||||
refreshDynamicConfigFields();
|
refreshDynamicConfigFields(new HashSet<>(keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshDynamicConfigFields() {
|
private void refreshDynamicConfigFields(Set<String> changedKeys) {
|
||||||
if (applicationContext != null) {
|
if (applicationContext != null) {
|
||||||
try {
|
try {
|
||||||
logger.info("[RefreshScope] Starting dynamic config field refresh...");
|
logger.info("[RefreshScope] Starting dynamic config field refresh for keys: {}", String.join(", ", changedKeys));
|
||||||
StandardEnvironment env = (StandardEnvironment) applicationContext.getEnvironment();
|
StandardEnvironment env = (StandardEnvironment) applicationContext.getEnvironment();
|
||||||
// 直接创建 RefreshScopeRegistry 并注册为 BeanPostProcessor
|
// 直接创建 RefreshScopeRegistry 并注册为 BeanPostProcessor
|
||||||
RefreshScopeRegistry registry = new RefreshScopeRegistry(env);
|
RefreshScopeRegistry registry = new RefreshScopeRegistry(env);
|
||||||
@@ -474,8 +476,8 @@ public class ConfigcenterContext {
|
|||||||
configPropsCount = registry.getConfigPropsCount();
|
configPropsCount = registry.getConfigPropsCount();
|
||||||
logger.info("[RefreshScope] Recorded {} @Value fields and {} @ConfigurationProperties beans", valueFieldCount, configPropsCount);
|
logger.info("[RefreshScope] Recorded {} @Value fields and {} @ConfigurationProperties beans", valueFieldCount, configPropsCount);
|
||||||
|
|
||||||
// 刷新
|
// 刷新(只刷新受影响的字段)
|
||||||
registry.refresh();
|
registry.refresh(changedKeys);
|
||||||
logger.info("[RefreshScope] RefreshScopeRegistry.refresh() completed, refreshed {} @Value fields and {} @ConfigurationProperties beans", registry.getValueFieldCount(), registry.getConfigPropsCount());
|
logger.info("[RefreshScope] RefreshScopeRegistry.refresh() completed, refreshed {} @Value fields and {} @ConfigurationProperties beans", registry.getValueFieldCount(), registry.getConfigPropsCount());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("[RefreshScope] RefreshScopeRegistry.refresh() error, msg:{}", e.getMessage(), e);
|
logger.warn("[RefreshScope] RefreshScopeRegistry.refresh() error, msg:{}", e.getMessage(), e);
|
||||||
|
|||||||
+4
-1
@@ -72,7 +72,10 @@ public class InternalConfigChangeListener {
|
|||||||
logger.info("[ConfigClient] {} configs deleted: {}", deletedKeys.length, String.join(", ", deletedKeys));
|
logger.info("[ConfigClient] {} configs deleted: {}", deletedKeys.length, String.join(", ", deletedKeys));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!data.isEmpty()) context.updateConfig(data);
|
if (!data.isEmpty()) {
|
||||||
|
context.updateConfig(data);
|
||||||
|
logger.info("[ConfigClient] {} configs changed: {}", data.size(), String.join(", ", data.keySet()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+16
-10
@@ -3,6 +3,7 @@ package cn.odboy.confcenter;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -60,15 +61,16 @@ public class RefreshScopeRegistry implements BeanPostProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh all @Value fields and @ConfigurationProperties beans
|
* Refresh only @Value fields and @ConfigurationProperties beans that are affected by changed keys
|
||||||
*/
|
*/
|
||||||
public void refresh() {
|
public void refresh(Set<String> changedKeys) {
|
||||||
refreshValueFields();
|
refreshValueFields(changedKeys);
|
||||||
refreshConfigPropsBeans();
|
refreshConfigPropsBeans(changedKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshValueFields() {
|
private void refreshValueFields(Set<String> changedKeys) {
|
||||||
for (ValueFieldHolder holder : valueFields) {
|
for (ValueFieldHolder holder : valueFields) {
|
||||||
|
if (changedKeys.contains(holder.key)) {
|
||||||
try {
|
try {
|
||||||
holder.refresh(environment);
|
holder.refresh(environment);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -76,11 +78,12 @@ public class RefreshScopeRegistry implements BeanPostProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshConfigPropsBeans() {
|
private void refreshConfigPropsBeans(Set<String> changedKeys) {
|
||||||
for (ConfigPropsHolder holder : configPropsBeans) {
|
for (ConfigPropsHolder holder : configPropsBeans) {
|
||||||
try {
|
try {
|
||||||
holder.refresh(environment);
|
holder.refresh(environment, changedKeys);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// ignore refresh errors
|
// ignore refresh errors
|
||||||
}
|
}
|
||||||
@@ -149,26 +152,29 @@ public class RefreshScopeRegistry implements BeanPostProcessor {
|
|||||||
this.bean = bean;
|
this.bean = bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh(Environment env) {
|
void refresh(Environment env, Set<String> changedKeys) {
|
||||||
Class<?> clazz = bean.getClass();
|
Class<?> clazz = bean.getClass();
|
||||||
ConfigurationProperties anno = clazz.getAnnotation(ConfigurationProperties.class);
|
ConfigurationProperties anno = clazz.getAnnotation(ConfigurationProperties.class);
|
||||||
if (anno != null) {
|
if (anno != null) {
|
||||||
String prefix = anno.prefix();
|
String prefix = anno.prefix();
|
||||||
bindProperties(bean, prefix, env);
|
bindProperties(bean, prefix, env, changedKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindProperties(Object bean, String prefix, Environment env) {
|
private void bindProperties(Object bean, String prefix, Environment env, Set<String> changedKeys) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = bean.getClass();
|
Class<?> clazz = bean.getClass();
|
||||||
for (Field field : getAllDeclaredFields(clazz)) {
|
for (Field field : getAllDeclaredFields(clazz)) {
|
||||||
String propertyName = prefix + "." + camelToKebab(field.getName());
|
String propertyName = prefix + "." + camelToKebab(field.getName());
|
||||||
|
// Only refresh if this property key is in changedKeys
|
||||||
|
if (changedKeys.contains(propertyName)) {
|
||||||
String value = env.getProperty(propertyName);
|
String value = env.getProperty(propertyName);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
field.set(bean, convertType(field.getType(), value));
|
field.set(bean, convertType(field.getType(), value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user