|
|
@@ -2,6 +2,7 @@ package config
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
+ "os"
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
)
|
|
|
@@ -14,10 +15,24 @@ type Config struct {
|
|
|
LLM LLMConfig
|
|
|
Serper SerperConfig
|
|
|
GitHub GitHubConfig
|
|
|
+ Security SecurityConfig
|
|
|
+ App AppConfig
|
|
|
}
|
|
|
|
|
|
type ServerConfig struct {
|
|
|
- Port int
|
|
|
+ Port int
|
|
|
+ CORSOrigins []string `mapstructure:"cors_origins"` // allowed origins, empty = *
|
|
|
+ MaxUploadMB int `mapstructure:"max_upload_mb"` // max upload size in MB, default 10
|
|
|
+}
|
|
|
+
|
|
|
+type SecurityConfig struct {
|
|
|
+ JWTSecret string `mapstructure:"jwt_secret"`
|
|
|
+ PasswordMinLen int `mapstructure:"password_min_len"` // default 8
|
|
|
+ WebhookAllowHTTP bool `mapstructure:"webhook_allow_http"` // allow http:// webhooks
|
|
|
+}
|
|
|
+
|
|
|
+type AppConfig struct {
|
|
|
+ Name string // display name, default "商户采集系统"
|
|
|
}
|
|
|
|
|
|
type MySQLConfig struct {
|
|
|
@@ -36,9 +51,11 @@ type RedisConfig struct {
|
|
|
}
|
|
|
|
|
|
type TelegramConfig struct {
|
|
|
- AppID int `mapstructure:"app_id"`
|
|
|
- AppHash string `mapstructure:"app_hash"`
|
|
|
- Accounts []TGAccount
|
|
|
+ AppID int `mapstructure:"app_id"`
|
|
|
+ AppHash string `mapstructure:"app_hash"`
|
|
|
+ Accounts []TGAccount
|
|
|
+ SessionsDir string `mapstructure:"sessions_dir"` // absolute path, e.g. /app/sessions
|
|
|
+ SecretKey string `mapstructure:"secret_key"` // base64 32-byte, or literal ${TG_SECRET_KEY}
|
|
|
}
|
|
|
|
|
|
type TGAccount struct {
|
|
|
@@ -80,6 +97,28 @@ func Load(path string) (*Config, error) {
|
|
|
return nil, fmt.Errorf("unmarshal config: %w", err)
|
|
|
}
|
|
|
|
|
|
+ // Apply defaults
|
|
|
+ if cfg.Security.JWTSecret == "" {
|
|
|
+ cfg.Security.JWTSecret = "spider-jwt-secret-2026"
|
|
|
+ }
|
|
|
+ if cfg.Security.PasswordMinLen < 6 {
|
|
|
+ cfg.Security.PasswordMinLen = 8
|
|
|
+ }
|
|
|
+ if cfg.Server.MaxUploadMB <= 0 {
|
|
|
+ cfg.Server.MaxUploadMB = 10
|
|
|
+ }
|
|
|
+ if cfg.App.Name == "" {
|
|
|
+ cfg.App.Name = "商户采集系统"
|
|
|
+ }
|
|
|
+
|
|
|
+ // Resolve ${ENV_VAR} placeholders for secret fields.
|
|
|
+ cfg.Telegram.SecretKey = expandEnvPlaceholder(cfg.Telegram.SecretKey)
|
|
|
+ cfg.Security.JWTSecret = expandEnvPlaceholder(cfg.Security.JWTSecret)
|
|
|
+
|
|
|
+ if cfg.Telegram.SessionsDir == "" {
|
|
|
+ cfg.Telegram.SessionsDir = "/app/sessions"
|
|
|
+ }
|
|
|
+
|
|
|
global = cfg
|
|
|
return cfg, nil
|
|
|
}
|
|
|
@@ -87,3 +126,12 @@ func Load(path string) (*Config, error) {
|
|
|
func Get() *Config {
|
|
|
return global
|
|
|
}
|
|
|
+
|
|
|
+// expandEnvPlaceholder replaces a value of the form "${VAR}" with os.Getenv("VAR").
|
|
|
+// A non-placeholder value is returned unchanged.
|
|
|
+func expandEnvPlaceholder(v string) string {
|
|
|
+ if len(v) > 3 && v[0] == '$' && v[1] == '{' && v[len(v)-1] == '}' {
|
|
|
+ return os.Getenv(v[2 : len(v)-1])
|
|
|
+ }
|
|
|
+ return v
|
|
|
+}
|