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

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
}