package coremain import ( "fmt" "os" "path/filepath" "strings" "go.uber.org/zap" "gopkg.in/yaml.v3" ) // DomainRule 域名规则配置 type DomainRule struct { Name string `json:"name"` // 规则名称 Description string `json:"description"` // 规则描述 DomainFile string `json:"domain_file"` // 域名文件路径 DNSStrategy string `json:"dns_strategy"` // DNS策略:china-dns/overseas-dns/smart-fallback EnableMikroTik bool `json:"enable_mikrotik"` // 是否启用MikroTik推送 MikroTikConfig MikroTikConfig `json:"mikrotik_config"` // MikroTik配置 Enabled bool `json:"enabled"` // 是否启用 } // MikroTikConfig MikroTik路由器配置 type MikroTikConfig struct { Host string `json:"host"` // 路由器地址 Port int `json:"port"` // API端口 Username string `json:"username"` // 用户名 Password string `json:"password"` // 密码 AddressList string `json:"address_list"` // 地址列表名称 Mask int `json:"mask"` // IP掩码 MaxIPs int `json:"max_ips"` // 最大IP数 CacheTTL int `json:"cache_ttl"` // 缓存时间(秒) TimeoutAddr int `json:"timeout_addr"` // 地址超时(秒) Comment string `json:"comment"` // 备注 } // ConfigBuilder 高级配置构建器 type ConfigBuilder struct { baseConfig *Config logger *zap.Logger rulesDir string } // NewConfigBuilder 创建配置构建器 func NewConfigBuilder(baseConfig *Config, logger *zap.Logger) *ConfigBuilder { return &ConfigBuilder{ baseConfig: baseConfig, logger: logger, rulesDir: "config.d/rules", } } // AddDomainRule 添加域名规则 func (b *ConfigBuilder) AddDomainRule(rule DomainRule) error { if rule.Name == "" { return fmt.Errorf("规则名称不能为空") } if rule.DomainFile == "" { return fmt.Errorf("域名文件路径不能为空") } if rule.DNSStrategy == "" { rule.DNSStrategy = "smart-fallback" // 默认使用智能防污染 } // 生成规则配置文件 ruleConfig := b.generateRuleConfig(rule) ruleFileName := rule.Name + ".yaml" ruleFilePath := filepath.Join(b.rulesDir, ruleFileName) // 保存规则文件 if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil { return fmt.Errorf("保存规则文件失败: %w", err) } // 将规则添加到主配置的include中 b.addRuleToIncludes(ruleFileName) b.logger.Info("域名规则添加成功", zap.String("rule_name", rule.Name), zap.String("file_path", ruleFilePath)) return nil } // UpdateDomainRule 更新域名规则 func (b *ConfigBuilder) UpdateDomainRule(ruleName string, rule DomainRule) error { ruleFileName := ruleName + ".yaml" ruleFilePath := filepath.Join(b.rulesDir, ruleFileName) // 检查规则文件是否存在 if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) { return fmt.Errorf("规则文件不存在: %s", ruleFilePath) } // 生成新的规则配置 ruleConfig := b.generateRuleConfig(rule) // 保存更新后的规则文件 if err := b.saveRuleConfig(ruleFilePath, ruleConfig); err != nil { return fmt.Errorf("更新规则文件失败: %w", err) } b.logger.Info("域名规则更新成功", zap.String("rule_name", ruleName), zap.String("file_path", ruleFilePath)) return nil } // DeleteDomainRule 删除域名规则 func (b *ConfigBuilder) DeleteDomainRule(ruleName string) error { ruleFileName := ruleName + ".yaml" ruleFilePath := filepath.Join(b.rulesDir, ruleFileName) // 检查规则文件是否存在 if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) { return fmt.Errorf("规则文件不存在: %s", ruleFilePath) } // 删除规则文件 if err := os.Remove(ruleFilePath); err != nil { return fmt.Errorf("删除规则文件失败: %w", err) } // 从主配置的include中移除 b.removeRuleFromIncludes(ruleFileName) b.logger.Info("域名规则删除成功", zap.String("rule_name", ruleName), zap.String("file_path", ruleFilePath)) return nil } // generateRuleConfig 生成规则配置文件内容 func (b *ConfigBuilder) generateRuleConfig(rule DomainRule) *Config { config := &Config{ Plugins: []PluginConfig{}, } // 1. 创建域名集合插件 domainSetTag := "domains_" + rule.Name domainSetPlugin := PluginConfig{ Tag: domainSetTag, Type: "domain_set", Args: map[string]interface{}{ "files": []string{rule.DomainFile}, }, } config.Plugins = append(config.Plugins, domainSetPlugin) // 2. 创建规则执行插件 ruleTag := "rule_" + rule.Name var ruleExec []map[string]interface{} if rule.Enabled { ruleExec = []map[string]interface{}{ { "matches": "qname $" + domainSetTag, "exec": "$" + rule.DNSStrategy, }, } } rulePlugin := PluginConfig{ Tag: ruleTag, Type: "sequence", Args: map[string]interface{}{ "exec": ruleExec, }, } config.Plugins = append(config.Plugins, rulePlugin) // 3. 如果启用MikroTik,创建MikroTik推送插件 if rule.EnableMikroTik { mikrotikTag := "mikrotik_" + rule.Name mikrotikPlugin := PluginConfig{ Tag: mikrotikTag, Type: "mikrotik_addresslist", Args: map[string]interface{}{ "host": rule.MikroTikConfig.Host, "port": rule.MikroTikConfig.Port, "username": rule.MikroTikConfig.Username, "password": rule.MikroTikConfig.Password, "address_list": b.getMikroTikAddressList(rule), "mask": rule.MikroTikConfig.Mask, "max_ips": rule.MikroTikConfig.MaxIPs, "cache_ttl": rule.MikroTikConfig.CacheTTL, "timeout_addr": rule.MikroTikConfig.TimeoutAddr, "comment": b.getMikroTikComment(rule), }, } config.Plugins = append(config.Plugins, mikrotikPlugin) // 在规则执行后添加MikroTik推送 rulePlugin.Args = map[string]interface{}{ "exec": append(ruleExec, map[string]interface{}{ "matches": "has_resp", "exec": "$" + mikrotikTag, }), } } return config } // getMikroTikAddressList 获取MikroTik地址列表名称 func (b *ConfigBuilder) getMikroTikAddressList(rule DomainRule) string { if rule.MikroTikConfig.AddressList != "" { return rule.MikroTikConfig.AddressList } return rule.Name // 默认使用规则名称 } // getMikroTikComment 获取MikroTik备注 func (b *ConfigBuilder) getMikroTikComment(rule DomainRule) string { if rule.MikroTikConfig.Comment != "" { return rule.MikroTikConfig.Comment } return fmt.Sprintf("%s-AutoAdd", rule.Name) // 默认备注 } // saveRuleConfig 保存规则配置到文件 func (b *ConfigBuilder) saveRuleConfig(filePath string, config *Config) error { // 确保目录存在 dir := filepath.Dir(filePath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建目录失败: %w", err) } // 编码配置为YAML var buf strings.Builder encoder := yaml.NewEncoder(&buf) encoder.SetIndent(2) // 设置缩进为2个空格 if err := encoder.Encode(config); err != nil { return fmt.Errorf("编码YAML失败: %w", err) } // 写入文件 if err := os.WriteFile(filePath, []byte(buf.String()), 0644); err != nil { return fmt.Errorf("写入文件失败: %w", err) } return nil } // addRuleToIncludes 将规则添加到主配置的include中 func (b *ConfigBuilder) addRuleToIncludes(ruleFileName string) { rulePath := filepath.Join(b.rulesDir, ruleFileName) // 检查是否已存在 for _, include := range b.baseConfig.Include { if include == rulePath { return // 已存在,无需添加 } } // 添加到include列表 b.baseConfig.Include = append(b.baseConfig.Include, rulePath) } // removeRuleFromIncludes 从主配置的include中移除规则 func (b *ConfigBuilder) removeRuleFromIncludes(ruleFileName string) { rulePath := filepath.Join(b.rulesDir, ruleFileName) // 找到并移除 for i, include := range b.baseConfig.Include { if include == rulePath { b.baseConfig.Include = append(b.baseConfig.Include[:i], b.baseConfig.Include[i+1:]...) break } } } // ListRules 列出所有规则 func (b *ConfigBuilder) ListRules() ([]DomainRule, error) { var rules []DomainRule // 确保规则目录存在 if _, err := os.Stat(b.rulesDir); os.IsNotExist(err) { return rules, nil // 目录不存在,返回空列表 } // 遍历规则文件 entries, err := os.ReadDir(b.rulesDir) if err != nil { return nil, fmt.Errorf("读取规则目录失败: %w", err) } for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") { continue } ruleName := strings.TrimSuffix(entry.Name(), ".yaml") rule, err := b.GetRule(ruleName) if err != nil { b.logger.Warn("加载规则失败", zap.String("rule_name", ruleName), zap.Error(err)) continue } rules = append(rules, rule) } return rules, nil } // GetRule 获取指定规则 func (b *ConfigBuilder) GetRule(ruleName string) (DomainRule, error) { ruleFileName := ruleName + ".yaml" ruleFilePath := filepath.Join(b.rulesDir, ruleFileName) // 检查规则文件是否存在 if _, err := os.Stat(ruleFilePath); os.IsNotExist(err) { return DomainRule{}, fmt.Errorf("规则文件不存在: %s", ruleFilePath) } // 读取并解析规则文件 data, err := os.ReadFile(ruleFilePath) if err != nil { return DomainRule{}, fmt.Errorf("读取规则文件失败: %w", err) } var config Config if err := yaml.Unmarshal(data, &config); err != nil { return DomainRule{}, fmt.Errorf("解析规则文件失败: %w", err) } // 从配置中提取规则信息 rule := b.parseRuleFromConfig(ruleName, &config) return rule, nil } // parseRuleFromConfig 从配置中解析规则信息 func (b *ConfigBuilder) parseRuleFromConfig(ruleName string, config *Config) DomainRule { rule := DomainRule{ Name: ruleName, Enabled: true, // 默认启用 } for _, plugin := range config.Plugins { switch plugin.Type { case "domain_set": if args, ok := plugin.Args.(map[string]interface{}); ok { if files, ok := args["files"].([]interface{}); ok && len(files) > 0 { if file, ok := files[0].(string); ok { rule.DomainFile = file } } } case "sequence": if plugin.Tag == "rule_"+ruleName { if args, ok := plugin.Args.(map[string]interface{}); ok { if exec, ok := args["exec"].([]interface{}); ok && len(exec) > 0 { if execMap, ok := exec[0].(map[string]interface{}); ok { if exec, ok := execMap["exec"].(string); ok { rule.DNSStrategy = strings.TrimPrefix(exec, "$") } } } } } case "mikrotik_addresslist": rule.EnableMikroTik = true if args, ok := plugin.Args.(map[string]interface{}); ok { rule.MikroTikConfig = MikroTikConfig{ Host: getStringValue(args, "host"), Port: getIntValue(args, "port"), Username: getStringValue(args, "username"), Password: getStringValue(args, "password"), AddressList: getStringValue(args, "address_list"), Mask: getIntValue(args, "mask"), MaxIPs: getIntValue(args, "max_ips"), CacheTTL: getIntValue(args, "cache_ttl"), TimeoutAddr: getIntValue(args, "timeout_addr"), Comment: getStringValue(args, "comment"), } } } } return rule } // 辅助函数:获取字符串值 func getStringValue(args map[string]interface{}, key string) string { if val, ok := args[key].(string); ok { return val } return "" } // 辅助函数:获取整数值 func getIntValue(args map[string]interface{}, key string) int { if val, ok := args[key].(int); ok { return val } return 0 } // Save 保存主配置 func (b *ConfigBuilder) Save() error { // 主配置文件的路径 configPath := "config.yaml" // 编码配置为YAML var buf strings.Builder encoder := yaml.NewEncoder(&buf) encoder.SetIndent(2) if err := encoder.Encode(b.baseConfig); err != nil { return fmt.Errorf("编码主配置失败: %w", err) } // 写入主配置文件 if err := os.WriteFile(configPath, []byte(buf.String()), 0644); err != nil { return fmt.Errorf("写入主配置失败: %w", err) } b.logger.Info("主配置保存成功", zap.String("file", configPath)) return nil }