feat: 实现配置删除通知机制(deletedKeys)
- 服务端 fetchChangedConfigs 接口新增 deletedKeys 字段,返回已删除配置的 key 列表 - Go/Node.js/Python/Spring Boot 客户端支持解析 deletedKeys 并删除本地配置 - Spring Boot 客户端新增 removeConfigs 方法,同步更新 Spring Environment
This commit is contained in:
@@ -223,6 +223,18 @@ func (c *Client) fetchChanged() error {
|
|||||||
delete(wrapper.Data, "latest.fetch.timestamp")
|
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 {
|
if len(wrapper.Data) > 0 {
|
||||||
changed := make(map[string]string)
|
changed := make(map[string]string)
|
||||||
for k, v := range wrapper.Data {
|
for k, v := range wrapper.Data {
|
||||||
|
|||||||
@@ -127,6 +127,17 @@ export class ConfigClient {
|
|||||||
}
|
}
|
||||||
delete changedData['latest.fetch.timestamp'];
|
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 = {};
|
const changed = {};
|
||||||
for (const [k, v] of Object.entries(changedData)) {
|
for (const [k, v] of Object.entries(changedData)) {
|
||||||
changed[k] = v;
|
changed[k] = v;
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ class ConfigClient:
|
|||||||
if ts:
|
if ts:
|
||||||
self._last_time = int(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:
|
if changed_data:
|
||||||
changed = {}
|
changed = {}
|
||||||
for k, v in changed_data.items():
|
for k, v in changed_data.items():
|
||||||
|
|||||||
+36
@@ -415,6 +415,42 @@ public class ConfigcenterContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void removeConfigs(List<String> 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<String, Object> map = ((MapPropertySource) source).getSource();
|
||||||
|
for (Entry<String, Object> entry : map.entrySet()) {
|
||||||
|
if (!keys.contains(entry.getKey())) {
|
||||||
|
properties.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (source instanceof PropertiesPropertySource) {
|
||||||
|
Map<String, Object> src = ((PropertiesPropertySource) source).getSource();
|
||||||
|
for (Entry<String, Object> 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() {
|
private void refreshDynamicConfigFields() {
|
||||||
if (applicationContext != null) {
|
if (applicationContext != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+9
@@ -63,6 +63,15 @@ public class InternalConfigChangeListener {
|
|||||||
if (data.containsKey(LATEST_FETCH_TIMESTAMP)) {
|
if (data.containsKey(LATEST_FETCH_TIMESTAMP)) {
|
||||||
params.put("lastTime", Long.parseLong(data.remove(LATEST_FETCH_TIMESTAMP).toString()));
|
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);
|
if (!data.isEmpty()) context.updateConfig(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -172,6 +173,32 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
|
|||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
var configIds []int
|
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
|
// load global changed configs
|
||||||
if !req.IgnoreGlobal && app.GroupId != nil {
|
if !req.IgnoreGlobal && app.GroupId != nil {
|
||||||
var globalConfigs []model.AppConfig
|
var globalConfigs []model.AppConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user