package config import ( "bytes" "fmt" "os" "path/filepath" "strings" "text/template" "github.com/subconverter-go/internal/logging" ) // TemplateManager 配置模板管理器 type TemplateManager struct { logger *logging.Logger basePath string templates map[string]*template.Template configs map[string]interface{} rulesets map[string]string snippets map[string]string profileConfig map[string]interface{} } // TemplateConfig 模板配置 type TemplateConfig struct { Clash ClashConfig `yaml:"clash"` Surge SurgeConfig `yaml:"surge"` QuantumultX QuantumultXConfig `yaml:"quantumultx"` Loon LoonConfig `yaml:"loon"` Surfboard SurfboardConfig `yaml:"surfboard"` V2Ray V2RayConfig `yaml:"v2ray"` } // ClashConfig Clash模板配置 type ClashConfig struct { HTTPPort int `yaml:"http_port"` SocksPort int `yaml:"socks_port"` AllowLAN bool `yaml:"allow_lan"` LogLevel string `yaml:"log_level"` ExternalController string `yaml:"external_controller"` } // SurgeConfig Surge模板配置 type SurgeConfig struct { Port int `yaml:"port"` ProxyHTTPPort int `yaml:"proxy_http_port"` ProxySOCKS5Port int `yaml:"proxy_socks5_port"` MixedPort int `yaml:"mixed_port"` AllowLAN bool `yaml:"allow_lan"` LogLevel string `yaml:"log_level"` } // QuantumultXConfig QuantumultX模板配置 type QuantumultXConfig struct { Port int `yaml:"port"` AllowLAN bool `yaml:"allow_lan"` LogLevel string `yaml:"log_level"` } // LoonConfig Loon模板配置 type LoonConfig struct { Port int `yaml:"port"` AllowLAN bool `yaml:"allow_lan"` LogLevel string `yaml:"log_level"` } // SurfboardConfig Surfboard模板配置 type SurfboardConfig struct { Port int `yaml:"port"` AllowLAN bool `yaml:"allow_lan"` LogLevel string `yaml:"log_level"` } // V2RayConfig V2Ray模板配置 type V2RayConfig struct { AllowLAN bool `yaml:"allow_lan"` MixedPort int `yaml:"mixed_port"` LogLevel string `yaml:"log_level"` } // TemplateVariables 模板变量 type TemplateVariables struct { Global TemplateConfig NodeInfo interface{} GroupName string UpdateTime string UserInfo string TotalNodes int SelectedRule string Request RequestConfig Local LocalConfig } // RequestConfig 请求配置 type RequestConfig struct { Target string `yaml:"target"` Clash map[string]interface{} `yaml:"clash"` Surge map[string]interface{} `yaml:"surge"` } // LocalConfig 本地配置 type LocalConfig struct { Clash map[string]interface{} `yaml:"clash"` Surge map[string]interface{} `yaml:"surge"` } // NewTemplateManager 创建模板管理器 func NewTemplateManager(basePath string, logger *logging.Logger) (*TemplateManager, error) { tm := &TemplateManager{ logger: logger, basePath: basePath, templates: make(map[string]*template.Template), configs: make(map[string]interface{}), rulesets: make(map[string]string), snippets: make(map[string]string), profileConfig: make(map[string]interface{}), } if err := tm.loadTemplates(); err != nil { return nil, fmt.Errorf("failed to load templates: %w", err) } if err := tm.loadRulesets(); err != nil { return nil, fmt.Errorf("failed to load rulesets: %w", err) } if err := tm.loadSnippets(); err != nil { return nil, fmt.Errorf("failed to load snippets: %w", err) } if err := tm.loadProfileConfigs(); err != nil { return nil, fmt.Errorf("failed to load profile configs: %w", err) } return tm, nil } // loadTemplates 加载配置模板 func (tm *TemplateManager) loadTemplates() error { baseDir := filepath.Join(tm.basePath, "base") // 遍历base目录加载模板 return filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } // 只处理支持的模板文件 ext := filepath.Ext(path) if !tm.isTemplateFile(ext) { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read template file %s: %w", path, err) } // 转换Jinja2语法到Go模板语法 convertedContent := tm.convertJinja2ToGoTemplate(string(content)) // 创建模板 name := tm.getTemplateName(path, baseDir) tmpl := template.New(name).Funcs(template.FuncMap{ "default": tm.defaultFunc, "or": tm.orFunc, "eq": tm.eqFunc, }) tmpl, err = tmpl.Parse(convertedContent) if err != nil { // 如果模板解析失败,记录警告但跳过这个模板 tm.logger.Warnf("Failed to parse template %s: %v, skipping", name, err) return nil } tm.templates[name] = tmpl tm.logger.Infof("Loaded template: %s", name) return nil }) } // loadRulesets 加载规则集 func (tm *TemplateManager) loadRulesets() error { rulesDir := filepath.Join(tm.basePath, "rules") return filepath.Walk(rulesDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } // 只处理规则文件 ext := filepath.Ext(path) if !tm.isRulesetFile(ext) { return nil } content, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read ruleset file %s: %w", path, err) } name := tm.getRulesetName(path, rulesDir) tm.rulesets[name] = string(content) tm.logger.Infof("Loaded ruleset: %s", name) return nil }) } // loadSnippets 加载代码片段 func (tm *TemplateManager) loadSnippets() error { snippetsDir := filepath.Join(tm.basePath, "snippets") entries, err := os.ReadDir(snippetsDir) if err != nil { if os.IsNotExist(err) { return nil } return err } for _, entry := range entries { if entry.IsDir() { continue } content, err := os.ReadFile(filepath.Join(snippetsDir, entry.Name())) if err != nil { return fmt.Errorf("failed to read snippet file %s: %w", entry.Name(), err) } tm.snippets[entry.Name()] = string(content) tm.logger.Infof("Loaded snippet: %s", entry.Name()) } return nil } // loadProfileConfigs 加载配置文件 func (tm *TemplateManager) loadProfileConfigs() error { configDir := filepath.Join(tm.basePath, "config") entries, err := os.ReadDir(configDir) if err != nil { if os.IsNotExist(err) { return nil } return err } for _, entry := range entries { if entry.IsDir() { continue } // 加载.ini配置文件 if strings.HasSuffix(entry.Name(), ".ini") { content, err := os.ReadFile(filepath.Join(configDir, entry.Name())) if err != nil { return fmt.Errorf("failed to read config file %s: %w", entry.Name(), err) } // 这里简化处理,实际应该解析INI文件 tm.profileConfig[entry.Name()] = string(content) tm.logger.Infof("Loaded profile config: %s", entry.Name()) } } return nil } // convertJinja2ToGoTemplate 转换Jinja2语法到Go模板语法 func (tm *TemplateManager) convertJinja2ToGoTemplate(content string) string { converted := content // 转换 if 条件语句 converted = strings.ReplaceAll(converted, "{% if ", "{{ if ") converted = strings.ReplaceAll(converted, "{% endif %", "{{ end }}") converted = strings.ReplaceAll(converted, "{% else %", "{{ else }}") // 转换字符串比较 - 将 == 转换为 eq 函数调用 // 这种转换比较复杂,我们只在特定的模式中转换 converted = strings.ReplaceAll(converted, ".Request.Target == \"clash\"", "eq .Request.Target \"clash\"") converted = strings.ReplaceAll(converted, ".Request.Target == \"clashr\"", "eq .Request.Target \"clashr\"") converted = strings.ReplaceAll(converted, " == \"1\"", " eq \"1\"") converted = strings.ReplaceAll(converted, " == \"true\"", " eq \"true\"") // 转换 or 运算符为 or 函数调用 converted = strings.ReplaceAll(converted, " or ", " | ") // 转换变量引用,比如 request.target 到 .Request.Target converted = strings.ReplaceAll(converted, "request.target", ".Request.Target") converted = strings.ReplaceAll(converted, "request.clash.", ".Request.Clash.") converted = strings.ReplaceAll(converted, "request.surge.", ".Request.Surge.") converted = strings.ReplaceAll(converted, "local.clash.", ".Local.Clash.") converted = strings.ReplaceAll(converted, "local.surge.", ".Local.Surge.") converted = strings.ReplaceAll(converted, "global.clash.", ".Global.Clash.") converted = strings.ReplaceAll(converted, "global.surge.", ".Global.Surge.") // 转换 default 函数调用 converted = strings.ReplaceAll(converted, "default(", "default ") return converted } // defaultFunc 实现default函数 func (tm *TemplateManager) defaultFunc(value, defaultValue interface{}) interface{} { if value == nil { return defaultValue } // 检查字符串是否为空 if str, ok := value.(string); ok { if str == "" { return defaultValue } } // 检查数字是否为0 if num, ok := value.(int); ok { if num == 0 { return defaultValue } } // 检查布尔值是否为false if b, ok := value.(bool); ok { if !b { return defaultValue } } return value } // orFunc 实现or函数 func (tm *TemplateManager) orFunc(args ...interface{}) interface{} { for _, arg := range args { if tm.isTrue(arg) { return arg } } return args[len(args)-1] // 返回最后一个参数 } // eqFunc 实现eq函数 func (tm *TemplateManager) eqFunc(a, b interface{}) bool { return a == b } // isTrue 检查值是否为真 func (tm *TemplateManager) isTrue(value interface{}) bool { if value == nil { return false } switch v := value.(type) { case bool: return v case string: return v != "" case int, int8, int16, int32, int64: return v != 0 case uint, uint8, uint16, uint32, uint64: return v != 0 case float32, float64: return v != 0 default: return true } } // isTemplateFile 检查是否为模板文件 func (tm *TemplateManager) isTemplateFile(ext string) bool { templateExts := []string{".yml", ".yaml", ".conf", ".json", ".tpl"} for _, templateExt := range templateExts { if ext == templateExt { return true } } return false } // isRulesetFile 检查是否为规则文件 func (tm *TemplateManager) isRulesetFile(ext string) bool { rulesetExts := []string{".list", ".txt", ".rules"} for _, rulesetExt := range rulesetExts { if ext == rulesetExt { return true } } return false } // getTemplateName 获取模板名称 func (tm *TemplateManager) getTemplateName(path, baseDir string) string { relPath, err := filepath.Rel(baseDir, path) if err != nil { return filepath.Base(path) } return strings.TrimSuffix(relPath, filepath.Ext(relPath)) } // getRulesetName 获取规则集名称 func (tm *TemplateManager) getRulesetName(path, rulesDir string) string { relPath, err := filepath.Rel(rulesDir, path) if err != nil { return filepath.Base(path) } return strings.TrimSuffix(relPath, filepath.Ext(relPath)) } // GetTemplate 获取模板 func (tm *TemplateManager) GetTemplate(name string) (*template.Template, bool) { tmpl, exists := tm.templates[name] return tmpl, exists } // GetRuleset 获取规则集 func (tm *TemplateManager) GetRuleset(name string) (string, bool) { ruleset, exists := tm.rulesets[name] return ruleset, exists } // GetSnippet 获取代码片段 func (tm *TemplateManager) GetSnippet(name string) (string, bool) { snippet, exists := tm.snippets[name] return snippet, exists } // GetProfileConfig 获取配置文件 func (tm *TemplateManager) GetProfileConfig(name string) (interface{}, bool) { config, exists := tm.profileConfig[name] return config, exists } // ListTemplates 列出所有模板 func (tm *TemplateManager) ListTemplates() []string { var templates []string for name := range tm.templates { templates = append(templates, name) } return templates } // ListRulesets 列出所有规则集 func (tm *TemplateManager) ListRulesets() []string { var rulesets []string for name := range tm.rulesets { rulesets = append(rulesets, name) } return rulesets } // ListProfileConfigs 列出所有配置文件 func (tm *TemplateManager) ListProfileConfigs() []string { var configs []string for name := range tm.profileConfig { configs = append(configs, name) } return configs } // RenderTemplate 渲染模板 func (tm *TemplateManager) RenderTemplate(name string, variables TemplateVariables) (string, error) { tmpl, exists := tm.GetTemplate(name) if !exists { return "", fmt.Errorf("template not found: %s", name) } var buf bytes.Buffer if err := tmpl.Execute(&buf, variables); err != nil { return "", fmt.Errorf("failed to render template %s: %w", name, err) } return buf.String(), nil } // RenderTemplateWithDefaults 使用默认配置渲染模板 func (tm *TemplateManager) RenderTemplateWithDefaults(name string, groupName string, nodeInfo interface{}, totalNodes int) (string, error) { variables := TemplateVariables{ Global: TemplateConfig{ Clash: ClashConfig{ HTTPPort: 7890, SocksPort: 7891, AllowLAN: true, LogLevel: "info", ExternalController: "127.0.0.1:9090", }, Surge: SurgeConfig{ Port: 8080, ProxyHTTPPort: 8081, ProxySOCKS5Port: 8082, MixedPort: 8080, AllowLAN: true, LogLevel: "info", }, QuantumultX: QuantumultXConfig{ Port: 8888, AllowLAN: true, LogLevel: "info", }, Loon: LoonConfig{ Port: 8080, AllowLAN: true, LogLevel: "info", }, Surfboard: SurfboardConfig{ Port: 8080, AllowLAN: true, LogLevel: "info", }, V2Ray: V2RayConfig{ AllowLAN: true, MixedPort: 2080, LogLevel: "info", }, }, NodeInfo: nodeInfo, GroupName: groupName, UpdateTime: tm.getCurrentTime(), UserInfo: "", TotalNodes: totalNodes, Request: RequestConfig{ Target: "clash", Clash: make(map[string]interface{}), Surge: make(map[string]interface{}), }, Local: LocalConfig{ Clash: make(map[string]interface{}), Surge: make(map[string]interface{}), }, } return tm.RenderTemplate(name, variables) } // getCurrentTime 获取当前时间 func (tm *TemplateManager) getCurrentTime() string { // 简化实现,返回格式化时间 return "2025-09-25 17:30:00" } // GetTemplateVariables 获取模板变量 func (tm *TemplateManager) GetTemplateVariables() TemplateVariables { return TemplateVariables{ Global: TemplateConfig{ Clash: ClashConfig{ HTTPPort: 7890, SocksPort: 7891, AllowLAN: true, LogLevel: "info", ExternalController: "127.0.0.1:9090", }, // ... 其他默认配置 }, } } // Reload 重新加载所有模板和配置 func (tm *TemplateManager) Reload() error { tm.logger.Info("Reloading templates and configurations...") // 清空现有数据 tm.templates = make(map[string]*template.Template) tm.rulesets = make(map[string]string) tm.snippets = make(map[string]string) tm.profileConfig = make(map[string]interface{}) // 重新加载 if err := tm.loadTemplates(); err != nil { return fmt.Errorf("failed to reload templates: %w", err) } if err := tm.loadRulesets(); err != nil { return fmt.Errorf("failed to reload rulesets: %w", err) } if err := tm.loadSnippets(); err != nil { return fmt.Errorf("failed to reload snippets: %w", err) } if err := tm.loadProfileConfigs(); err != nil { return fmt.Errorf("failed to reload profile configs: %w", err) } tm.logger.Info("Templates and configurations reloaded successfully") return nil }