Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
801 lines
21 KiB
Go
801 lines
21 KiB
Go
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"
|
||
}
|
||
} |