feat(config): 增强配置历史管理并统一数据库时间

- 新增 dao.Now() 函数获取数据库毫秒精度时间,避免多实例时钟偏差
- 增量同步查询增加上界(updated_at <= upperBound)防止竞态条件
- 配置创建、更新、删除、回滚统一使用数据库时间替代 time.Now()
- 配置历史新增 operate 字段记录操作类型(update/delete/rollback)
- 删除配置和回滚操作前增加历史快照记录
- 新增 DeleteHistory 接口支持手动删除单条历史记录
- 移除 GetHistory 的 Limit(5) 限制,返回完整历史列表
- 删除应用时同步更新关联配置的 updated_at 以便增量同步识别
- version 字段改为必填,前后端同步增加校验规则
- 前端历史列表增加操作类型列、删除按钮及表格最大高度限制
- 数据库表结构 datetime 升级为 datetime(3) 支持毫秒精度
This commit is contained in:
2026-09-06 21:04:25 +08:00
parent c0a7631d5c
commit 92c09df103
12 changed files with 224 additions and 63 deletions
+27 -10
View File
@@ -91,6 +91,14 @@ func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
return
}
// 游标上界取查询前的数据库时间,保证查询期间发生的更新会在下次增量中返回
upperBound, err := dao.Now()
if err != nil {
logger.SugaredLogger.Errorf("[FetchAllConfigs] get database time error: %v", err)
c.JSON(http.StatusInternalServerError, model.WrapperResponse{Code: 500, Msg: "服务暂时不可用"})
return
}
result := make(map[string]interface{})
var configIds []int
@@ -125,7 +133,7 @@ func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
return
}
result["latest.fetch.timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
result["latest.fetch.timestamp"] = strconv.FormatInt(upperBound.UnixMilli(), 10)
// record client instances asynchronously
go h.recordClientInstances(getClientIP(c), configIds, req.AppName, req.Env, "fetchAll")
@@ -170,6 +178,15 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
lastUpdateTime := time.UnixMilli(req.LastTime)
// 以数据库时间为本次查询的固定上界,避免查询与游标生成之间的竞态,
// 同时避免多实例应用服务器的时钟偏差。
upperBound, err := dao.Now()
if err != nil {
logger.SugaredLogger.Errorf("[FetchChangedConfigs] get database time error: %v", err)
c.JSON(http.StatusInternalServerError, model.WrapperResponse{Code: 500, Msg: "服务暂时不可用"})
return
}
result := make(map[string]string)
var configIds []int
@@ -177,8 +194,8 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
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)
dao.Engine.Where("env = ? AND version = ? AND enabled = 0 AND group_id = ? AND (app_id is null OR app_id = 0) AND updated_at > ? AND updated_at <= ?",
req.Env, version, *app.GroupId, lastUpdateTime, upperBound).Find(&deletedGlobalConfigs)
for _, cfg := range deletedGlobalConfigs {
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k := range kvMap {
@@ -187,8 +204,8 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
}
}
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)
dao.Engine.Where("env = ? AND version = ? AND enabled = 0 AND app_id = ? AND updated_at > ? AND updated_at <= ?",
req.Env, version, app.Id, lastUpdateTime, upperBound).Find(&deletedAppConfigs)
for _, cfg := range deletedAppConfigs {
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k := range kvMap {
@@ -202,8 +219,8 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
// load global changed configs
if !req.IgnoreGlobal && app.GroupId != nil {
var globalConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0) AND updated_at > ?",
req.Env, version, *app.GroupId, lastUpdateTime).Find(&globalConfigs)
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0) AND updated_at > ? AND updated_at <= ?",
req.Env, version, *app.GroupId, lastUpdateTime, upperBound).Find(&globalConfigs)
for _, cfg := range globalConfigs {
configIds = append(configIds, cfg.Id)
h.addChangedConfigs(result, &cfg, req.FetchAll)
@@ -212,14 +229,14 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
// load app changed configs
var appConfigs1 []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ?",
req.Env, version, app.Id, lastUpdateTime).Find(&appConfigs1)
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ? AND updated_at <= ?",
req.Env, version, app.Id, lastUpdateTime, upperBound).Find(&appConfigs1)
for _, cfg := range appConfigs1 {
configIds = append(configIds, cfg.Id)
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
result["latest.fetch.timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
result["latest.fetch.timestamp"] = strconv.FormatInt(upperBound.UnixMilli(), 10)
// query all config ids for this app to record client instances
var allConfigIds []int