package config import ( "fmt" "os" "sync" "time" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Config struct { Server ServerConfig `mapstructure:"server"` Log LogConfig `mapstructure:"log"` API APIConfig `mapstructure:"api"` Cron CronConfig `mapstructure:"cron"` Retention RetentionConfig `mapstructure:"retention"` DB DBConfig `mapstructure:"db"` Storage StorageConfig `mapstructure:"storage"` Admin AdminConfig `mapstructure:"admin"` Token TokenConfig `mapstructure:"token"` Feature FeatureConfig `mapstructure:"feature"` Web WebConfig `mapstructure:"web"` } type ServerConfig struct { Port int `mapstructure:"port"` BaseURL string `mapstructure:"base_url"` } type LogConfig struct { Level string `mapstructure:"level"` } type APIConfig struct { Mode string `mapstructure:"mode"` // local | redirect } type CronConfig struct { Enabled bool `mapstructure:"enabled"` DailySpec string `mapstructure:"daily_spec"` } type RetentionConfig struct { Days int `mapstructure:"days"` } type DBConfig struct { Type string `mapstructure:"type"` // sqlite/mysql/postgres DSN string `mapstructure:"dsn"` } type StorageConfig struct { Type string `mapstructure:"type"` // local/s3/webdav Local LocalConfig `mapstructure:"local"` S3 S3Config `mapstructure:"s3"` WebDAV WebDAVConfig `mapstructure:"webdav"` } type LocalConfig struct { Root string `mapstructure:"root"` } type S3Config struct { Endpoint string `mapstructure:"endpoint"` Region string `mapstructure:"region"` Bucket string `mapstructure:"bucket"` AccessKey string `mapstructure:"access_key"` SecretKey string `mapstructure:"secret_key"` PublicURLPrefix string `mapstructure:"public_url_prefix"` ForcePathStyle bool `mapstructure:"force_path_style"` } type WebDAVConfig struct { URL string `mapstructure:"url"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` PublicURLPrefix string `mapstructure:"public_url_prefix"` } type AdminConfig struct { PasswordBcrypt string `mapstructure:"password_bcrypt"` } type TokenConfig struct { DefaultTTL string `mapstructure:"default_ttl"` } type FeatureConfig struct { WriteDailyFiles bool `mapstructure:"write_daily_files"` } type WebConfig struct { Path string `mapstructure:"path"` } // Bing 默认配置 (内置) const ( BingMkt = "zh-CN" BingFetchN = 8 BingAPIBase = "https://www.bing.com/HPImageArchive.aspx" ) var ( GlobalConfig *Config configLock sync.RWMutex v *viper.Viper ) func Init(configPath string) error { v = viper.New() if configPath != "" { v.SetConfigFile(configPath) } else { v.SetConfigName("config") v.SetConfigType("yaml") v.AddConfigPath("./data") v.AddConfigPath(".") } v.SetDefault("server.port", 8080) v.SetDefault("log.level", "info") v.SetDefault("api.mode", "local") v.SetDefault("cron.enabled", true) v.SetDefault("cron.daily_spec", "0 10 * * *") v.SetDefault("retention.days", 30) v.SetDefault("db.type", "sqlite") v.SetDefault("db.dsn", "data/bing_paper.db") v.SetDefault("storage.type", "local") v.SetDefault("storage.local.root", "data/picture") v.SetDefault("token.default_ttl", "168h") v.SetDefault("feature.write_daily_files", true) v.SetDefault("web.path", "web") v.SetDefault("admin.password_bcrypt", "$2a$10$fYHPeWHmwObephJvtlyH1O8DIgaLk5TINbi9BOezo2M8cSjmJchka") // 默认密码: admin123 if err := v.ReadInConfig(); err != nil { // 如果指定了配置文件但读取失败(且不是找不到文件的错误),或者没指定但也没找到 _, isNotFound := err.(viper.ConfigFileNotFoundError) // 如果显式指定了文件,viper 报错可能不是 ConfigFileNotFoundError 而是 os.PathError if !isNotFound && configPath != "" { if _, statErr := os.Stat(configPath); os.IsNotExist(statErr) { isNotFound = true } } if !isNotFound { return err } // 如果文件不存在,我们使用默认值并尝试创建一个默认配置文件 targetConfigPath := configPath if targetConfigPath == "" { targetConfigPath = "data/config.yaml" } fmt.Printf("Config file not found, creating default config at %s\n", targetConfigPath) if err := v.SafeWriteConfigAs(targetConfigPath); err != nil { fmt.Printf("Warning: Failed to create default config file: %v\n", err) } } var cfg Config if err := v.Unmarshal(&cfg); err != nil { return err } GlobalConfig = &cfg v.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) var newCfg Config if err := v.Unmarshal(&newCfg); err == nil { configLock.Lock() GlobalConfig = &newCfg configLock.Unlock() } }) v.WatchConfig() return nil } func GetConfig() *Config { configLock.RLock() defer configLock.RUnlock() return GlobalConfig } func SaveConfig(cfg *Config) error { v.Set("server", cfg.Server) v.Set("log", cfg.Log) v.Set("api", cfg.API) v.Set("cron", cfg.Cron) v.Set("retention", cfg.Retention) v.Set("db", cfg.DB) v.Set("storage", cfg.Storage) v.Set("admin", cfg.Admin) v.Set("token", cfg.Token) v.Set("feature", cfg.Feature) v.Set("web", cfg.Web) return v.WriteConfig() } func GetRawViper() *viper.Viper { return v } func GetTokenTTL() time.Duration { ttl, err := time.ParseDuration(GetConfig().Token.DefaultTTL) if err != nil { return 168 * time.Hour } return ttl }