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
450 lines
13 KiB
Go
450 lines
13 KiB
Go
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
|
||
}
|