config.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package config
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/spf13/viper"
  6. )
  7. type Config struct {
  8. Server ServerConfig
  9. MySQL MySQLConfig
  10. Redis RedisConfig
  11. Telegram TelegramConfig
  12. LLM LLMConfig
  13. Serper SerperConfig
  14. GitHub GitHubConfig
  15. Security SecurityConfig
  16. App AppConfig
  17. }
  18. type ServerConfig struct {
  19. Port int
  20. CORSOrigins []string `mapstructure:"cors_origins"` // allowed origins, empty = *
  21. MaxUploadMB int `mapstructure:"max_upload_mb"` // max upload size in MB, default 10
  22. }
  23. type SecurityConfig struct {
  24. JWTSecret string `mapstructure:"jwt_secret"`
  25. PasswordMinLen int `mapstructure:"password_min_len"` // default 8
  26. WebhookAllowHTTP bool `mapstructure:"webhook_allow_http"` // allow http:// webhooks
  27. }
  28. type AppConfig struct {
  29. Name string // display name, default "商户采集系统"
  30. }
  31. type MySQLConfig struct {
  32. Host string
  33. Port int
  34. User string
  35. Password string
  36. Database string
  37. }
  38. type RedisConfig struct {
  39. Host string
  40. Port int
  41. Password string
  42. DB int
  43. }
  44. type TelegramConfig struct {
  45. AppID int `mapstructure:"app_id"`
  46. AppHash string `mapstructure:"app_hash"`
  47. Accounts []TGAccount
  48. SessionsDir string `mapstructure:"sessions_dir"` // absolute path, e.g. /app/sessions
  49. SecretKey string `mapstructure:"secret_key"` // base64 32-byte, or literal ${TG_SECRET_KEY}
  50. }
  51. type TGAccount struct {
  52. Phone string
  53. SessionFile string `mapstructure:"session_file"`
  54. }
  55. type LLMConfig struct {
  56. Provider string
  57. BaseURL string `mapstructure:"base_url"`
  58. APIKey string `mapstructure:"api_key"`
  59. Model string
  60. Timeout string
  61. }
  62. type SerperConfig struct {
  63. APIKey string `mapstructure:"api_key"`
  64. ResultsPerPage int `mapstructure:"results_per_page"`
  65. MaxPages int `mapstructure:"max_pages"`
  66. }
  67. type GitHubConfig struct {
  68. Token string
  69. }
  70. var global *Config
  71. func Load(path string) (*Config, error) {
  72. v := viper.New()
  73. v.SetConfigFile(path)
  74. v.SetConfigType("yaml")
  75. if err := v.ReadInConfig(); err != nil {
  76. return nil, fmt.Errorf("read config file: %w", err)
  77. }
  78. cfg := &Config{}
  79. if err := v.Unmarshal(cfg); err != nil {
  80. return nil, fmt.Errorf("unmarshal config: %w", err)
  81. }
  82. // Apply defaults
  83. if cfg.Security.JWTSecret == "" {
  84. cfg.Security.JWTSecret = "spider-jwt-secret-2026"
  85. }
  86. if cfg.Security.PasswordMinLen < 6 {
  87. cfg.Security.PasswordMinLen = 8
  88. }
  89. if cfg.Server.MaxUploadMB <= 0 {
  90. cfg.Server.MaxUploadMB = 10
  91. }
  92. if cfg.App.Name == "" {
  93. cfg.App.Name = "商户采集系统"
  94. }
  95. // Resolve ${ENV_VAR} placeholders for secret fields.
  96. cfg.Telegram.SecretKey = expandEnvPlaceholder(cfg.Telegram.SecretKey)
  97. cfg.Security.JWTSecret = expandEnvPlaceholder(cfg.Security.JWTSecret)
  98. if cfg.Telegram.SessionsDir == "" {
  99. cfg.Telegram.SessionsDir = "/app/sessions"
  100. }
  101. global = cfg
  102. return cfg, nil
  103. }
  104. func Get() *Config {
  105. return global
  106. }
  107. // expandEnvPlaceholder replaces a value of the form "${VAR}" with os.Getenv("VAR").
  108. // A non-placeholder value is returned unchanged.
  109. func expandEnvPlaceholder(v string) string {
  110. if len(v) > 3 && v[0] == '$' && v[1] == '{' && v[len(v)-1] == '}' {
  111. return os.Getenv(v[2 : len(v)-1])
  112. }
  113. return v
  114. }