feat(config): 增强配置解析的语法校验与错误处理

- 重构 ParseConfigToKVMap 使其在解析失败时返回 error 而非静默忽略
- 新增 ValidateConfig 方法,在保存和更新配置前校验语法正确性
- 将 XML 解析器从简单字符串匹配改为标准库 xml.NewDecoder,支持嵌套标签和实体转义
- 在配置保存和更新接口中集成语法校验,校验失败返回 400 错误
- API 拉取配置时解析失败的条目记录日志并跳过,避免整体请求中断
- 新增 config_parse_test.go 覆盖各配置类型的正常与异常场景
This commit is contained in:
2026-09-08 21:19:55 +08:00
parent 92c09df103
commit a2c26cea98
4 changed files with 219 additions and 54 deletions
+14
View File
@@ -231,6 +231,11 @@ func (h *ConfigHandler) Add(c *gin.Context) {
}
}
if err := h.parseService.ValidateConfig(&config); err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: err.Error()})
return
}
if _, err := dao.Engine.Insert(&config); err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 500, Msg: "保存失败"})
return
@@ -264,6 +269,15 @@ func (h *ConfigHandler) Update(c *gin.Context) {
return
}
// 校验新内容语法,避免保存后客户端解析失败
newConfig := config
newConfig.Contents = req.Contents
newConfig.Version = req.Version
if err := h.parseService.ValidateConfig(&newConfig); err != nil {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: err.Error()})
return
}
// save history
h.saveHistory(&config, userInfo.Name, "update")
+33 -10
View File
@@ -108,8 +108,12 @@ func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0)",
req.Env, version, *app.GroupId).Find(&globalConfigs)
for _, cfg := range globalConfigs {
kvMap, err := h.parseService.ParseConfigToKVMap(&cfg)
if err != nil {
logger.SugaredLogger.Errorf("[FetchAllConfigs] parse config %d (%s) error: %v", cfg.Id, cfg.Name, err)
continue
}
configIds = append(configIds, cfg.Id)
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
@@ -121,8 +125,12 @@ func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
req.Env, version, app.Id).Find(&appConfigs)
for _, cfg := range appConfigs {
kvMap, err := h.parseService.ParseConfigToKVMap(&cfg)
if err != nil {
logger.SugaredLogger.Errorf("[FetchAllConfigs] parse config %d (%s) error: %v", cfg.Id, cfg.Name, err)
continue
}
configIds = append(configIds, cfg.Id)
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
@@ -197,7 +205,11 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
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)
kvMap, err := h.parseService.ParseConfigToKVMap(&cfg)
if err != nil {
logger.SugaredLogger.Errorf("[FetchChangedConfigs] parse deleted config %d (%s) error: %v", cfg.Id, cfg.Name, err)
continue
}
for k := range kvMap {
deletedKeys = append(deletedKeys, k)
}
@@ -207,7 +219,11 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
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)
kvMap, err := h.parseService.ParseConfigToKVMap(&cfg)
if err != nil {
logger.SugaredLogger.Errorf("[FetchChangedConfigs] parse deleted config %d (%s) error: %v", cfg.Id, cfg.Name, err)
continue
}
for k := range kvMap {
deletedKeys = append(deletedKeys, k)
}
@@ -222,8 +238,9 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
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)
if h.addChangedConfigs(result, &cfg, req.FetchAll) {
configIds = append(configIds, cfg.Id)
}
}
}
@@ -232,8 +249,9 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
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)
if h.addChangedConfigs(result, &cfg, req.FetchAll) {
configIds = append(configIds, cfg.Id)
}
}
result["latest.fetch.timestamp"] = strconv.FormatInt(upperBound.UnixMilli(), 10)
@@ -266,11 +284,16 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: result})
}
func (h *ConfigApiHandler) addChangedConfigs(result map[string]string, config *model.AppConfig, fetchAll bool) {
kvMap := h.parseService.ParseConfigToKVMap(config)
func (h *ConfigApiHandler) addChangedConfigs(result map[string]string, config *model.AppConfig, fetchAll bool) bool {
kvMap, err := h.parseService.ParseConfigToKVMap(config)
if err != nil {
logger.SugaredLogger.Errorf("[FetchChangedConfigs] parse config %d (%s) error: %v", config.Id, config.Name, err)
return false
}
for k, v := range kvMap {
result[k] = fmt.Sprintf("%v", v)
}
return true
}
func (h *ConfigApiHandler) recordClientInstances(ip string, configIds []int, appName, env, fetchType string) {