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
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:
449
internal/config/model.go
Normal file
449
internal/config/model.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user