package handler import ( "fmt" "path/filepath" "strconv" "strings" "time" "github.com/subconverter-go/internal/config" "github.com/subconverter-go/internal/logging" "github.com/gofiber/fiber/v2" ) // TemplateHandler 模板处理器 type TemplateHandler struct { templateManager *config.TemplateManager logger *logging.Logger configManager *config.ConfigManager } // TemplateInfo 模板信息 type TemplateInfo struct { Name string `json:"name"` Type string `json:"type"` Description string `json:"description"` Size int64 `json:"size"` } // RulesetInfo 规则集信息 type RulesetInfo struct { Name string `json:"name"` Type string `json:"type"` Description string `json:"description"` RuleCount int `json:"rule_count"` } // TemplateListRequest 模板列表请求 type TemplateListRequest struct { Type string `query:"type"` // 模板类型: clash, surge, quanx, loon, surfboard, v2ray Format string `query:"format"` // 格式: json, yaml Profile string `query:"profile"` // 配置文件名 } // TemplateRenderRequest 模板渲染请求 type TemplateRenderRequest struct { Template string `json:"template"` // 模板名称 Profile string `json:"profile"` // 配置文件名 Variables map[string]interface{} `json:"variables"` // 自定义变量 Target string `json:"target"` // 目标格式 NodeData interface{} `json:"node_data"` // 节点数据 GroupName string `json:"group_name"` // 组名 } // TemplateRenderResponse 模板渲染响应 type TemplateRenderResponse struct { Success bool `json:"success"` Content string `json:"content"` Variables map[string]interface{} `json:"variables"` Metadata map[string]interface{} `json:"metadata"` Error string `json:"error,omitempty"` } // NewTemplateHandler 创建模板处理器 func NewTemplateHandler(templateManager *config.TemplateManager, logger *logging.Logger, configManager *config.ConfigManager) *TemplateHandler { return &TemplateHandler{ templateManager: templateManager, logger: logger, configManager: configManager, } } // RegisterRoutes 注册路由 func (h *TemplateHandler) RegisterRoutes(app *fiber.App) { // 原项目兼容性路由 - /render 端点 app.Get("/render", h.RenderDirect) // 模板相关路由 app.Get("/api/templates", h.ListTemplates) app.Get("/api/templates/:name", h.GetTemplate) app.Post("/api/templates/render", h.RenderTemplate) app.Get("/api/templates/:name/preview", h.PreviewTemplate) // 规则集相关路由 app.Get("/api/rulesets", h.ListRulesets) app.Get("/api/rulesets/:name", h.GetRuleset) app.Get("/api/rulesets/:name/content", h.GetRulesetContent) // 配置文件相关路由 app.Get("/api/profiles", h.ListProfiles) app.Get("/api/profiles/:name", h.GetProfile) app.Post("/api/profiles/:name/apply", h.ApplyProfile) // 代码片段相关路由 app.Get("/api/snippets", h.ListSnippets) app.Get("/api/snippets/:name", h.GetSnippet) // 模板管理路由 app.Post("/api/templates/reload", h.ReloadTemplates) app.Get("/api/templates/info", h.GetTemplatesInfo) } // RenderDirect 直接渲染模板(原项目兼容性端点) func (h *TemplateHandler) RenderDirect(c *fiber.Ctx) error { path := c.Query("path") if path == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "path parameter is required", }) } // 验证路径安全性,防止目录遍历攻击 if strings.Contains(path, "..") || strings.HasPrefix(path, "/") { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid path", }) } // 从模板路径中提取模板名称 templateName := strings.TrimSuffix(path, filepath.Ext(path)) // 检查模板是否存在 if _, exists := h.templateManager.GetTemplate(templateName); !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": fmt.Sprintf("Template '%s' not found", templateName), }) } // 准备模板变量,从URL参数中获取 variables := h.prepareTemplateVariables(c) // 渲染模板 content, err := h.templateManager.RenderTemplate(templateName, variables) if err != nil { h.logger.Errorf("Failed to render template %s: %v", templateName, err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": fmt.Sprintf("Failed to render template: %v", err), }) } // 根据模板类型设置Content-Type contentType := h.getContentType(templateName) c.Set("Content-Type", contentType) return c.SendString(content) } // prepareTemplateVariables 准备模板变量 func (h *TemplateHandler) prepareTemplateVariables(c *fiber.Ctx) config.TemplateVariables { // 从查询参数中提取模板变量 requestConfig := config.RequestConfig{ Target: c.Query("target", "clash"), Clash: make(map[string]interface{}), Surge: make(map[string]interface{}), } // 处理额外的查询参数 for key, value := range c.Queries() { switch { case strings.HasPrefix(key, "clash."): paramName := strings.TrimPrefix(key, "clash.") requestConfig.Clash[paramName] = value case strings.HasPrefix(key, "surge."): paramName := strings.TrimPrefix(key, "surge.") requestConfig.Surge[paramName] = value } } localConfig := config.LocalConfig{ Clash: make(map[string]interface{}), Surge: make(map[string]interface{}), } // 获取全局配置 globalConfig := config.TemplateConfig{ Clash: config.ClashConfig{ HTTPPort: 7890, SocksPort: 7891, AllowLAN: true, LogLevel: "info", ExternalController: "127.0.0.1:9090", }, Surge: config.SurgeConfig{ Port: 8080, ProxyHTTPPort: 8081, ProxySOCKS5Port: 8082, MixedPort: 8080, AllowLAN: true, LogLevel: "info", }, // 可以添加其他客户端的默认配置 } return config.TemplateVariables{ Global: globalConfig, NodeInfo: h.getSampleNodeData(), GroupName: c.Query("group", "Proxy"), UpdateTime: time.Now().Format("2006-01-02 15:04:05"), UserInfo: "", TotalNodes: 1, Request: requestConfig, Local: localConfig, } } // ListTemplates 列出所有模板 func (h *TemplateHandler) ListTemplates(c *fiber.Ctx) error { var req TemplateListRequest if err := c.QueryParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid request parameters", }) } templates := h.templateManager.ListTemplates() var filteredTemplates []TemplateInfo for _, name := range templates { // 根据类型过滤 if req.Type != "" { if !h.isTemplateOfType(name, req.Type) { continue } } templateInfo := TemplateInfo{ Name: name, Type: h.getTemplateType(name), Description: h.getTemplateDescription(name), } // 获取模板文件大小 if content, err := h.templateManager.RenderTemplate(name, config.TemplateVariables{}); err == nil { templateInfo.Size = int64(len(content)) } filteredTemplates = append(filteredTemplates, templateInfo) } // 根据格式返回不同响应 if req.Format == "yaml" { return c.JSON(filteredTemplates) } return c.JSON(fiber.Map{ "success": true, "templates": filteredTemplates, "total": len(filteredTemplates), }) } // GetTemplate 获取特定模板 func (h *TemplateHandler) GetTemplate(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Template name is required", }) } _, exists := h.templateManager.GetTemplate(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Template not found", }) } // 返回模板信息 templateInfo := fiber.Map{ "name": name, "type": h.getTemplateType(name), "description": h.getTemplateDescription(name), "exists": true, } return c.JSON(fiber.Map{ "success": true, "template": templateInfo, }) } // RenderTemplate 渲染模板 func (h *TemplateHandler) RenderTemplate(c *fiber.Ctx) error { var req TemplateRenderRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid request body", }) } // 验证必需参数 if req.Template == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Template name is required", }) } // 准备模板变量 variables := config.TemplateVariables{ GroupName: req.GroupName, NodeInfo: req.NodeData, TotalNodes: h.getTotalNodes(req.NodeData), } // 应用自定义变量 if req.Variables != nil { if global, ok := req.Variables["global"]; ok { if globalMap, ok := global.(map[string]interface{}); ok { variables.Global = h.parseGlobalConfig(globalMap) } } } // 渲染模板 content, err := h.templateManager.RenderTemplateWithDefaults( req.Template, variables.GroupName, variables.NodeInfo, variables.TotalNodes, ) if err != nil { h.logger.Errorf("Failed to render template %s: %v", req.Template, err) return c.Status(fiber.StatusInternalServerError).JSON(TemplateRenderResponse{ Success: false, Error: fmt.Sprintf("Failed to render template: %v", err), }) } // 构建响应 response := TemplateRenderResponse{ Success: true, Content: content, Variables: map[string]interface{}{ "group_name": variables.GroupName, "total_nodes": variables.TotalNodes, "template": req.Template, }, Metadata: map[string]interface{}{ "template_name": req.Template, "target": req.Target, "group_name": req.GroupName, "node_count": variables.TotalNodes, }, } return c.JSON(response) } // PreviewTemplate 预览模板 func (h *TemplateHandler) PreviewTemplate(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Template name is required", }) } // 使用示例数据预览 sampleData := h.getSampleNodeData() content, err := h.templateManager.RenderTemplateWithDefaults( name, "SampleGroup", sampleData, 1, ) if err != nil { h.logger.Errorf("Failed to preview template %s: %v", name, err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": fmt.Sprintf("Failed to preview template: %v", err), }) } // 根据模板类型设置Content-Type contentType := h.getContentType(name) c.Set("Content-Type", contentType) return c.SendString(content) } // ListRulesets 列出所有规则集 func (h *TemplateHandler) ListRulesets(c *fiber.Ctx) error { rulesets := h.templateManager.ListRulesets() var rulesetInfos []RulesetInfo for _, name := range rulesets { if content, exists := h.templateManager.GetRuleset(name); exists { rulesetInfos = append(rulesetInfos, RulesetInfo{ Name: name, Type: h.getRulesetType(name), Description: h.getRulesetDescription(name), RuleCount: h.countRules(content), }) } } return c.JSON(fiber.Map{ "success": true, "rulesets": rulesetInfos, "total": len(rulesetInfos), }) } // GetRuleset 获取规则集信息 func (h *TemplateHandler) GetRuleset(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Ruleset name is required", }) } content, exists := h.templateManager.GetRuleset(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Ruleset not found", }) } rulesetInfo := RulesetInfo{ Name: name, Type: h.getRulesetType(name), Description: h.getRulesetDescription(name), RuleCount: h.countRules(content), } return c.JSON(fiber.Map{ "success": true, "ruleset": rulesetInfo, }) } // GetRulesetContent 获取规则集内容 func (h *TemplateHandler) GetRulesetContent(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Ruleset name is required", }) } content, exists := h.templateManager.GetRuleset(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Ruleset not found", }) } c.Set("Content-Type", "text/plain") return c.SendString(content) } // ListProfiles 列出所有配置文件 func (h *TemplateHandler) ListProfiles(c *fiber.Ctx) error { profiles := h.templateManager.ListProfileConfigs() return c.JSON(fiber.Map{ "success": true, "profiles": profiles, "total": len(profiles), }) } // GetProfile 获取配置文件 func (h *TemplateHandler) GetProfile(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Profile name is required", }) } profile, exists := h.templateManager.GetProfileConfig(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Profile not found", }) } return c.JSON(fiber.Map{ "success": true, "profile": profile, }) } // ApplyProfile 应用配置文件 func (h *TemplateHandler) ApplyProfile(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Profile name is required", }) } // 这里简化处理,实际应该解析并应用配置文件 _, exists := h.templateManager.GetProfileConfig(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Profile not found", }) } h.logger.Infof("Applied profile: %s", name) return c.JSON(fiber.Map{ "success": true, "message": fmt.Sprintf("Profile '%s' applied successfully", name), }) } // ListSnippets 列出所有代码片段 func (h *TemplateHandler) ListSnippets(c *fiber.Ctx) error { snippets := make(map[string]string) // 这里简化处理,实际应该从templateManager获取 snippets["groups.txt"] = "Proxy group configuration snippets" snippets["rename_node.txt"] = "Node renaming snippets" return c.JSON(fiber.Map{ "success": true, "snippets": snippets, "total": len(snippets), }) } // GetSnippet 获取代码片段 func (h *TemplateHandler) GetSnippet(c *fiber.Ctx) error { name := c.Params("name") if name == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Snippet name is required", }) } snippet, exists := h.templateManager.GetSnippet(name) if !exists { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "error": "Snippet not found", }) } c.Set("Content-Type", "text/plain") return c.SendString(snippet) } // ReloadTemplates 重新加载模板 func (h *TemplateHandler) ReloadTemplates(c *fiber.Ctx) error { if err := h.templateManager.Reload(); err != nil { h.logger.Errorf("Failed to reload templates: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": fmt.Sprintf("Failed to reload templates: %v", err), }) } h.logger.Info("Templates reloaded successfully") return c.JSON(fiber.Map{ "success": true, "message": "Templates reloaded successfully", }) } // GetTemplatesInfo 获取模板系统信息 func (h *TemplateHandler) GetTemplatesInfo(c *fiber.Ctx) error { templates := h.templateManager.ListTemplates() rulesets := h.templateManager.ListRulesets() profiles := h.templateManager.ListProfileConfigs() info := fiber.Map{ "template_count": len(templates), "ruleset_count": len(rulesets), "profile_count": len(profiles), "base_path": h.configManager.GetBasePath(), "supported_types": []string{"clash", "surge", "quanx", "loon", "surfboard", "v2ray"}, } return c.JSON(fiber.Map{ "success": true, "info": info, }) } // 辅助方法 // isTemplateOfType 检查模板是否为指定类型 func (h *TemplateHandler) isTemplateOfType(name, templateType string) bool { return strings.Contains(strings.ToLower(name), strings.ToLower(templateType)) } // getTemplateType 获取模板类型 func (h *TemplateHandler) getTemplateType(name string) string { name = strings.ToLower(name) switch { case strings.Contains(name, "clash"): return "clash" case strings.Contains(name, "surge"): return "surge" case strings.Contains(name, "quanx"): return "quanx" case strings.Contains(name, "loon"): return "loon" case strings.Contains(name, "surfboard"): return "surfboard" case strings.Contains(name, "v2ray"): return "v2ray" default: return "unknown" } } // getTemplateDescription 获取模板描述 func (h *TemplateHandler) getTemplateDescription(name string) string { descriptions := map[string]string{ "GeneralClashConfig": "General Clash configuration template", "forcerule": "Force rule configuration template", "surge.conf": "Surge configuration template", "quan.conf": "Quantumult configuration template", "loon.conf": "Loon configuration template", "surfboard.conf": "Surfboard configuration template", } if desc, exists := descriptions[name]; exists { return desc } return fmt.Sprintf("Template: %s", name) } // getRulesetType 获取规则集类型 func (h *TemplateHandler) getRulesetType(name string) string { ext := filepath.Ext(name) switch ext { case ".list": return "domain_list" case ".txt": return "text_rules" case ".rules": return "rules" default: return "unknown" } } // getRulesetDescription 获取规则集描述 func (h *TemplateHandler) getRulesetDescription(name string) string { // 这里可以根据规则集名称返回描述 return fmt.Sprintf("Ruleset: %s", name) } // countRules 计算规则数量 func (h *TemplateHandler) countRules(content string) int { lines := strings.Split(content, "\n") count := 0 for _, line := range lines { line = strings.TrimSpace(line) if line != "" && !strings.HasPrefix(line, "#") { count++ } } return count } // parseGlobalConfig 解析全局配置 func (h *TemplateHandler) parseGlobalConfig(cfg map[string]interface{}) config.TemplateConfig { var globalConfig config.TemplateConfig // 解析Clash配置 if clash, ok := cfg["clash"].(map[string]interface{}); ok { globalConfig.Clash.HTTPPort = h.getInt(clash, "http_port", 7890) globalConfig.Clash.SocksPort = h.getInt(clash, "socks_port", 7891) globalConfig.Clash.AllowLAN = h.getBool(clash, "allow_lan", true) globalConfig.Clash.LogLevel = h.getString(clash, "log_level", "info") globalConfig.Clash.ExternalController = h.getString(clash, "external_controller", "127.0.0.1:9090") } // 解析Surge配置 if surge, ok := cfg["surge"].(map[string]interface{}); ok { globalConfig.Surge.Port = h.getInt(surge, "port", 8080) globalConfig.Surge.ProxyHTTPPort = h.getInt(surge, "proxy_http_port", 8081) globalConfig.Surge.ProxySOCKS5Port = h.getInt(surge, "proxy_socks5_port", 8082) globalConfig.Surge.MixedPort = h.getInt(surge, "mixed_port", 8080) globalConfig.Surge.AllowLAN = h.getBool(surge, "allow_lan", true) globalConfig.Surge.LogLevel = h.getString(surge, "log_level", "info") } return globalConfig } // getInt 获取整数值 func (h *TemplateHandler) getInt(m map[string]interface{}, key string, defaultValue int) int { if val, ok := m[key]; ok { switch v := val.(type) { case float64: return int(v) case int: return v case string: if i, err := strconv.Atoi(v); err == nil { return i } } } return defaultValue } // getBool 获取布尔值 func (h *TemplateHandler) getBool(m map[string]interface{}, key string, defaultValue bool) bool { if val, ok := m[key]; ok { switch v := val.(type) { case bool: return v case string: if b, err := strconv.ParseBool(v); err == nil { return b } } } return defaultValue } // getString 获取字符串值 func (h *TemplateHandler) getString(m map[string]interface{}, key string, defaultValue string) string { if val, ok := m[key]; ok { if str, ok := val.(string); ok { return str } } return defaultValue } // getTotalNodes 获取节点总数 func (h *TemplateHandler) getTotalNodes(nodeData interface{}) int { if nodeData == nil { return 0 } // 根据不同的数据结构计算节点数量 switch data := nodeData.(type) { case []interface{}: return len(data) case map[string]interface{}: if proxies, ok := data["proxies"].([]interface{}); ok { return len(proxies) } } return 1 // 默认返回1 } // getSampleNodeData 获取示例节点数据 func (h *TemplateHandler) getSampleNodeData() interface{} { return map[string]interface{}{ "remark": "Sample Server", "hostname": "sample.example.com", "port": 443, "type": "shadowsocks", "settings": map[string]interface{}{ "method": "aes-256-gcm", "password": "sample-password", }, } } // getContentType 获取内容类型 func (h *TemplateHandler) getContentType(name string) string { ext := filepath.Ext(name) switch ext { case ".yml", ".yaml": return "application/x-yaml" case ".json": return "application/json" case ".conf": return "text/plain" default: return "text/plain" } }