diff --git a/internal/logic/controllers/crud.go b/internal/logic/controllers/crud.go index a819d5b..41b4ec8 100644 --- a/internal/logic/controllers/crud.go +++ b/internal/logic/controllers/crud.go @@ -35,6 +35,10 @@ func CreateSyslogRule(ctx *gin.Context) { infra.Response.Error(ctx, err) return } + if err := validateSyslogRule(&row); err != nil { + infra.Response.Error(ctx, err) + return + } row.ID = 0 if err := impl.DBService.Create(&row).Error; err != nil { infra.Response.Error(ctx, err) @@ -60,6 +64,10 @@ func UpdateSyslogRule(ctx *gin.Context) { return } row.ID = id + if err := validateSyslogRule(&row); err != nil { + infra.Response.Error(ctx, err) + return + } if err := impl.DBService.Save(&row).Error; err != nil { infra.Response.Error(ctx, err) return @@ -98,6 +106,10 @@ func CreateTrapRule(ctx *gin.Context) { infra.Response.Error(ctx, err) return } + if err := validateTrapRule(&row); err != nil { + infra.Response.Error(ctx, err) + return + } row.ID = 0 if err := impl.DBService.Create(&row).Error; err != nil { infra.Response.Error(ctx, err) @@ -123,6 +135,10 @@ func UpdateTrapRule(ctx *gin.Context) { return } row.ID = id + if err := validateTrapRule(&row); err != nil { + infra.Response.Error(ctx, err) + return + } if err := impl.DBService.Save(&row).Error; err != nil { infra.Response.Error(ctx, err) return diff --git a/internal/logic/controllers/rule_validation.go b/internal/logic/controllers/rule_validation.go new file mode 100644 index 0000000..118e480 --- /dev/null +++ b/internal/logic/controllers/rule_validation.go @@ -0,0 +1,70 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "git.apinb.com/ops/logs/internal/models" +) + +func validateSyslogRule(rule *models.SyslogRule) error { + regexFields := []struct { + name string + pattern string + }{ + {name: "keyword_regex", pattern: rule.KeywordRegex}, + {name: "message_regex", pattern: rule.MessageRegex}, + {name: "resource_uid_extract_regex", pattern: rule.ResourceUIDExtractRegex}, + } + for _, field := range regexFields { + if err := validateOptionalRegex(field.name, field.pattern); err != nil { + return err + } + } + + if raw := strings.TrimSpace(rule.SeverityMappingJSON); raw != "" { + var mapping map[string]string + if err := json.Unmarshal([]byte(raw), &mapping); err != nil { + return fmt.Errorf("severity_mapping_json 必须是字符串到字符串的 JSON 对象:%v;请修正 severity_mapping_json 后重试", err) + } + if mapping == nil { + return fmt.Errorf("severity_mapping_json 必须是字符串到字符串的 JSON 对象,不能是 null;请改为 JSON 对象后重试") + } + for pattern := range mapping { + if _, err := regexp.Compile(pattern); err != nil { + return fmt.Errorf("severity_mapping_json 的映射键 %q 不是有效正则表达式:%v;请修正该映射键后重试", pattern, err) + } + } + } + + if strings.TrimSpace(rule.DeviceNameContains) == "" && + strings.TrimSpace(rule.SourceMatch) == "" && + strings.TrimSpace(rule.KeywordRegex) == "" && + strings.TrimSpace(rule.MessageRegex) == "" { + return fmt.Errorf("Syslog 规则的匹配条件全部为空,运行时永远不会命中;请至少填写 device_name_contains、source_match、keyword_regex、message_regex 中的一项") + } + return nil +} + +func validateTrapRule(rule *models.TrapRule) error { + if err := validateOptionalRegex("varbind_match_regex", rule.VarbindMatchRegex); err != nil { + return err + } + if strings.TrimSpace(rule.OIDPrefix) == "" && strings.TrimSpace(rule.VarbindMatchRegex) == "" { + return fmt.Errorf("Trap 规则的匹配条件全部为空,运行时永远不会命中;请至少填写 oid_prefix、varbind_match_regex 中的一项") + } + return nil +} + +func validateOptionalRegex(field, pattern string) error { + pattern = strings.TrimSpace(pattern) + if pattern == "" { + return nil + } + if _, err := regexp.Compile(pattern); err != nil { + return fmt.Errorf("%s 不是有效正则表达式:%v;请修正 %s 后重试", field, err, field) + } + return nil +}