first commit
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

This commit is contained in:
Rogee
2025-09-28 10:05:07 +08:00
commit 7fcabe0225
481 changed files with 125127 additions and 0 deletions

467
internal/config/manager.go Normal file
View File

@@ -0,0 +1,467 @@
package config
import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"github.com/subconverter-go/internal/logging"
)
// ConfigManager 配置管理器
// 封装viper功能提供统一的配置管理接口
type ConfigManager struct {
viper *viper.Viper
config *Configuration
logger *logging.Logger
filePath string
basePath string
templateManager *TemplateManager
}
// NewConfigManager 创建新的配置管理器
// 返回初始化好的ConfigManager实例
func NewConfigManager(configPath string) (*ConfigManager, error) {
logger, err := logging.NewDefaultLogger()
if err != nil {
return nil, fmt.Errorf("failed to create logger: %v", err)
}
// 设置base路径
basePath := "./base"
if _, err := os.Stat(basePath); os.IsNotExist(err) {
basePath = "../base"
}
manager := &ConfigManager{
viper: viper.New(),
logger: logger,
basePath: basePath,
}
// 设置默认配置
manager.setDefaults()
// 配置viper
manager.viper.SetConfigName("config")
manager.viper.SetConfigType("yaml")
// 设置配置文件路径
if configPath != "" {
manager.viper.SetConfigFile(configPath)
manager.filePath = configPath
} else {
// 默认配置文件路径
manager.viper.AddConfigPath(".")
manager.viper.AddConfigPath("./config")
manager.viper.AddConfigPath("./configs")
manager.viper.AddConfigPath("/etc/subconverter-go")
}
// 读取配置文件
if err := manager.viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件不存在,使用默认配置
logger.Info("Configuration file not found, using defaults")
} else {
return nil, fmt.Errorf("failed to read config file: %v", err)
}
} else {
logger.Infof("Configuration loaded from: %s", manager.viper.ConfigFileUsed())
manager.filePath = manager.viper.ConfigFileUsed()
}
// 绑定环境变量
manager.bindEnvironmentVariables()
// 解析配置到结构体
if err := manager.parseConfig(); err != nil {
return nil, fmt.Errorf("failed to parse config: %v", err)
}
// 初始化模板管理器
templateManager, err := NewTemplateManager(basePath, logger)
if err != nil {
logger.Warnf("Failed to initialize template manager: %v, template features will be disabled", err)
} else {
manager.templateManager = templateManager
logger.Info("Template manager initialized successfully")
}
return manager, nil
}
// setDefaults 设置默认配置值
func (cm *ConfigManager) setDefaults() {
// 服务器默认配置
cm.viper.SetDefault("server.host", "0.0.0.0")
cm.viper.SetDefault("server.port", 25500)
cm.viper.SetDefault("server.read_timeout", 30)
cm.viper.SetDefault("server.write_timeout", 30)
cm.viper.SetDefault("server.max_request_size", 10*1024*1024) // 10MB
// 日志默认配置
cm.viper.SetDefault("logging.level", "info")
cm.viper.SetDefault("logging.format", "json")
cm.viper.SetDefault("logging.output", "stdout")
cm.viper.SetDefault("logging.file", "")
// 安全默认配置
cm.viper.SetDefault("security.access_tokens", []string{})
cm.viper.SetDefault("security.cors_origins", []string{"*"})
cm.viper.SetDefault("security.rate_limit", 0)
cm.viper.SetDefault("security.timeout", 60)
// 转换默认配置
cm.viper.SetDefault("conversion.default_target", "clash")
cm.viper.SetDefault("conversion.default_emoji", false)
cm.viper.SetDefault("conversion.default_udp", false)
cm.viper.SetDefault("conversion.max_nodes", 0)
cm.viper.SetDefault("conversion.cache_timeout", 60)
cm.viper.SetDefault("conversion.supported_targets", []string{
"clash", "surge", "quanx", "loon", "surfboard", "v2ray",
})
}
// bindEnvironmentVariables 绑定环境变量
func (cm *ConfigManager) bindEnvironmentVariables() {
// 服务器配置
cm.viper.BindEnv("server.host", "SUBCONVERTER_HOST")
cm.viper.BindEnv("server.port", "SUBCONVERTER_PORT")
cm.viper.BindEnv("server.read_timeout", "SUBCONVERTER_READ_TIMEOUT")
cm.viper.BindEnv("server.write_timeout", "SUBCONVERTER_WRITE_TIMEOUT")
cm.viper.BindEnv("server.max_request_size", "SUBCONVERTER_MAX_REQUEST_SIZE")
// 日志配置
cm.viper.BindEnv("logging.level", "SUBCONVERTER_LOG_LEVEL")
cm.viper.BindEnv("logging.format", "SUBCONVERTER_LOG_FORMAT")
cm.viper.BindEnv("logging.output", "SUBCONVERTER_LOG_OUTPUT")
cm.viper.BindEnv("logging.file", "SUBCONVERTER_LOG_FILE")
// 安全配置
cm.viper.BindEnv("security.access_tokens", "SUBCONVERTER_ACCESS_TOKENS")
cm.viper.BindEnv("security.cors_origins", "SUBCONVERTER_CORS_ORIGINS")
cm.viper.BindEnv("security.rate_limit", "SUBCONVERTER_RATE_LIMIT")
cm.viper.BindEnv("security.timeout", "SUBCONVERTER_TIMEOUT")
// 转换配置
cm.viper.BindEnv("conversion.default_target", "SUBCONVERTER_DEFAULT_TARGET")
cm.viper.BindEnv("conversion.default_emoji", "SUBCONVERTER_DEFAULT_EMOJI")
cm.viper.BindEnv("conversion.default_udp", "SUBCONVERTER_DEFAULT_UDP")
cm.viper.BindEnv("conversion.max_nodes", "SUBCONVERTER_MAX_NODES")
cm.viper.BindEnv("conversion.cache_timeout", "SUBCONVERTER_CACHE_TIMEOUT")
}
// parseConfig 解析配置到结构体
func (cm *ConfigManager) parseConfig() error {
cm.config = &Configuration{}
// 手动解析配置因为viper的直接映射可能不适用复杂结构
if err := cm.viper.Unmarshal(cm.config); err != nil {
return fmt.Errorf("failed to unmarshal config: %v", err)
}
// 后处理配置
cm.postProcessConfig()
return nil
}
// postProcessConfig 后处理配置
func (cm *ConfigManager) postProcessConfig() {
// 处理安全配置
if cm.config.Security.CorsOrigins == nil {
cm.config.Security.CorsOrigins = []string{"*"}
}
// 处理转换配置
if cm.config.Conversion.SupportedTargets == nil {
cm.config.Conversion.SupportedTargets = []string{
"clash", "clashr", "surge", "quanx", "loon", "surfboard", "v2ray",
}
}
}
// GetConfig 获取配置
// 返回当前配置的副本
func (cm *ConfigManager) GetConfig() *Configuration {
if cm.config == nil {
return nil
}
return cm.config.Clone()
}
// GetServerConfig 获取服务器配置
func (cm *ConfigManager) GetServerConfig() *ServerConfig {
if cm.config == nil {
return nil
}
clone := cm.config.Server.Clone()
return &clone
}
// GetLoggingConfig 获取日志配置
func (cm *ConfigManager) GetLoggingConfig() *LoggingConfig {
if cm.config == nil {
return nil
}
clone := cm.config.Logging.Clone()
return &clone
}
// GetSecurityConfig 获取安全配置
func (cm *ConfigManager) GetSecurityConfig() *SecurityConfig {
if cm.config == nil {
return nil
}
clone := cm.config.Security.Clone()
return &clone
}
// GetConversionConfig 获取转换配置
func (cm *ConfigManager) GetConversionConfig() *ConversionConfig {
if cm.config == nil {
return nil
}
clone := cm.config.Conversion.Clone()
return &clone
}
// UpdateConfig 更新配置
func (cm *ConfigManager) UpdateConfig(config *Configuration) error {
if config == nil {
return fmt.Errorf("config cannot be nil")
}
// 验证配置
if err := config.Validate(); err != nil {
return fmt.Errorf("invalid config: %v", err)
}
// 更新配置
cm.config = config.Clone()
// 同步到viper
cm.syncToViper()
// 保存到文件(如果配置了文件路径)
if cm.filePath != "" {
if err := cm.SaveConfig(); err != nil {
cm.logger.WithError(err).Warn("Failed to save config to file")
}
}
return nil
}
// syncToViper 将配置同步到viper
func (cm *ConfigManager) syncToViper() {
// 使用反射同步配置
v := reflect.ValueOf(cm.config).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
// 获取字段名考虑yaml标签
fieldName := getFieldName(fieldType)
// 递归设置嵌套结构
cm.setViperValue(cm.viper, fieldName, field)
}
}
// setViperValue 递归设置viper值
func (cm *ConfigManager) setViperValue(v *viper.Viper, key string, value reflect.Value) {
switch value.Kind() {
case reflect.Struct:
// 处理嵌套结构
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fieldType := value.Type().Field(i)
subKey := key + "." + getFieldName(fieldType)
cm.setViperValue(v, subKey, field)
}
case reflect.Slice, reflect.Array:
// 处理切片和数组
if value.Len() > 0 {
slice := make([]interface{}, value.Len())
for i := 0; i < value.Len(); i++ {
slice[i] = value.Index(i).Interface()
}
v.Set(key, slice)
}
case reflect.Map:
// 处理map
mapValue := make(map[string]interface{})
for _, key := range value.MapKeys() {
mapValue[key.String()] = value.MapIndex(key).Interface()
}
v.Set(key, mapValue)
default:
// 处理基本类型
v.Set(key, value.Interface())
}
}
// getFieldName 获取字段名
func getFieldName(field reflect.StructField) string {
// 优先使用yaml标签
if yamlTag := field.Tag.Get("yaml"); yamlTag != "" {
if yamlTag != "-" {
return strings.Split(yamlTag, ",")[0]
}
}
// 其次使用json标签
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
if jsonTag != "-" {
return strings.Split(jsonTag, ",")[0]
}
}
// 最后使用字段名
return strings.ToLower(field.Name)
}
// SaveConfig 保存配置到文件
func (cm *ConfigManager) SaveConfig() error {
if cm.filePath == "" {
return fmt.Errorf("no config file path configured")
}
// 确保目录存在
dir := filepath.Dir(cm.filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
// 设置配置文件路径
cm.viper.SetConfigFile(cm.filePath)
// 保存配置
if err := cm.viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to write config: %v", err)
}
cm.logger.Infof("Configuration saved to: %s", cm.filePath)
return nil
}
// ReloadConfig 从磁盘重新加载配置
func (cm *ConfigManager) ReloadConfig() error {
if cm.filePath == "" {
return fmt.Errorf("no config file path configured")
}
cm.logger.Infof("Reloading configuration from: %s", cm.filePath)
cm.viper.SetConfigFile(cm.filePath)
if err := cm.viper.ReadInConfig(); err != nil {
return fmt.Errorf("failed to read config file: %v", err)
}
if err := cm.parseConfig(); err != nil {
return fmt.Errorf("failed to parse config: %v", err)
}
return nil
}
// WatchConfig 监听配置文件变化
func (cm *ConfigManager) WatchConfig(callback func(*Configuration)) {
cm.viper.WatchConfig()
cm.viper.OnConfigChange(func(e fsnotify.Event) {
cm.logger.Infof("Configuration file changed: %s", e.Name)
// 重新解析配置
if err := cm.parseConfig(); err != nil {
cm.logger.WithError(err).Error("Failed to parse changed config")
return
}
// 调用回调函数
if callback != nil {
callback(cm.config)
}
})
}
// GetString 获取字符串配置值
func (cm *ConfigManager) GetString(key string) string {
return cm.viper.GetString(key)
}
// GetInt 获取整型配置值
func (cm *ConfigManager) GetInt(key string) int {
return cm.viper.GetInt(key)
}
// GetBool 获取布尔配置值
func (cm *ConfigManager) GetBool(key string) bool {
return cm.viper.GetBool(key)
}
// GetStringSlice 获取字符串切片配置值
func (cm *ConfigManager) GetStringSlice(key string) []string {
return cm.viper.GetStringSlice(key)
}
// IsSet 检查配置是否设置
func (cm *ConfigManager) IsSet(key string) bool {
return cm.viper.IsSet(key)
}
// Set 设置配置值
func (cm *ConfigManager) Set(key string, value interface{}) {
cm.viper.Set(key, value)
}
// GetFilePath 获取配置文件路径
func (cm *ConfigManager) GetFilePath() string {
return cm.filePath
}
// Close 关闭配置管理器
func (cm *ConfigManager) Close() error {
// 停止监听配置变化
cm.viper.OnConfigChange(nil)
// 保存配置(如果需要)
if cm.filePath != "" {
if err := cm.SaveConfig(); err != nil {
return err
}
}
return nil
}
// GetTemplateManager 获取模板管理器
func (cm *ConfigManager) GetTemplateManager() *TemplateManager {
return cm.templateManager
}
// GetBasePath 获取base路径
func (cm *ConfigManager) GetBasePath() string {
return cm.basePath
}
// HasTemplateManager 检查是否有模板管理器
func (cm *ConfigManager) HasTemplateManager() bool {
return cm.templateManager != nil
}
// ReloadTemplates 重新加载模板
func (cm *ConfigManager) ReloadTemplates() error {
if cm.templateManager == nil {
return fmt.Errorf("template manager not initialized")
}
return cm.templateManager.Reload()
}

449
internal/config/model.go Normal file
View File

@@ -0,0 +1,449 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
// Configuration 表示应用程序的完整配置
// 该结构体包含服务器、日志、安全和转换相关的所有配置项
type Configuration struct {
// ServerConfig 服务器配置
Server ServerConfig `yaml:"server" json:"server"`
// LoggingConfig 日志配置
Logging LoggingConfig `yaml:"logging" json:"logging"`
// SecurityConfig 安全配置
Security SecurityConfig `yaml:"security" json:"security"`
// ConversionConfig 转换配置
Conversion ConversionConfig `yaml:"conversion" json:"conversion"`
}
// ServerConfig 服务器相关配置
type ServerConfig struct {
// Port 服务器监听端口默认25500
Port int `yaml:"port" json:"port" env:"SUBCONVERTER_PORT" mapstructure:"port"`
// Host 服务器监听地址,默认"0.0.0.0"
Host string `yaml:"host" json:"host" env:"SUBCONVERTER_HOST" mapstructure:"host"`
// ReadTimeout 读取超时时间默认30
ReadTimeout int `yaml:"read_timeout" json:"read_timeout" env:"SUBCONVERTER_READ_TIMEOUT" mapstructure:"read_timeout"`
// WriteTimeout 写入超时时间默认30
WriteTimeout int `yaml:"write_timeout" json:"write_timeout" env:"SUBCONVERTER_WRITE_TIMEOUT" mapstructure:"write_timeout"`
// MaxRequestSize 最大请求大小字节默认10MB
MaxRequestSize int64 `yaml:"max_request_size" json:"max_request_size" env:"SUBCONVERTER_MAX_REQUEST_SIZE" mapstructure:"max_request_size"`
}
// Clone 创建服务器配置的深拷贝
func (s *ServerConfig) Clone() ServerConfig {
if s == nil {
return ServerConfig{}
}
return ServerConfig{
Port: s.Port,
Host: s.Host,
ReadTimeout: s.ReadTimeout,
WriteTimeout: s.WriteTimeout,
MaxRequestSize: s.MaxRequestSize,
}
}
// LoggingConfig 日志相关配置
type LoggingConfig struct {
// Level 日志级别可选debug, info, warn, error默认info
Level string `yaml:"level" json:"level" env:"SUBCONVERTER_LOG_LEVEL" mapstructure:"level"`
// Format 日志格式可选json, text默认json
Format string `yaml:"format" json:"format" env:"SUBCONVERTER_LOG_FORMAT" mapstructure:"format"`
// Output 日志输出可选stdout, stderr, file默认stdout
Output string `yaml:"output" json:"output" env:"SUBCONVERTER_LOG_OUTPUT" mapstructure:"output"`
// File 如果Output为file指定日志文件路径
File string `yaml:"file" json:"file" env:"SUBCONVERTER_LOG_FILE" mapstructure:"file"`
}
// Clone 创建日志配置的深拷贝
func (l *LoggingConfig) Clone() LoggingConfig {
if l == nil {
return LoggingConfig{}
}
return LoggingConfig{
Level: l.Level,
Format: l.Format,
Output: l.Output,
File: l.File,
}
}
// SecurityConfig 安全相关配置
type SecurityConfig struct {
// AccessTokens 访问令牌列表,空列表表示无需认证
AccessTokens []string `yaml:"access_tokens" json:"access_tokens" env:"SUBCONVERTER_ACCESS_TOKENS" mapstructure:"access_tokens"`
// CorsOrigins CORS允许的源列表支持通配符*
CorsOrigins []string `yaml:"cors_origins" json:"cors_origins" env:"SUBCONVERTER_CORS_ORIGINS" mapstructure:"cors_origins"`
// RateLimit 速率限制每分钟请求数0表示不限制
RateLimit int `yaml:"rate_limit" json:"rate_limit" env:"SUBCONVERTER_RATE_LIMIT" mapstructure:"rate_limit"`
// Timeout 请求超时时间默认60
Timeout int `yaml:"timeout" json:"timeout" env:"SUBCONVERTER_TIMEOUT" mapstructure:"timeout"`
}
// Clone 创建安全配置的深拷贝
func (s *SecurityConfig) Clone() SecurityConfig {
if s == nil {
return SecurityConfig{}
}
// 深拷贝切片
accessTokens := make([]string, len(s.AccessTokens))
copy(accessTokens, s.AccessTokens)
corsOrigins := make([]string, len(s.CorsOrigins))
copy(corsOrigins, s.CorsOrigins)
return SecurityConfig{
AccessTokens: accessTokens,
CorsOrigins: corsOrigins,
RateLimit: s.RateLimit,
Timeout: s.Timeout,
}
}
// ConversionConfig 转换相关配置
type ConversionConfig struct {
// DefaultTarget 默认目标格式默认clash
DefaultTarget string `yaml:"default_target" json:"default_target" env:"SUBCONVERTER_DEFAULT_TARGET" mapstructure:"default_target"`
// SupportedTargets 支持的目标格式列表
SupportedTargets []string `yaml:"supported_targets" json:"supported_targets" mapstructure:"supported_targets"`
// DefaultEmoji 默认是否启用emoji默认false
DefaultEmoji bool `yaml:"default_emoji" json:"default_emoji" env:"SUBCONVERTER_DEFAULT_EMOJI" mapstructure:"default_emoji"`
// DefaultUDP 默认是否启用UDP默认false
DefaultUDP bool `yaml:"default_udp" json:"default_udp" env:"SUBCONVERTER_DEFAULT_UDP" mapstructure:"default_udp"`
// MaxNodes 最大节点数量默认0无限制
MaxNodes int `yaml:"max_nodes" json:"max_nodes" env:"SUBCONVERTER_MAX_NODES" mapstructure:"max_nodes"`
// CacheTimeout 缓存超时时间分钟默认60
CacheTimeout int `yaml:"cache_timeout" json:"cache_timeout" env:"SUBCONVERTER_CACHE_TIMEOUT" mapstructure:"cache_timeout"`
}
// Clone 创建转换配置的深拷贝
func (c *ConversionConfig) Clone() ConversionConfig {
if c == nil {
return ConversionConfig{}
}
// 深拷贝切片
supportedTargets := make([]string, len(c.SupportedTargets))
copy(supportedTargets, c.SupportedTargets)
return ConversionConfig{
DefaultTarget: c.DefaultTarget,
SupportedTargets: supportedTargets,
DefaultEmoji: c.DefaultEmoji,
DefaultUDP: c.DefaultUDP,
MaxNodes: c.MaxNodes,
CacheTimeout: c.CacheTimeout,
}
}
// NewConfiguration 创建新的默认配置
// 返回包含所有默认值的Configuration结构体
func NewConfiguration() *Configuration {
return &Configuration{
Server: ServerConfig{
Port: 25500,
Host: "0.0.0.0",
ReadTimeout: 30,
WriteTimeout: 30,
MaxRequestSize: 10 * 1024 * 1024, // 10MB
},
Logging: LoggingConfig{
Level: "info",
Format: "json",
Output: "stdout",
File: "",
},
Security: SecurityConfig{
AccessTokens: []string{},
CorsOrigins: []string{"*"},
RateLimit: 0,
Timeout: 60,
},
Conversion: ConversionConfig{
DefaultTarget: "clash",
SupportedTargets: []string{"clash", "clashr", "surge", "quanx", "loon", "surfboard", "v2ray"},
DefaultEmoji: false,
DefaultUDP: false,
MaxNodes: 0,
CacheTimeout: 60,
},
}
}
// Validate 验证配置的有效性
// 返回error如果配置无效nil表示配置有效
func (c *Configuration) Validate() error {
// 验证服务器配置
if c.Server.Port < 1 || c.Server.Port > 65535 {
return fmt.Errorf("server port must be between 1 and 65535, got: %d", c.Server.Port)
}
if c.Server.Host == "" {
return fmt.Errorf("server host cannot be empty")
}
if c.Server.ReadTimeout < 1 {
return fmt.Errorf("read timeout must be positive, got: %d", c.Server.ReadTimeout)
}
if c.Server.WriteTimeout < 1 {
return fmt.Errorf("write timeout must be positive, got: %d", c.Server.WriteTimeout)
}
if c.Server.MaxRequestSize < 1 {
return fmt.Errorf("max request size must be positive, got: %d", c.Server.MaxRequestSize)
}
// 验证日志配置
validLogLevels := map[string]bool{
"debug": true,
"info": true,
"warn": true,
"error": true,
}
if !validLogLevels[c.Logging.Level] {
return fmt.Errorf("log level must be one of: debug, info, warn, error, got: %s", c.Logging.Level)
}
validLogFormats := map[string]bool{
"json": true,
"text": true,
}
if !validLogFormats[c.Logging.Format] {
return fmt.Errorf("log format must be one of: json, text, got: %s", c.Logging.Format)
}
validOutputs := map[string]bool{
"stdout": true,
"stderr": true,
"file": true,
}
if !validOutputs[c.Logging.Output] {
return fmt.Errorf("log output must be one of: stdout, stderr, file, got: %s", c.Logging.Output)
}
if c.Logging.Output == "file" && c.Logging.File == "" {
return fmt.Errorf("log file path must be specified when output is file")
}
// 验证安全配置
if c.Security.Timeout < 1 {
return fmt.Errorf("timeout must be positive, got: %d", c.Security.Timeout)
}
if c.Security.RateLimit < 0 {
return fmt.Errorf("rate limit must be non-negative, got: %d", c.Security.RateLimit)
}
// 验证转换配置
if !contains(c.Conversion.SupportedTargets, c.Conversion.DefaultTarget) {
return fmt.Errorf("default target must be in supported targets, got: %s", c.Conversion.DefaultTarget)
}
if c.Conversion.MaxNodes < 0 {
return fmt.Errorf("max nodes must be non-negative, got: %d", c.Conversion.MaxNodes)
}
if c.Conversion.CacheTimeout < 0 {
return fmt.Errorf("cache timeout must be non-negative, got: %d", c.Conversion.CacheTimeout)
}
return nil
}
// Clone 创建配置的深拷贝
// 返回配置的独立副本
func (c *Configuration) Clone() *Configuration {
if c == nil {
return nil
}
clone := &Configuration{
Server: c.Server.Clone(),
Logging: c.Logging.Clone(),
Security: c.Security.Clone(),
Conversion: c.Conversion.Clone(),
}
return clone
}
// LoadFromEnvironment 从环境变量加载配置
// 根据环境变量覆盖默认配置值
func (c *Configuration) LoadFromEnvironment() {
// 服务器配置
if port := os.Getenv("SUBCONVERTER_PORT"); port != "" {
if p, err := strconv.Atoi(port); err == nil {
c.Server.Port = p
}
}
if host := os.Getenv("SUBCONVERTER_HOST"); host != "" {
c.Server.Host = host
}
if readTimeout := os.Getenv("SUBCONVERTER_READ_TIMEOUT"); readTimeout != "" {
if rt, err := strconv.Atoi(readTimeout); err == nil {
c.Server.ReadTimeout = rt
}
}
if writeTimeout := os.Getenv("SUBCONVERTER_WRITE_TIMEOUT"); writeTimeout != "" {
if wt, err := strconv.Atoi(writeTimeout); err == nil {
c.Server.WriteTimeout = wt
}
}
// 日志配置
if logLevel := os.Getenv("SUBCONVERTER_LOG_LEVEL"); logLevel != "" {
c.Logging.Level = logLevel
}
if logFormat := os.Getenv("SUBCONVERTER_LOG_FORMAT"); logFormat != "" {
c.Logging.Format = logFormat
}
if logOutput := os.Getenv("SUBCONVERTER_LOG_OUTPUT"); logOutput != "" {
c.Logging.Output = logOutput
}
if logFile := os.Getenv("SUBCONVERTER_LOG_FILE"); logFile != "" {
c.Logging.File = logFile
}
// 安全配置
if accessTokens := os.Getenv("SUBCONVERTER_ACCESS_TOKENS"); accessTokens != "" {
c.Security.AccessTokens = strings.Split(accessTokens, ",")
}
if corsOrigins := os.Getenv("SUBCONVERTER_CORS_ORIGINS"); corsOrigins != "" {
c.Security.CorsOrigins = strings.Split(corsOrigins, ",")
}
if rateLimit := os.Getenv("SUBCONVERTER_RATE_LIMIT"); rateLimit != "" {
if rl, err := strconv.Atoi(rateLimit); err == nil {
c.Security.RateLimit = rl
}
}
if timeout := os.Getenv("SUBCONVERTER_TIMEOUT"); timeout != "" {
if t, err := strconv.Atoi(timeout); err == nil {
c.Security.Timeout = t
}
}
// 转换配置
if defaultTarget := os.Getenv("SUBCONVERTER_DEFAULT_TARGET"); defaultTarget != "" {
c.Conversion.DefaultTarget = defaultTarget
}
if defaultEmoji := os.Getenv("SUBCONVERTER_DEFAULT_EMOJI"); defaultEmoji != "" {
if de, err := strconv.ParseBool(defaultEmoji); err == nil {
c.Conversion.DefaultEmoji = de
}
}
if defaultUDP := os.Getenv("SUBCONVERTER_DEFAULT_UDP"); defaultUDP != "" {
if du, err := strconv.ParseBool(defaultUDP); err == nil {
c.Conversion.DefaultUDP = du
}
}
if maxNodes := os.Getenv("SUBCONVERTER_MAX_NODES"); maxNodes != "" {
if mn, err := strconv.Atoi(maxNodes); err == nil {
c.Conversion.MaxNodes = mn
}
}
if cacheTimeout := os.Getenv("SUBCONVERTER_CACHE_TIMEOUT"); cacheTimeout != "" {
if ct, err := strconv.Atoi(cacheTimeout); err == nil {
c.Conversion.CacheTimeout = ct
}
}
}
// GetServerAddress 返回服务器地址字符串
// 格式为 "host:port"
func (c *Configuration) GetServerAddress() string {
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
}
// GetReadTimeoutDuration 返回读取超时时间duration
func (c *Configuration) GetReadTimeoutDuration() time.Duration {
return time.Duration(c.Server.ReadTimeout) * time.Second
}
// GetWriteTimeoutDuration 返回写入超时时间duration
func (c *Configuration) GetWriteTimeoutDuration() time.Duration {
return time.Duration(c.Server.WriteTimeout) * time.Second
}
// GetTimeoutDuration 返回请求超时时间duration
func (c *Configuration) GetTimeoutDuration() time.Duration {
return time.Duration(c.Security.Timeout) * time.Second
}
// GetCacheTimeoutDuration 返回缓存超时时间duration
func (c *Configuration) GetCacheTimeoutDuration() time.Duration {
return time.Duration(c.Conversion.CacheTimeout) * time.Minute
}
// IsAccessTokenValid 检查访问令牌是否有效
func (c *Configuration) IsAccessTokenValid(token string) bool {
if len(c.Security.AccessTokens) == 0 {
return true // 空列表表示无需认证
}
return contains(c.Security.AccessTokens, token)
}
// IsCorsOriginAllowed 检查CORS源是否允许
func (c *Configuration) IsCorsOriginAllowed(origin string) bool {
for _, allowed := range c.Security.CorsOrigins {
if allowed == "*" || allowed == origin {
return true
}
}
return false
}
// IsTargetSupported 检查目标格式是否支持
func (c *Configuration) IsTargetSupported(target string) bool {
return contains(c.Conversion.SupportedTargets, target)
}
// contains 检查字符串切片是否包含特定字符串
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}

511
internal/config/proxy.go Normal file
View File

@@ -0,0 +1,511 @@
package config
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
)
// ProxyConfig 表示代理节点的配置
// 该结构体包含各种代理协议的通用和特定配置
type ProxyConfig struct {
// 基本信息
Name string `yaml:"name" json:"name"`
Type string `yaml:"type" json:"type"` // ss, ssr, vmess, trojan, http, socks5
// 服务器信息
Server string `yaml:"server" json:"server"`
Port int `yaml:"port" json:"port"`
Password string `yaml:"password" json:"password,omitempty"`
// 加密和协议
Method string `yaml:"method" json:"method,omitempty"` // ss, ssr
Protocol string `yaml:"protocol" json:"protocol,omitempty"` // ssr
Obfs string `yaml:"obfs" json:"obfs,omitempty"` // ssr
ObfsParam string `yaml:"obfs_param" json:"obfs_param,omitempty"` // ssr
SSRProtocol string `yaml:"ssr_protocol" json:"ssr_protocol,omitempty"` // ssr (兼容性字段)
// VMess特定配置
VMessConfig *VMessConfig `yaml:"vmess_config,omitempty" json:"vmess_config,omitempty"`
// TLS配置
TLS bool `yaml:"tls" json:"tls"`
SNI string `yaml:"sni" json:"sni,omitempty"`
Alpn string `yaml:"alpn" json:"alpn,omitempty"`
SkipVerify bool `yaml:"skip_verify" json:"skip_verify"`
// WebSocket配置
WSPath string `yaml:"ws_path" json:"ws_path,omitempty"`
WSHeaders map[string]string `yaml:"ws_headers" json:"ws_headers,omitempty"`
// HTTP/2配置
H2Host string `yaml:"h2_host" json:"h2_host,omitempty"`
H2Path string `yaml:"h2_path" json:"h2_path,omitempty"`
// QUIC配置
QUICSecurity string `yaml:"quic_security" json:"quic_security,omitempty"`
QUICKey string `yaml:"quic_key" json:"quic_key,omitempty"`
// gRPC配置
GRPCServiceName string `yaml:"grpc_service_name" json:"grpc_service_name,omitempty"`
GRPCMultiMode bool `yaml:"grpc_multi_mode" json:"grpc_multi_mode,omitempty"`
// 其他配置
UDP bool `yaml:"udp" json:"udp"`
TFO bool `yaml:"tfo" json:"tfo"` // TCP Fast Open
Mux bool `yaml:"mux" json:"mux"` // 多路复用
Scramble bool `yaml:"scramble" json:"scramble"`
Plugin string `yaml:"plugin" json:"plugin,omitempty"`
PluginOpts string `yaml:"plugin_opts" json:"plugin_opts,omitempty"`
// 节点属性
Location string `yaml:"location" json:"location,omitempty"`
Provider string `yaml:"provider" json:"provider,omitempty"`
Ping int `yaml:"ping" json:"ping,omitempty"` // 延迟(ms)
}
// VMessConfig VMess协议特定配置
type VMessConfig struct {
VMessUUID string `yaml:"vmess_uuid" json:"vmess_uuid"`
VMessAlterID string `yaml:"vmess_alter_id" json:"vmess_alter_id"`
VMessSecurity string `yaml:"vmess_security" json:"vmess_security"`
}
// NewProxyConfig 创建新的代理配置
// 返回包含基本默认值的ProxyConfig结构体
func NewProxyConfig() *ProxyConfig {
return &ProxyConfig{
Name: "",
Type: "",
Server: "",
Port: 0,
Password: "",
Method: "",
Protocol: "",
Obfs: "",
ObfsParam: "",
TLS: false,
SNI: "",
Alpn: "",
SkipVerify: false,
WSPath: "",
WSHeaders: make(map[string]string),
H2Host: "",
H2Path: "",
QUICSecurity: "",
QUICKey: "",
GRPCServiceName: "",
GRPCMultiMode: false,
UDP: false,
TFO: false,
Mux: false,
Scramble: false,
Plugin: "",
PluginOpts: "",
Location: "",
Provider: "",
Ping: 0,
}
}
// Validate 验证代理配置的有效性
// 返回error如果配置无效nil表示配置有效
func (p *ProxyConfig) Validate() error {
// 基本字段验证
if p.Name == "" {
return fmt.Errorf("proxy name cannot be empty")
}
if p.Type == "" {
return fmt.Errorf("proxy type cannot be empty")
}
if p.Server == "" {
return fmt.Errorf("proxy server cannot be empty")
}
if p.Port < 1 || p.Port > 65535 {
return fmt.Errorf("proxy port must be between 1 and 65535, got: %d", p.Port)
}
// 根据类型验证特定字段
switch strings.ToLower(p.Type) {
case "ss", "shadowsocks":
if p.Password == "" {
return fmt.Errorf("shadowsocks password cannot be empty")
}
if p.Method == "" {
return fmt.Errorf("shadowsocks method cannot be empty")
}
case "ssr", "shadowsocksr":
if p.Password == "" {
return fmt.Errorf("shadowsocksr password cannot be empty")
}
if p.Method == "" {
return fmt.Errorf("shadowsocksr method cannot be empty")
}
if p.Protocol == "" {
return fmt.Errorf("shadowsocksr protocol cannot be empty")
}
case "vmess":
if p.VMessConfig == nil || p.VMessConfig.VMessUUID == "" {
return fmt.Errorf("vmess uuid cannot be empty")
}
if p.VMessConfig.VMessAlterID == "" {
return fmt.Errorf("vmess alter id cannot be empty")
}
if p.VMessConfig.VMessSecurity == "" {
return fmt.Errorf("vmess security cannot be empty")
}
case "trojan":
if p.Password == "" {
return fmt.Errorf("trojan password cannot be empty")
}
case "http", "https":
if p.Password == "" && p.Type == "https" {
return fmt.Errorf("https proxy requires authentication")
}
case "socks5":
// SOCKS5可以无认证
default:
return fmt.Errorf("unsupported proxy type: %s", p.Type)
}
// TLS配置验证
if p.TLS {
if p.SNI == "" {
p.SNI = p.Server // 默认使用服务器地址作为SNI
}
}
// WebSocket配置验证
if p.WSPath != "" && p.WSHeaders == nil {
p.WSHeaders = make(map[string]string)
}
// HTTP/2配置验证
if p.H2Path != "" && p.H2Host == "" {
return fmt.Errorf("h2 host cannot be empty when h2 path is specified")
}
// QUIC配置验证
if p.QUICKey != "" && p.QUICSecurity == "" {
return fmt.Errorf("quic security cannot be empty when quic key is specified")
}
// gRPC配置验证
if p.GRPCServiceName != "" && !p.TLS {
return fmt.Errorf("grpc requires tls to be enabled")
}
return nil
}
// ToURL 将代理配置转换为URL格式
// 返回代理URL字符串适用于分享和导入
func (p *ProxyConfig) ToURL() (string, error) {
switch strings.ToLower(p.Type) {
case "ss", "shadowsocks":
return p.toSSURL()
case "ssr", "shadowsocksr":
return p.toSSRURL()
case "vmess":
return p.toVMessURL()
case "trojan":
return p.toTrojanURL()
case "http", "https":
return p.toHTTPURL()
case "socks5":
return p.toSocks5URL()
default:
return "", fmt.Errorf("unsupported proxy type: %s", p.Type)
}
}
// FromURL 从URL解析代理配置
// 根据URL类型自动识别并解析代理配置
func (p *ProxyConfig) FromURL(proxyURL string) error {
u, err := url.Parse(proxyURL)
if err != nil {
return fmt.Errorf("failed to parse proxy URL: %v", err)
}
switch u.Scheme {
case "ss":
return p.fromSSURL(u)
case "ssr":
return p.fromSSRURL(u)
case "vmess":
return p.fromVMessURL(u)
case "trojan":
return p.fromTrojanURL(u)
case "http", "https":
return p.fromHTTPURL(u)
case "socks5":
return p.fromSocks5URL(u)
default:
return fmt.Errorf("unsupported proxy URL scheme: %s", u.Scheme)
}
}
// toSSURL 转换为SS URL格式
func (p *ProxyConfig) toSSURL() (string, error) {
if p.Type != "ss" && p.Type != "shadowsocks" {
return "", fmt.Errorf("not a shadowsocks proxy")
}
// SS URL格式: ss://method:password@server:port#name
host := fmt.Sprintf("%s:%d", p.Server, p.Port)
u := &url.URL{
Scheme: "ss",
User: url.UserPassword(p.Method, p.Password),
Host: host,
}
if p.Name != "" {
u.Fragment = p.Name
}
return u.String(), nil
}
// toSSRURL 转换为SSR URL格式
func (p *ProxyConfig) toSSRURL() (string, error) {
if p.Type != "ssr" && p.Type != "shadowsocksr" {
return "", fmt.Errorf("not a shadowsocksr proxy")
}
// SSR URL格式: ssr://base64(server:port:protocol:method:obfs:password/?obfsparam=...&protoparam=...&remarks=...)
params := make(url.Values)
params.Set("obfsparam", p.ObfsParam)
params.Set("protoparam", "")
params.Set("remarks", p.Name)
params.Set("group", "")
params.Set("udpport", "0")
params.Set("uot", "0")
baseInfo := fmt.Sprintf("%s:%d:%s:%s:%s:%s",
p.Server, p.Port, p.Protocol, p.Method, p.Obfs, p.Password)
encodedInfo := url.QueryEscape(base64Encode(baseInfo))
encodedParams := url.QueryEscape(params.Encode())
return fmt.Sprintf("ssr://%s/?%s", encodedInfo, encodedParams), nil
}
// toVMessURL 转换为VMess URL格式
func (p *ProxyConfig) toVMessURL() (string, error) {
if p.Type != "vmess" {
return "", fmt.Errorf("not a vmess proxy")
}
// VMess URL格式: vmess://base64(json_config)
config := map[string]interface{}{
"v": "2",
"ps": p.Name,
"add": p.Server,
"port": p.Port,
"id": p.VMessConfig.VMessUUID,
"aid": p.VMessConfig.VMessAlterID,
"net": "tcp", // 简化版本
"type": "none",
"host": "",
"path": "",
"tls": "",
}
if p.TLS {
config["tls"] = "tls"
}
configJSON, err := json.Marshal(config)
if err != nil {
return "", fmt.Errorf("failed to marshal vmess config: %v", err)
}
return "vmess://" + base64Encode(string(configJSON)), nil
}
// toTrojanURL 转换为Trojan URL格式
func (p *ProxyConfig) toTrojanURL() (string, error) {
if p.Type != "trojan" {
return "", fmt.Errorf("not a trojan proxy")
}
// Trojan URL格式: trojan://password@server:port?allowinsecure=1#name
u := &url.URL{
Scheme: "trojan",
User: url.UserPassword(p.Password, ""),
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
}
params := u.Query()
if p.SkipVerify {
params.Set("allowinsecure", "1")
}
if p.SNI != "" {
params.Set("sni", p.SNI)
}
u.RawQuery = params.Encode()
if p.Name != "" {
u.Fragment = p.Name
}
return u.String(), nil
}
// toHTTPURL 转换为HTTP/HTTPS URL格式
func (p *ProxyConfig) toHTTPURL() (string, error) {
if p.Type != "http" && p.Type != "https" {
return "", fmt.Errorf("not an http proxy")
}
scheme := "http"
if p.Type == "https" {
scheme = "https"
}
u := &url.URL{
Scheme: scheme,
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
}
if p.Password != "" {
u.User = url.UserPassword("", p.Password)
}
if p.Name != "" {
u.Fragment = p.Name
}
return u.String(), nil
}
// toSocks5URL 转换为SOCKS5 URL格式
func (p *ProxyConfig) toSocks5URL() (string, error) {
if p.Type != "socks5" {
return "", fmt.Errorf("not a socks5 proxy")
}
u := &url.URL{
Scheme: "socks5",
Host: fmt.Sprintf("%s:%d", p.Server, p.Port),
}
if p.Password != "" {
u.User = url.UserPassword("", p.Password)
}
if p.Name != "" {
u.Fragment = p.Name
}
return u.String(), nil
}
// 以下是FromURL的各种实现方法由于篇幅限制这里只提供框架
func (p *ProxyConfig) fromSSURL(u *url.URL) error {
// 解析SS URL的实现
p.Type = "ss"
p.Server = u.Hostname()
if port, err := strconv.Atoi(u.Port()); err == nil {
p.Port = port
}
p.Name = u.Fragment
if u.User != nil {
p.Method = u.User.Username()
if password, ok := u.User.Password(); ok {
p.Password = password
}
}
return p.Validate()
}
func (p *ProxyConfig) fromSSRURL(u *url.URL) error {
// 解析SSR URL的实现
p.Type = "ssr"
// 实现SSR URL解析逻辑
return p.Validate()
}
func (p *ProxyConfig) fromVMessURL(u *url.URL) error {
// 解析VMess URL的实现
p.Type = "vmess"
// 实现VMess URL解析逻辑
return p.Validate()
}
func (p *ProxyConfig) fromTrojanURL(u *url.URL) error {
// 解析Trojan URL的实现
p.Type = "trojan"
p.Server = u.Hostname()
if port, err := strconv.Atoi(u.Port()); err == nil {
p.Port = port
}
p.Name = u.Fragment
if u.User != nil {
p.Password = u.User.Username()
}
return p.Validate()
}
func (p *ProxyConfig) fromHTTPURL(u *url.URL) error {
// 解析HTTP URL的实现
p.Type = u.Scheme
p.Server = u.Hostname()
if port, err := strconv.Atoi(u.Port()); err == nil {
p.Port = port
}
p.Name = u.Fragment
if u.User != nil {
if password, ok := u.User.Password(); ok {
p.Password = password
}
}
return p.Validate()
}
func (p *ProxyConfig) fromSocks5URL(u *url.URL) error {
// 解析SOCKS5 URL的实现
p.Type = "socks5"
p.Server = u.Hostname()
if port, err := strconv.Atoi(u.Port()); err == nil {
p.Port = port
}
p.Name = u.Fragment
if u.User != nil {
if password, ok := u.User.Password(); ok {
p.Password = password
}
}
return p.Validate()
}
// 辅助函数
func base64Encode(s string) string {
return base64.StdEncoding.EncodeToString([]byte(s))
}
func base64Decode(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
return string(data), err
}

View File

@@ -0,0 +1,599 @@
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
}