From 751a0f770faa9bb8495a26f730a94d49d8d1d190 Mon Sep 17 00:00:00 2001 From: Odboy Date: Wed, 25 Mar 2026 21:00:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=85=8D=E7=BD=AE=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E4=BC=98=E5=8C=96=E5=92=8C=E5=8A=A0=E5=AF=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor(springboot2): 简化配置刷新逻辑,移除 EnvironmentChangeEvent 包装 - ConfigChangeHanlder 直接接收 Map 参数 - 移除 EnvironmentChangeEvent 事件类 - 优化 token 参数构建和敏感信息脱敏处理 - refactor(server): 移除配置加密功能 - 删除 CryptoConfig 结构体和相关配置项 - 移除 application-dev.properties 中的加密配置 - 项目不再使用配置加密功能 - docs: 更新 README 和模块文档 - fix: 修复 Node.js 客户端配置缓存 --- README.md | 1 - .../config-cache.json | 1 - .../odboy/confcenter/ConfigChangeHanlder.java | 2 +- .../odboy/confcenter/ConfigcenterContext.java | 984 ++++++++---------- .../confcenter/EnvironmentChangeEvent.java | 24 - server/internal/config/config.go | 7 - 6 files changed, 448 insertions(+), 571 deletions(-) delete mode 100644 kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/EnvironmentChangeEvent.java diff --git a/README.md b/README.md index 745e9d5..ac82b49 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ database.user=root database.password=your_password jwt.secret=your-jwt-secret jwt.expireHours=24 -crypto.secretKey=your-crypto-key ``` 启动服务(首次启动自动建表并初始化默认数据): diff --git a/kenaito-conf-nodejs-client-demo/config-cache.json b/kenaito-conf-nodejs-client-demo/config-cache.json index 06b7a46..e79b693 100644 --- a/kenaito-conf-nodejs-client-demo/config-cache.json +++ b/kenaito-conf-nodejs-client-demo/config-cache.json @@ -1,5 +1,4 @@ { - "crypto.secretKey": "k9#mP2$vL7qR4xN8", "database.host": "127.0.0.1", "database.name": "kenaito_conf", "database.password": "1234567", diff --git a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigChangeHanlder.java b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigChangeHanlder.java index d6c51a9..8c703ae 100644 --- a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigChangeHanlder.java +++ b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigChangeHanlder.java @@ -4,5 +4,5 @@ import java.util.Map; public interface ConfigChangeHanlder { - public void onConfigChanged(Map changedConfigs); + void onConfigChanged(Map changedConfigs); } diff --git a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigcenterContext.java b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigcenterContext.java index 8e22527..03821e9 100644 --- a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigcenterContext.java +++ b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/ConfigcenterContext.java @@ -7,12 +7,6 @@ import java.security.MessageDigest; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; @@ -34,535 +28,451 @@ import com.alibaba.fastjson2.JSON; */ public class ConfigcenterContext { - private final static Logger logger = LoggerFactory.getLogger("cn.odboy"); - - private static ConfigcenterContext instance = new ConfigcenterContext(); - - private List sensitiveKeys = new ArrayList<>(Arrays.asList("password", "key", "secret", "token", "credentials")); - - public static final String MANAGER_PROPERTY_SOURCE = "kenaitoConfigPropertySource"; - - private static final String IGNORE_PLACEHOLER = "[Ignore]"; - private static final String CRYPT_PREFIX = "{Cipher}"; - - // Local property storage (replaces ResourceUtils) - private static final Map localProps = new ConcurrentHashMap<>(); - - // Spring ApplicationContext (replaces InstanceFactory) - private static ApplicationContext applicationContext; - - private Boolean remoteEnabled; - private final String nodeId; - private String[] apiBaseUrls; - private String app; - private String env; - private String version; - private boolean ignoreGlobal; - private String secret; - private String globalSecret; - private String token; - private boolean remoteFirst = false; - private boolean isSpringboot; - private int syncIntervalSeconds = 50; - private InternalConfigChangeListener configChangeListener; - private List configChangeHanlders; - private boolean processed; - private Properties remoteProperties; - - private ConfigcenterContext() { - String name; - try { - name = InetAddress.getLocalHost().getHostName(); - } catch (Exception e) { - name = "unknown"; - } - this.nodeId = name; - } - - public static ConfigcenterContext getInstance() { - return instance; - } - - public static void setApplicationContext(ApplicationContext ctx) { - applicationContext = ctx; - } - - public static ApplicationContext getApplicationContext() { - return applicationContext; - } - - // ==================== Property Utils (replaces ResourceUtils) ==================== - - public static void mergeProperties(Properties props) { - if (props != null) { - for (Entry entry : props.entrySet()) { - localProps.put(entry.getKey().toString(), entry.getValue().toString()); - } - } - } - - public static void addProperty(String key, String value) { - localProps.put(key, value); - } - - public static String getProperty(String key) { - String val = localProps.get(key); - if (val == null) val = System.getProperty(key); - if (val == null) val = System.getenv(key); - return val; - } - - public static String getProperty(String key, String defaultValue) { - String val = getProperty(key); - return val != null && !val.isEmpty() ? val : defaultValue; - } - - public static boolean getBoolean(String key, boolean defaultValue) { - String val = getProperty(key); - if (val == null || val.isEmpty()) return defaultValue; - return Boolean.parseBoolean(val); - } - - public static int getInt(String key, int defaultValue) { - String val = getProperty(key); - if (val == null || val.isEmpty()) return defaultValue; - try { - return Integer.parseInt(val); - } catch (NumberFormatException e) { - return defaultValue; - } - } - - public static String getAnyProperty(String... keys) { - for (String key : keys) { - String val = getProperty(key); - if (val != null && !val.isEmpty()) return val; - } - return null; - } - - // ==================== Crypto Utils (replaces mendmix AES/DES/Base64/DigestUtils) ==================== - - public static String md5(String input) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - for (byte b : digest) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } catch (Exception e) { - throw new RuntimeException("MD5 error", e); - } - } - - public static String md5Short(String input) { - if (input == null || input.isEmpty()) return ""; - return md5(input).substring(0, 16); - } - - private static String desEncrypt(String key, String data) { - try { - SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "DES"); - Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(encrypted); - } catch (Exception e) { - throw new RuntimeException("DES encrypt error", e); - } - } - - private static byte[] aesDecrypt(byte[] data, byte[] key) throws Exception { - SecretKey secretKey = new SecretKeySpec(key, "AES"); - // Use CBC mode with zero IV to match Go server - IvParameterSpec iv = new IvParameterSpec(new byte[16]); - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); - byte[] decrypted = cipher.doFinal(data); - // Remove PKCS7 padding - int padding = decrypted[decrypted.length - 1] & 0xFF; - if (padding > 0 && padding <= 16) { - byte[] result = new byte[decrypted.length - padding]; - System.arraycopy(decrypted, 0, result, 0, result.length); - return result; - } - return decrypted; - } - - private static String decryptWithAES(String key, String data) { - try { - String secretKey = md5(key).substring(16); - byte[] encrypted = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)); - byte[] decrypted = aesDecrypt(encrypted, secretKey.getBytes(StandardCharsets.UTF_8)); - return new String(decrypted, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new RuntimeException(String.format("解密错误:%s", data)); - } - } - - // ==================== HTTP Utils (replaces mendmix HttpUtils) ==================== - - public static String httpGet(String url) throws Exception { - return HttpClientUtils.get(url); - } - - public static String httpPostJson(String url, String json) throws Exception { - return HttpClientUtils.postJson(url, json); - } - - // ==================== Core Methods ==================== - - public boolean isProcessed() { - return processed; - } - - public synchronized void init(Properties properties, boolean isSpringboot) { - if (processed || !isRemoteEnabled()) return; - if (properties != null) mergeProperties(properties); - - System.setProperty("client.nodeId", nodeId); - System.setProperty("springboot", String.valueOf(isSpringboot)); - this.isSpringboot = isSpringboot; - - String defaultAppName = getProperty("spring.application.name"); - app = getProperty("kenaito.config.appName", defaultAppName); - if (remoteEnabled == null) remoteEnabled = getBoolean("kenaito.config.enabled", true); - - if (!isRemoteEnabled()) return; - - env = getProperty("kenaito.config.profile", "dev"); - Validate.notBlank(env, "[kenaito.config.profile] is required"); - - setApiBaseUrl(getAnyProperty("kenaito.config.baseUrl", "kenaito.config.baseUrl")); - - version = getProperty("kenaito.config.version", "latest"); - ignoreGlobal = getBoolean("kenaito.config.global-ignore", false); - syncIntervalSeconds = getInt("kenaito.config.sync-interval-seconds", 60); - token = getProperty("kenaito.config.token"); - - logger.info("\n=====Configcenter config=====\nappName:{}\nenv:{}\nversion:{}\nremoteEnabled:{}\napiBaseUrls:{}\n=====Configcenter config=====", - app, env, version, isRemoteEnabled(), JSON.toJSONString(apiBaseUrls)); - } - - public boolean isRemoteEnabled() { - return remoteEnabled == null || remoteEnabled; - } - - public void setRemoteEnabled(boolean remoteEnabled) { - this.remoteEnabled = remoteEnabled; - } - - public String[] getApiBaseUrls() { - return apiBaseUrls; - } - - public void setApiBaseUrl(String apiBaseUrl) { - Validate.notBlank(apiBaseUrl, "[kenaito.config.baseUrl] is required"); - String[] urls = apiBaseUrl.split(",|;"); - this.apiBaseUrls = new String[urls.length]; - for (int i = 0; i < urls.length; i++) { - if (urls[i].endsWith("/")) { - this.apiBaseUrls[i] = urls[i].substring(0, urls[i].length() - 1); - } else { - this.apiBaseUrls[i] = urls[i]; - } - } - } - - public String getApp() { return app; } - public String getEnv() { return env; } - public String getVersion() { return version; } - public boolean isIgnoreGlobal() { return ignoreGlobal; } - public String getSecret() { return secret; } - public boolean isRemoteFirst() { return remoteFirst; } - public int getSyncIntervalSeconds() { return syncIntervalSeconds; } - public String getNodeId() { return nodeId; } - public boolean isSpringboot() { return isSpringboot; } - - public void mergeRemoteProperties(Properties properties) { - if (!remoteEnabled) return; - remoteProperties = getAllRemoteProperties(); - if (remoteProperties != null) { - Set> entrySet = remoteProperties.entrySet(); - for (Entry entry : entrySet) { - if (!isRemoteFirst() && properties.containsKey(entry.getKey())) { - continue; - } - String value = entry.getValue().toString(); - if (IGNORE_PLACEHOLER.equals(value)) continue; - properties.setProperty(entry.getKey().toString(), value); - } - } - - // 替换本地变量占位符 - Set> entrySet = properties.entrySet(); - for (Entry entry : entrySet) { - String key = entry.getKey().toString(); - String value = entry.getValue().toString(); - addProperty(key, value); - } - - // 加载本地测试配置 - String path = System.getProperty("localtest.config.location"); - if (StringUtils.isNotBlank(path)) { - try (FileReader fileReader = new FileReader(path)) { - Properties localProperties = new Properties(); - localProperties.load(fileReader); - if (!localProperties.isEmpty()) { - properties.putAll(localProperties); - } - } catch (Exception e) { - logger.warn("Failed to load local test config from {}: {}", path, e.getMessage()); - } - } - - // Close existing listener before creating new one - if (configChangeListener != null) { - configChangeListener.close(); - } - // register listener - configChangeListener = new InternalConfigChangeListener(); - printConfigs(properties); - } - - private Properties getAllRemoteProperties() { - if (remoteProperties != null) return remoteProperties; - Properties properties = new Properties(); - - Map map = fetchConfigFromServer(); - if (map == null) { - throw new RuntimeException("fetch remote config error!"); - } - - // 解密密匙 - secret = Objects.toString(map.remove("kenaito.config.encrypt-secret"), null); - globalSecret = Objects.toString(map.remove("kenaito.config.global-encrypt-secret"), null); - remoteFirst = Boolean.parseBoolean(Objects.toString(map.remove("kenaito.config.remote-config-first"), "false")); - - Set keys = map.keySet(); - for (String key : keys) { - Object value = decodeEncryptIfRequire(map.get(key)); - properties.put(key, value); - } - - return properties; - } - - @SuppressWarnings("unchecked") - private Map fetchConfigFromServer() { - Map result = null; - String errorMsg = null; - for (String apiBaseUrl : apiBaseUrls) { - String url = apiBaseUrl + "/api/fetchAllConfigs"; - Map params = new HashMap<>(); - params.put("appName", app); - params.put("env", env); - params.put("version", version); - params.put("ignoreGlobal", ignoreGlobal); - if (token != null && !token.isEmpty()) { - params.put("token", token); - } - logger.info("fetch configs url:{}", url); - try { - String jsonString = httpPostJson(url, JSON.toJSONString(params)); - if (jsonString != null) { - result = JSON.parseObject(jsonString, Map.class); - if (result.containsKey("code")) { - errorMsg = result.get("msg").toString(); - logger.error("fetch error:{}", errorMsg); - result = null; - } else { - break; - } - } - } catch (Exception e) { - logger.error("fetch config error from {}: {}", apiBaseUrl, e.getMessage()); - } - } - - if (result == null) { - logger.warn(">>>>>remote config fetch error, load from local Cache"); - result = LocalCacheUtils.read(); - } else { - LocalCacheUtils.write(result); - } - return result; - } - - private void printConfigs(Properties properties) { - List sortKeys = new ArrayList<>(); - Set> entrySet = properties.entrySet(); - for (Entry entry : entrySet) { - sortKeys.add(entry.getKey().toString()); - } - Collections.sort(sortKeys); - System.out.println("==================final config list start=================="); - for (String key : sortKeys) { - String value = hideSensitive(key, properties.getProperty(key)); - System.out.printf("%s = %s%n", key, value); - } - System.out.println("==================final config list end===================="); - } - - @SuppressWarnings("unchecked") - public synchronized void updateConfig(Map updateConfig) { - if (!updateConfig.isEmpty()) { - Set keySet = updateConfig.keySet(); - for (String key : keySet) { - String oldValue = getProperty(key); - addProperty(key, decodeEncryptIfRequire(updateConfig.get(key)).toString()); - logger.info("Config [{}] Change, oldValue:{}, newValue:{}", key, oldValue, updateConfig.get(key)); - } - - // Update Spring Environment property source - if (applicationContext != null) { - StandardEnvironment environment = (StandardEnvironment) applicationContext.getEnvironment(); - MutablePropertySources propertySources = environment.getPropertySources(); - synchronized (propertySources) { - Properties properties = new Properties(); - if (propertySources.contains(MANAGER_PROPERTY_SOURCE)) { - Object source = propertySources.get(MANAGER_PROPERTY_SOURCE); - if (source instanceof MapPropertySource) { - Map map = ((MapPropertySource) source).getSource(); - for (Entry entry : map.entrySet()) { - properties.put(entry.getKey(), entry.getValue()); - } - } else if (source instanceof PropertiesPropertySource) { - Map src = ((PropertiesPropertySource) source).getSource(); - for (Entry entry : src.entrySet()) { - properties.put(entry.getKey(), entry.getValue().toString()); - } - } - } - for (Entry entry : updateConfig.entrySet()) { - properties.put(entry.getKey(), entry.getValue()); - } - propertySources.replace(MANAGER_PROPERTY_SOURCE, new PropertiesPropertySource(MANAGER_PROPERTY_SOURCE, properties)); - } - } - - if (configChangeHanlders == null) { - configChangeHanlders = new ArrayList<>(); - if (applicationContext != null) { - Map interfaces = applicationContext.getBeansOfType(ConfigChangeHanlder.class); - if (interfaces != null) { - configChangeHanlders.addAll(interfaces.values()); - } - } - } - - for (ConfigChangeHanlder handler : configChangeHanlders) { - try { - handler.onConfigChanged(updateConfig); - logger.info("invoke {}.onConfigChanged successed!", handler.getClass().getName()); - } catch (Exception e) { - logger.warn("invoke {}.onConfigChanged error, msg:{}", handler.getClass().getName(), e.getMessage()); - } - } - - // Refresh @Value and @ConfigurationProperties fields - logger.info("[RefreshScope] Calling refreshDynamicConfigFields()..."); - refreshDynamicConfigFields(); - logger.info("[RefreshScope] refreshDynamicConfigFields() completed"); - } - } - - private void refreshDynamicConfigFields() { - if (applicationContext != null) { - try { - logger.info("[RefreshScope] Starting dynamic config field refresh..."); - StandardEnvironment env = (StandardEnvironment) applicationContext.getEnvironment(); - // 直接创建 RefreshScopeRegistry 并注册为 BeanPostProcessor - RefreshScopeRegistry registry = new RefreshScopeRegistry(env); - - // 遍历所有已注册的 Bean,记录 @Value 和 @ConfigurationProperties 字段 - String[] beanNames = applicationContext.getBeanDefinitionNames(); - logger.info("[RefreshScope] Found {} beans in ApplicationContext", beanNames.length); - - int valueFieldCount = 0; - int configPropsCount = 0; - for (String beanName : beanNames) { - Object bean = applicationContext.getBean(beanName); - registry.postProcessAfterInitialization(bean, beanName); - } - - valueFieldCount = registry.getValueFieldCount(); - configPropsCount = registry.getConfigPropsCount(); - logger.info("[RefreshScope] Recorded {} @Value fields and {} @ConfigurationProperties beans", valueFieldCount, configPropsCount); - - // 刷新 - registry.refresh(); - logger.info("[RefreshScope] RefreshScopeRegistry.refresh() completed, refreshed {} @Value fields and {} @ConfigurationProperties beans", - registry.getValueFieldCount(), registry.getConfigPropsCount()); - } catch (Exception e) { - logger.warn("[RefreshScope] RefreshScopeRegistry.refresh() error, msg:{}", e.getMessage(), e); - } - } else { - logger.warn("[RefreshScope] ApplicationContext is null, cannot refresh dynamic config fields"); - } - } - - public boolean pingCcServer(String pingUrl, int retry) { - boolean result = false; - try { - result = httpGet(pingUrl) != null; - } catch (Exception e) { - // ignore - } - if (retry == 0) return false; - if (!result) { - try { Thread.sleep(1500); } catch (Exception e) { } - return pingCcServer(pingUrl, --retry); - } - return result; - } - - public String buildTokenParameter(String url) { - if (token == null || token.isEmpty()) { - return url; - } - return url + (url.contains("?") ? "&" : "?") + "token=" + token; - } - - public void close() { - if (configChangeListener != null) { - configChangeListener.close(); - } - } - - private Object decodeEncryptIfRequire(Object data) { - if (data == null) return ""; - if (data.toString().startsWith(CRYPT_PREFIX)) { - Validate.notBlank(secret, "config[kenaito.config.encrypt-secret] is required"); - data = data.toString().replace(CRYPT_PREFIX, ""); - String decryptString; - try { - decryptString = decryptWithAES(secret, data.toString()); - } catch (Exception e) { - if (globalSecret != null && !globalSecret.isEmpty()) { - decryptString = decryptWithAES(globalSecret, data.toString()); - } else { - throw e; - } - } - return decryptString; - } - return data; - } - - private String hideSensitive(String key, String origin) { - if (StringUtils.isAnyBlank(key, origin)) return ""; - boolean isSensitive = false; - for (String k : sensitiveKeys) { - if (key.toLowerCase().contains(k)) { - isSensitive = true; - break; - } - } - int length = origin.length(); - if (isSensitive && length > 1) return origin.substring(0, length / 2).concat("****"); - return origin; - } + private final static Logger logger = LoggerFactory.getLogger("cn.odboy"); + + private static ConfigcenterContext instance = new ConfigcenterContext(); + + private List sensitiveKeys = new ArrayList<>(Arrays.asList("password", "key", "secret", "token", "credentials")); + + public static final String MANAGER_PROPERTY_SOURCE = "kenaitoConfigPropertySource"; + + private static final String IGNORE_PLACEHOLER = "[Ignore]"; + private static final String CRYPT_PREFIX = "{Cipher}"; + + // Local property storage (replaces ResourceUtils) + private static final Map localProps = new ConcurrentHashMap<>(); + + // Spring ApplicationContext (replaces InstanceFactory) + private static ApplicationContext applicationContext; + + private Boolean remoteEnabled; + private final String nodeId; + private String[] apiBaseUrls; + private String app; + private String env; + private String version; + private boolean ignoreGlobal; + private String secret; + private String globalSecret; + private String token; + private boolean remoteFirst = false; + private boolean isSpringboot; + private int syncIntervalSeconds = 50; + private InternalConfigChangeListener configChangeListener; + private List configChangeHanlders; + private boolean processed; + private Properties remoteProperties; + + private ConfigcenterContext() { + String name; + try { + name = InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + name = "unknown"; + } + this.nodeId = name; + } + + public static ConfigcenterContext getInstance() { + return instance; + } + + public static void setApplicationContext(ApplicationContext ctx) { + applicationContext = ctx; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + // ==================== Property Utils (replaces ResourceUtils) ==================== + + public static void mergeProperties(Properties props) { + if (props != null) { + for (Entry entry : props.entrySet()) { + localProps.put(entry.getKey().toString(), entry.getValue().toString()); + } + } + } + + public static void addProperty(String key, String value) { + localProps.put(key, value); + } + + public static String getProperty(String key) { + String val = localProps.get(key); + if (val == null) val = System.getProperty(key); + if (val == null) val = System.getenv(key); + return val; + } + + public static String getProperty(String key, String defaultValue) { + String val = getProperty(key); + return val != null && !val.isEmpty() ? val : defaultValue; + } + + public static boolean getBoolean(String key, boolean defaultValue) { + String val = getProperty(key); + if (val == null || val.isEmpty()) return defaultValue; + return Boolean.parseBoolean(val); + } + + public static int getInt(String key, int defaultValue) { + String val = getProperty(key); + if (val == null || val.isEmpty()) return defaultValue; + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static String getAnyProperty(String... keys) { + for (String key : keys) { + String val = getProperty(key); + if (val != null && !val.isEmpty()) return val; + } + return null; + } + + // ==================== HTTP Utils (replaces mendmix HttpUtils) ==================== + + public static String httpGet(String url) throws Exception { + return HttpClientUtils.get(url); + } + + public static String httpPostJson(String url, String json) throws Exception { + return HttpClientUtils.postJson(url, json); + } + + // ==================== Core Methods ==================== + + public boolean isProcessed() { + return processed; + } + + public synchronized void init(Properties properties, boolean isSpringboot) { + if (processed || !isRemoteEnabled()) return; + if (properties != null) mergeProperties(properties); + + System.setProperty("client.nodeId", nodeId); + System.setProperty("springboot", String.valueOf(isSpringboot)); + this.isSpringboot = isSpringboot; + + String defaultAppName = getProperty("spring.application.name"); + app = getProperty("kenaito.config.appName", defaultAppName); + if (remoteEnabled == null) remoteEnabled = getBoolean("kenaito.config.enabled", true); + + if (!isRemoteEnabled()) return; + + env = getProperty("kenaito.config.profile", "dev"); + Validate.notBlank(env, "[kenaito.config.profile] is required"); + + setApiBaseUrl(getAnyProperty("kenaito.config.baseUrl", "kenaito.config.baseUrl")); + + version = getProperty("kenaito.config.version", "latest"); + ignoreGlobal = getBoolean("kenaito.config.global-ignore", false); + syncIntervalSeconds = getInt("kenaito.config.sync-interval-seconds", 60); + token = getProperty("kenaito.config.token"); + + logger.info("\n=====Configcenter config=====\nappName:{}\nenv:{}\nversion:{}\nremoteEnabled:{}\napiBaseUrls:{}\n=====Configcenter config=====", app, env, version, isRemoteEnabled(), JSON.toJSONString(apiBaseUrls)); + } + + public boolean isRemoteEnabled() { + return remoteEnabled == null || remoteEnabled; + } + + public void setRemoteEnabled(boolean remoteEnabled) { + this.remoteEnabled = remoteEnabled; + } + + public String[] getApiBaseUrls() { + return apiBaseUrls; + } + + public void setApiBaseUrl(String apiBaseUrl) { + Validate.notBlank(apiBaseUrl, "[kenaito.config.baseUrl] is required"); + String[] urls = apiBaseUrl.split(",|;"); + this.apiBaseUrls = new String[urls.length]; + for (int i = 0; i < urls.length; i++) { + if (urls[i].endsWith("/")) { + this.apiBaseUrls[i] = urls[i].substring(0, urls[i].length() - 1); + } else { + this.apiBaseUrls[i] = urls[i]; + } + } + } + + public String getApp() { + return app; + } + + public String getEnv() { + return env; + } + + public String getVersion() { + return version; + } + + public boolean isIgnoreGlobal() { + return ignoreGlobal; + } + + public boolean isRemoteFirst() { + return remoteFirst; + } + + public int getSyncIntervalSeconds() { + return syncIntervalSeconds; + } + + public void mergeRemoteProperties(Properties properties) { + if (!remoteEnabled) return; + remoteProperties = getAllRemoteProperties(); + if (remoteProperties != null) { + Set> entrySet = remoteProperties.entrySet(); + for (Entry entry : entrySet) { + if (!isRemoteFirst() && properties.containsKey(entry.getKey())) { + continue; + } + String value = entry.getValue().toString(); + if (IGNORE_PLACEHOLER.equals(value)) continue; + properties.setProperty(entry.getKey().toString(), value); + } + } + + // 替换本地变量占位符 + Set> entrySet = properties.entrySet(); + for (Entry entry : entrySet) { + String key = entry.getKey().toString(); + String value = entry.getValue().toString(); + addProperty(key, value); + } + + // 加载本地测试配置 + String path = System.getProperty("localtest.config.location"); + if (StringUtils.isNotBlank(path)) { + try (FileReader fileReader = new FileReader(path)) { + Properties localProperties = new Properties(); + localProperties.load(fileReader); + if (!localProperties.isEmpty()) { + properties.putAll(localProperties); + } + } catch (Exception e) { + logger.warn("Failed to load local test config from {}: {}", path, e.getMessage()); + } + } + + // Close existing listener before creating new one + if (configChangeListener != null) { + configChangeListener.close(); + } + // register listener + configChangeListener = new InternalConfigChangeListener(); + printConfigs(properties); + } + + private Properties getAllRemoteProperties() { + if (remoteProperties != null) return remoteProperties; + Properties properties = new Properties(); + + Map map = fetchConfigFromServer(); + if (map == null) { + throw new RuntimeException("fetch remote config error!"); + } + + // 解密密匙 + secret = Objects.toString(map.remove("kenaito.config.encrypt-secret"), null); + globalSecret = Objects.toString(map.remove("kenaito.config.global-encrypt-secret"), null); + remoteFirst = Boolean.parseBoolean(Objects.toString(map.remove("kenaito.config.remote-config-first"), "false")); + + Set keys = map.keySet(); + for (String key : keys) { + Object value = map.get(key); + properties.put(key, value); + } + + return properties; + } + + @SuppressWarnings("unchecked") + private Map fetchConfigFromServer() { + Map result = null; + String errorMsg = null; + for (String apiBaseUrl : apiBaseUrls) { + String url = apiBaseUrl + "/api/fetchAllConfigs"; + Map params = new HashMap<>(); + params.put("appName", app); + params.put("env", env); + params.put("version", version); + params.put("ignoreGlobal", ignoreGlobal); + if (token != null && !token.isEmpty()) { + params.put("token", token); + } + logger.info("fetch configs url:{}", url); + try { + String jsonString = httpPostJson(url, JSON.toJSONString(params)); + if (jsonString != null) { + result = JSON.parseObject(jsonString, Map.class); + if (result.containsKey("code")) { + errorMsg = result.get("msg").toString(); + logger.error("fetch error:{}", errorMsg); + result = null; + } else { + break; + } + } + } catch (Exception e) { + logger.error("fetch config error from {}: {}", apiBaseUrl, e.getMessage()); + } + } + + if (result == null) { + logger.warn(">>>>>remote config fetch error, load from local Cache"); + result = LocalCacheUtils.read(); + } else { + LocalCacheUtils.write(result); + } + return result; + } + + private void printConfigs(Properties properties) { + List sortKeys = new ArrayList<>(); + Set> entrySet = properties.entrySet(); + for (Entry entry : entrySet) { + sortKeys.add(entry.getKey().toString()); + } + Collections.sort(sortKeys); + System.out.println("==================final config list start=================="); + for (String key : sortKeys) { + String value = hideSensitive(key, properties.getProperty(key)); + System.out.printf("%s = %s%n", key, value); + } + System.out.println("==================final config list end===================="); + } + + @SuppressWarnings("unchecked") + public synchronized void updateConfig(Map updateConfig) { + if (!updateConfig.isEmpty()) { + Set keySet = updateConfig.keySet(); + for (String key : keySet) { + String oldValue = getProperty(key); + addProperty(key, updateConfig.get(key).toString()); + logger.info("Config [{}] Change, oldValue:{}, newValue:{}", key, oldValue, updateConfig.get(key)); + } + + // Update Spring Environment property source + if (applicationContext != null) { + StandardEnvironment environment = (StandardEnvironment) applicationContext.getEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + synchronized (propertySources) { + Properties properties = new Properties(); + if (propertySources.contains(MANAGER_PROPERTY_SOURCE)) { + Object source = propertySources.get(MANAGER_PROPERTY_SOURCE); + if (source instanceof MapPropertySource) { + Map map = ((MapPropertySource) source).getSource(); + for (Entry entry : map.entrySet()) { + properties.put(entry.getKey(), entry.getValue()); + } + } else if (source instanceof PropertiesPropertySource) { + Map src = ((PropertiesPropertySource) source).getSource(); + for (Entry entry : src.entrySet()) { + properties.put(entry.getKey(), entry.getValue().toString()); + } + } + } + for (Entry entry : updateConfig.entrySet()) { + properties.put(entry.getKey(), entry.getValue()); + } + propertySources.replace(MANAGER_PROPERTY_SOURCE, new PropertiesPropertySource(MANAGER_PROPERTY_SOURCE, properties)); + } + } + + if (configChangeHanlders == null) { + configChangeHanlders = new ArrayList<>(); + if (applicationContext != null) { + Map interfaces = applicationContext.getBeansOfType(ConfigChangeHanlder.class); + if (interfaces != null) { + configChangeHanlders.addAll(interfaces.values()); + } + } + } + + for (ConfigChangeHanlder handler : configChangeHanlders) { + try { + handler.onConfigChanged(updateConfig); + logger.info("invoke {}.onConfigChanged successed!", handler.getClass().getName()); + } catch (Exception e) { + logger.warn("invoke {}.onConfigChanged error, msg:{}", handler.getClass().getName(), e.getMessage()); + } + } + + // Refresh @Value and @ConfigurationProperties fields + logger.info("[RefreshScope] Calling refreshDynamicConfigFields()..."); + refreshDynamicConfigFields(); + logger.info("[RefreshScope] refreshDynamicConfigFields() completed"); + } + } + + private void refreshDynamicConfigFields() { + if (applicationContext != null) { + try { + logger.info("[RefreshScope] Starting dynamic config field refresh..."); + StandardEnvironment env = (StandardEnvironment) applicationContext.getEnvironment(); + // 直接创建 RefreshScopeRegistry 并注册为 BeanPostProcessor + RefreshScopeRegistry registry = new RefreshScopeRegistry(env); + + // 遍历所有已注册的 Bean,记录 @Value 和 @ConfigurationProperties 字段 + String[] beanNames = applicationContext.getBeanDefinitionNames(); + logger.info("[RefreshScope] Found {} beans in ApplicationContext", beanNames.length); + + int valueFieldCount = 0; + int configPropsCount = 0; + for (String beanName : beanNames) { + Object bean = applicationContext.getBean(beanName); + registry.postProcessAfterInitialization(bean, beanName); + } + + valueFieldCount = registry.getValueFieldCount(); + configPropsCount = registry.getConfigPropsCount(); + logger.info("[RefreshScope] Recorded {} @Value fields and {} @ConfigurationProperties beans", valueFieldCount, configPropsCount); + + // 刷新 + registry.refresh(); + logger.info("[RefreshScope] RefreshScopeRegistry.refresh() completed, refreshed {} @Value fields and {} @ConfigurationProperties beans", registry.getValueFieldCount(), registry.getConfigPropsCount()); + } catch (Exception e) { + logger.warn("[RefreshScope] RefreshScopeRegistry.refresh() error, msg:{}", e.getMessage(), e); + } + } else { + logger.warn("[RefreshScope] ApplicationContext is null, cannot refresh dynamic config fields"); + } + } + + public String buildTokenParameter(String url) { + if (token == null || token.isEmpty()) { + return url; + } + return url + (url.contains("?") ? "&" : "?") + "token=" + token; + } + + public void close() { + if (configChangeListener != null) { + configChangeListener.close(); + } + } + + private String hideSensitive(String key, String origin) { + if (StringUtils.isAnyBlank(key, origin)) return ""; + boolean isSensitive = false; + for (String k : sensitiveKeys) { + if (key.toLowerCase().contains(k)) { + isSensitive = true; + break; + } + } + int length = origin.length(); + if (isSensitive && length > 1) return origin.substring(0, length / 2).concat("****"); + return origin; + } } diff --git a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/EnvironmentChangeEvent.java b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/EnvironmentChangeEvent.java deleted file mode 100644 index 0d9a90f..0000000 --- a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/EnvironmentChangeEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.odboy.confcenter; - -import java.util.Set; - -import org.springframework.context.ApplicationEvent; - -public class EnvironmentChangeEvent extends ApplicationEvent { - - private static final long serialVersionUID = 1L; - - private Set keys; - - public EnvironmentChangeEvent(Set keys) { - super(keys); - this.keys = keys; - } - - /** - * @return the keys - */ - public Set getKeys() { - return keys; - } -} diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 6eee5ab..03d37c0 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -14,7 +14,6 @@ type Config struct { Server ServerConfig Database DatabaseConfig JWT JWTConfig - Crypto CryptoConfig } type ServerConfig struct { @@ -35,10 +34,6 @@ type JWTConfig struct { ExpireHours int } -type CryptoConfig struct { - SecretKey string -} - var Cfg *Config func Load(path string) error { @@ -77,8 +72,6 @@ func Load(path string) error { Cfg.JWT.Secret = baseProps["jwt.secret"] Cfg.JWT.ExpireHours = intProp(baseProps, "jwt.expireHours", 24) - Cfg.Crypto.SecretKey = baseProps["crypto.secretKey"] - if Cfg.Database.Host == "" { return fmt.Errorf("missing required config: database.host") }