From 639475b730a170b194e76ff441e181f3c6c87824 Mon Sep 17 00:00:00 2001 From: Odboy Date: Wed, 17 Jun 2026 21:07:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=80=9A=E7=9F=A5=E6=9C=BA=E5=88=B6=EF=BC=88?= =?UTF-8?q?deletedKeys=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 服务端 fetchChangedConfigs 接口新增 deletedKeys 字段,返回已删除配置的 key 列表 - Go/Node.js/Python/Spring Boot 客户端支持解析 deletedKeys 并删除本地配置 - Spring Boot 客户端新增 removeConfigs 方法,同步更新 Spring Environment --- kenaito-conf-go-client/client.go | 12 +++++++ kenaito-conf-nodejs-client/lib/client.js | 11 ++++++ .../kenaito_conf/client.py | 9 +++++ .../odboy/confcenter/ConfigcenterContext.java | 36 +++++++++++++++++++ .../InternalConfigChangeListener.java | 9 +++++ server/internal/handler/config_api.go | 27 ++++++++++++++ 6 files changed, 104 insertions(+) diff --git a/kenaito-conf-go-client/client.go b/kenaito-conf-go-client/client.go index 1067a4c..b25aabc 100644 --- a/kenaito-conf-go-client/client.go +++ b/kenaito-conf-go-client/client.go @@ -223,6 +223,18 @@ func (c *Client) fetchChanged() error { delete(wrapper.Data, "latest.fetch.timestamp") } + // 处理删除的配置 + if deletedKeysStr, ok := wrapper.Data["deletedKeys"]; ok && deletedKeysStr != "" { + deletedKeys := strings.Split(deletedKeysStr, ",") + c.mu.Lock() + for _, key := range deletedKeys { + delete(c.configs, key) + } + c.mu.Unlock() + log.Printf("[ConfigClient] %d configs deleted", len(deletedKeys)) + delete(wrapper.Data, "deletedKeys") + } + if len(wrapper.Data) > 0 { changed := make(map[string]string) for k, v := range wrapper.Data { diff --git a/kenaito-conf-nodejs-client/lib/client.js b/kenaito-conf-nodejs-client/lib/client.js index 257f2f4..0b5849c 100644 --- a/kenaito-conf-nodejs-client/lib/client.js +++ b/kenaito-conf-nodejs-client/lib/client.js @@ -127,6 +127,17 @@ export class ConfigClient { } delete changedData['latest.fetch.timestamp']; + // 处理删除的配置 + const deletedKeysStr = changedData['deletedKeys']; + if (deletedKeysStr) { + const deletedKeys = deletedKeysStr.split(','); + for (const key of deletedKeys) { + delete this.configs[key]; + } + console.info('[ConfigClient]', deletedKeys.length, 'configs deleted'); + delete changedData['deletedKeys']; + } + const changed = {}; for (const [k, v] of Object.entries(changedData)) { changed[k] = v; diff --git a/kenaito-conf-python-client/kenaito_conf/client.py b/kenaito-conf-python-client/kenaito_conf/client.py index 11920c3..ca4ed60 100644 --- a/kenaito-conf-python-client/kenaito_conf/client.py +++ b/kenaito-conf-python-client/kenaito_conf/client.py @@ -103,6 +103,15 @@ class ConfigClient: if ts: self._last_time = int(ts) + # 处理删除的配置 + deleted_keys_str = changed_data.pop("deletedKeys", None) + if deleted_keys_str: + deleted_keys = deleted_keys_str.split(",") + with self._lock: + for key in deleted_keys: + self._configs.pop(key, None) + logger.info("[ConfigClient] %d configs deleted", len(deleted_keys)) + if changed_data: changed = {} for k, v in changed_data.items(): 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 03821e9..6b395c5 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 @@ -415,6 +415,42 @@ public class ConfigcenterContext { } } + public synchronized void removeConfigs(List keys) { + for (String key : keys) { + localProps.remove(key); + logger.info("Config [{}] removed", key); + } + // Update Spring Environment property source + if (applicationContext != null) { + StandardEnvironment environment = (StandardEnvironment) applicationContext.getEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + synchronized (propertySources) { + if (propertySources.contains(MANAGER_PROPERTY_SOURCE)) { + Properties properties = new Properties(); + Object source = propertySources.get(MANAGER_PROPERTY_SOURCE); + if (source instanceof MapPropertySource) { + Map map = ((MapPropertySource) source).getSource(); + for (Entry entry : map.entrySet()) { + if (!keys.contains(entry.getKey())) { + properties.put(entry.getKey(), entry.getValue()); + } + } + } else if (source instanceof PropertiesPropertySource) { + Map src = ((PropertiesPropertySource) source).getSource(); + for (Entry entry : src.entrySet()) { + if (!keys.contains(entry.getKey())) { + properties.put(entry.getKey(), entry.getValue().toString()); + } + } + } + propertySources.replace(MANAGER_PROPERTY_SOURCE, new PropertiesPropertySource(MANAGER_PROPERTY_SOURCE, properties)); + } + } + } + // Refresh @Value and @ConfigurationProperties fields + refreshDynamicConfigFields(); + } + private void refreshDynamicConfigFields() { if (applicationContext != null) { try { diff --git a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/InternalConfigChangeListener.java b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/InternalConfigChangeListener.java index e6c8a46..590fcec 100644 --- a/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/InternalConfigChangeListener.java +++ b/kenaito-conf-springboot2-client/src/main/java/cn/odboy/confcenter/InternalConfigChangeListener.java @@ -63,6 +63,15 @@ public class InternalConfigChangeListener { if (data.containsKey(LATEST_FETCH_TIMESTAMP)) { params.put("lastTime", Long.parseLong(data.remove(LATEST_FETCH_TIMESTAMP).toString())); } + // 处理删除的配置 + if (data.containsKey("deletedKeys")) { + String deletedKeysStr = (String) data.remove("deletedKeys"); + if (deletedKeysStr != null && !deletedKeysStr.isEmpty()) { + String[] deletedKeys = deletedKeysStr.split(","); + context.removeConfigs(java.util.Arrays.asList(deletedKeys)); + logger.info("[ConfigClient] {} configs deleted", deletedKeys.length); + } + } if (!data.isEmpty()) context.updateConfig(data); } } diff --git a/server/internal/handler/config_api.go b/server/internal/handler/config_api.go index 07c9642..0bf7849 100644 --- a/server/internal/handler/config_api.go +++ b/server/internal/handler/config_api.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "strconv" + "strings" "time" "github.com/gin-gonic/gin" @@ -172,6 +173,32 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) { result := make(map[string]string) var configIds []int + // 查询已删除的配置(enabled=0 且 updated_at > lastTime),返回 deletedKeys + var deletedKeys []string + if !req.IgnoreGlobal && app.GroupId != nil { + var deletedGlobalConfigs []model.AppConfig + dao.Engine.Where("env = ? AND version = ? AND enabled = 0 AND group_id = ? AND (app_id is null OR app_id = 0) AND updated_at > ?", + req.Env, version, *app.GroupId, lastUpdateTime).Find(&deletedGlobalConfigs) + for _, cfg := range deletedGlobalConfigs { + kvMap := h.parseService.ParseConfigToKVMap(&cfg) + for k := range kvMap { + deletedKeys = append(deletedKeys, k) + } + } + } + var deletedAppConfigs []model.AppConfig + dao.Engine.Where("env = ? AND version = ? AND enabled = 0 AND app_id = ? AND updated_at > ?", + req.Env, version, app.Id, lastUpdateTime).Find(&deletedAppConfigs) + for _, cfg := range deletedAppConfigs { + kvMap := h.parseService.ParseConfigToKVMap(&cfg) + for k := range kvMap { + deletedKeys = append(deletedKeys, k) + } + } + if len(deletedKeys) > 0 { + result["deletedKeys"] = strings.Join(deletedKeys, ",") + } + // load global changed configs if !req.IgnoreGlobal && app.GroupId != nil { var globalConfigs []model.AppConfig