package ruleio import ( "encoding/json" "fmt" "strings" "github.com/prism/proxy/internal/store" ) const ExportVersion = 1 type ExportDoc struct { Version int `json:"version"` Rules []ExportRule `json:"rules"` } type ExportRule struct { Priority int `json:"priority"` RuleType string `json:"rule_type"` Payload string `json:"payload"` Target string `json:"target"` Enabled *bool `json:"enabled,omitempty"` } type ImportResult struct { Imported int `json:"imported"` Skipped int `json:"skipped"` Errors []string `json:"errors,omitempty"` } func ruleEnabled(r ExportRule) bool { if r.Enabled == nil { return true } return *r.Enabled } func ToExportRules(rules []store.Rule) []ExportRule { out := make([]ExportRule, 0, len(rules)) for _, r := range rules { enabled := r.Enabled out = append(out, ExportRule{ Priority: r.Priority, RuleType: r.RuleType, Payload: r.Payload, Target: r.Target, Enabled: &enabled, }) } return out } func ExportJSON(rules []store.Rule) ([]byte, error) { doc := ExportDoc{Version: ExportVersion, Rules: ToExportRules(rules)} return json.MarshalIndent(doc, "", " ") } func ExportText(rules []store.Rule) string { var b strings.Builder for _, r := range rules { if !r.Enabled { continue } b.WriteString(formatRuleLine(r)) b.WriteByte('\n') } return b.String() } func formatRuleLine(r store.Rule) string { if r.Payload == "" { return fmt.Sprintf("%s,%s", r.RuleType, r.Target) } return fmt.Sprintf("%s,%s,%s", r.RuleType, r.Payload, r.Target) } func ParseJSON(data []byte) ([]ExportRule, error) { var doc ExportDoc if err := json.Unmarshal(data, &doc); err == nil && (len(doc.Rules) > 0 || doc.Version > 0) { return doc.Rules, nil } var rules []ExportRule if err := json.Unmarshal(data, &rules); err != nil { return nil, fmt.Errorf("invalid JSON: %w", err) } return rules, nil } func ParseText(text string) ([]ExportRule, []string) { lines := strings.Split(text, "\n") var out []ExportRule var errs []string for i, line := range lines { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } r, err := ParseLine(line) if err != nil { errs = append(errs, fmt.Sprintf("第 %d 行: %v", i+1, err)) continue } out = append(out, r) } return out, errs } func ParseLine(line string) (ExportRule, error) { parts := strings.Split(line, ",") if len(parts) < 2 { return ExportRule{}, fmt.Errorf("格式无效 %q", line) } ruleType := strings.TrimSpace(strings.ToUpper(parts[0])) if ruleType == "MATCH" { return ExportRule{}, fmt.Errorf("跳过 MATCH 规则") } target := strings.TrimSpace(parts[len(parts)-1]) if target == "" { return ExportRule{}, fmt.Errorf("缺少出站目标") } payload := "" if len(parts) > 2 { payload = strings.Join(parts[1:len(parts)-1], ",") } if needsPayload(ruleType) && payload == "" { return ExportRule{}, fmt.Errorf("%s 需要匹配值", ruleType) } enabled := true return ExportRule{ RuleType: ruleType, Payload: payload, Target: target, Enabled: &enabled, }, nil } func needsPayload(ruleType string) bool { switch ruleType { case "DIRECT", "REJECT", "REJECT-DROP", "MATCH": return false default: return true } } func ToStoreRules(rules []ExportRule) ([]store.Rule, []string) { out := make([]store.Rule, 0, len(rules)) var errs []string for i, r := range rules { ruleType := strings.TrimSpace(strings.ToUpper(r.RuleType)) target := strings.TrimSpace(r.Target) if ruleType == "" || ruleType == "MATCH" { errs = append(errs, fmt.Sprintf("规则 %d: 类型无效", i+1)) continue } if target == "" { errs = append(errs, fmt.Sprintf("规则 %d: 缺少出站目标", i+1)) continue } payload := strings.TrimSpace(r.Payload) if needsPayload(ruleType) && payload == "" { errs = append(errs, fmt.Sprintf("规则 %d: %s 需要匹配值", i+1, ruleType)) continue } out = append(out, store.Rule{ Priority: r.Priority, RuleType: ruleType, Payload: payload, Target: target, Source: "manual", Enabled: ruleEnabled(r), }) } return out, errs } func AssignPriorities(rules []store.Rule, start int) { if start <= 0 { start = 100 } step := 10 for i := range rules { if rules[i].Priority <= 0 { rules[i].Priority = start start += step } } }