新增 YAML 标签支持并优化配置文件保存逻辑

This commit is contained in:
2026-01-28 16:25:34 +08:00
parent b31711d86d
commit 617c1d0967

View File

@@ -10,38 +10,39 @@ import (
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3"
) )
type Config struct { type Config struct {
Server ServerConfig `mapstructure:"server"` Server ServerConfig `mapstructure:"server" yaml:"server"`
Log LogConfig `mapstructure:"log"` Log LogConfig `mapstructure:"log" yaml:"log"`
API APIConfig `mapstructure:"api"` API APIConfig `mapstructure:"api" yaml:"api"`
Cron CronConfig `mapstructure:"cron"` Cron CronConfig `mapstructure:"cron" yaml:"cron"`
Retention RetentionConfig `mapstructure:"retention"` Retention RetentionConfig `mapstructure:"retention" yaml:"retention"`
DB DBConfig `mapstructure:"db"` DB DBConfig `mapstructure:"db" yaml:"db"`
Storage StorageConfig `mapstructure:"storage"` Storage StorageConfig `mapstructure:"storage" yaml:"storage"`
Admin AdminConfig `mapstructure:"admin"` Admin AdminConfig `mapstructure:"admin" yaml:"admin"`
Token TokenConfig `mapstructure:"token"` Token TokenConfig `mapstructure:"token" yaml:"token"`
Feature FeatureConfig `mapstructure:"feature"` Feature FeatureConfig `mapstructure:"feature" yaml:"feature"`
Web WebConfig `mapstructure:"web"` Web WebConfig `mapstructure:"web" yaml:"web"`
} }
type ServerConfig struct { type ServerConfig struct {
Port int `mapstructure:"port"` Port int `mapstructure:"port" yaml:"port"`
BaseURL string `mapstructure:"base_url"` BaseURL string `mapstructure:"base_url" yaml:"base_url"`
} }
type LogConfig struct { type LogConfig struct {
Level string `mapstructure:"level"` Level string `mapstructure:"level" yaml:"level"`
Filename string `mapstructure:"filename"` // 业务日志文件名 Filename string `mapstructure:"filename" yaml:"filename"` // 业务日志文件名
DBFilename string `mapstructure:"db_filename"` // 数据库日志文件名 DBFilename string `mapstructure:"db_filename" yaml:"db_filename"` // 数据库日志文件名
MaxSize int `mapstructure:"max_size"` // 每个日志文件最大大小 (MB) MaxSize int `mapstructure:"max_size" yaml:"max_size"` // 每个日志文件最大大小 (MB)
MaxBackups int `mapstructure:"max_backups"` // 保留旧日志文件最大个数 MaxBackups int `mapstructure:"max_backups" yaml:"max_backups"` // 保留旧日志文件最大个数
MaxAge int `mapstructure:"max_age"` // 保留旧日志文件最大天数 MaxAge int `mapstructure:"max_age" yaml:"max_age"` // 保留旧日志文件最大天数
Compress bool `mapstructure:"compress"` // 是否压缩旧日志文件 Compress bool `mapstructure:"compress" yaml:"compress"` // 是否压缩旧日志文件
LogConsole bool `mapstructure:"log_console"` // 是否同时输出到控制台 LogConsole bool `mapstructure:"log_console" yaml:"log_console"` // 是否同时输出到控制台
ShowDBLog bool `mapstructure:"show_db_log"` // 是否在控制台显示数据库日志 ShowDBLog bool `mapstructure:"show_db_log" yaml:"show_db_log"` // 是否在控制台显示数据库日志
DBLogLevel string `mapstructure:"db_log_level"` // 数据库日志级别: debug, info, warn, error DBLogLevel string `mapstructure:"db_log_level" yaml:"db_log_level"` // 数据库日志级别: debug, info, warn, error
} }
func (c LogConfig) GetLevel() string { return c.Level } func (c LogConfig) GetLevel() string { return c.Level }
@@ -56,65 +57,65 @@ func (c LogConfig) GetShowDBLog() bool { return c.ShowDBLog }
func (c LogConfig) GetDBLogLevel() string { return c.DBLogLevel } func (c LogConfig) GetDBLogLevel() string { return c.DBLogLevel }
type APIConfig struct { type APIConfig struct {
Mode string `mapstructure:"mode"` // local | redirect Mode string `mapstructure:"mode" yaml:"mode"` // local | redirect
} }
type CronConfig struct { type CronConfig struct {
Enabled bool `mapstructure:"enabled"` Enabled bool `mapstructure:"enabled" yaml:"enabled"`
DailySpec string `mapstructure:"daily_spec"` DailySpec string `mapstructure:"daily_spec" yaml:"daily_spec"`
} }
type RetentionConfig struct { type RetentionConfig struct {
Days int `mapstructure:"days"` Days int `mapstructure:"days" yaml:"days"`
} }
type DBConfig struct { type DBConfig struct {
Type string `mapstructure:"type"` // sqlite/mysql/postgres Type string `mapstructure:"type" yaml:"type"` // sqlite/mysql/postgres
DSN string `mapstructure:"dsn"` DSN string `mapstructure:"dsn" yaml:"dsn"`
} }
type StorageConfig struct { type StorageConfig struct {
Type string `mapstructure:"type"` // local/s3/webdav Type string `mapstructure:"type" yaml:"type"` // local/s3/webdav
Local LocalConfig `mapstructure:"local"` Local LocalConfig `mapstructure:"local" yaml:"local"`
S3 S3Config `mapstructure:"s3"` S3 S3Config `mapstructure:"s3" yaml:"s3"`
WebDAV WebDAVConfig `mapstructure:"webdav"` WebDAV WebDAVConfig `mapstructure:"webdav" yaml:"webdav"`
} }
type LocalConfig struct { type LocalConfig struct {
Root string `mapstructure:"root"` Root string `mapstructure:"root" yaml:"root"`
} }
type S3Config struct { type S3Config struct {
Endpoint string `mapstructure:"endpoint"` Endpoint string `mapstructure:"endpoint" yaml:"endpoint"`
Region string `mapstructure:"region"` Region string `mapstructure:"region" yaml:"region"`
Bucket string `mapstructure:"bucket"` Bucket string `mapstructure:"bucket" yaml:"bucket"`
AccessKey string `mapstructure:"access_key"` AccessKey string `mapstructure:"access_key" yaml:"access_key"`
SecretKey string `mapstructure:"secret_key"` SecretKey string `mapstructure:"secret_key" yaml:"secret_key"`
PublicURLPrefix string `mapstructure:"public_url_prefix"` PublicURLPrefix string `mapstructure:"public_url_prefix" yaml:"public_url_prefix"`
ForcePathStyle bool `mapstructure:"force_path_style"` ForcePathStyle bool `mapstructure:"force_path_style" yaml:"force_path_style"`
} }
type WebDAVConfig struct { type WebDAVConfig struct {
URL string `mapstructure:"url"` URL string `mapstructure:"url" yaml:"url"`
Username string `mapstructure:"username"` Username string `mapstructure:"username" yaml:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password" yaml:"password"`
PublicURLPrefix string `mapstructure:"public_url_prefix"` PublicURLPrefix string `mapstructure:"public_url_prefix" yaml:"public_url_prefix"`
} }
type AdminConfig struct { type AdminConfig struct {
PasswordBcrypt string `mapstructure:"password_bcrypt"` PasswordBcrypt string `mapstructure:"password_bcrypt" yaml:"password_bcrypt"`
} }
type TokenConfig struct { type TokenConfig struct {
DefaultTTL string `mapstructure:"default_ttl"` DefaultTTL string `mapstructure:"default_ttl" yaml:"default_ttl"`
} }
type FeatureConfig struct { type FeatureConfig struct {
WriteDailyFiles bool `mapstructure:"write_daily_files"` WriteDailyFiles bool `mapstructure:"write_daily_files" yaml:"write_daily_files"`
} }
type WebConfig struct { type WebConfig struct {
Path string `mapstructure:"path"` Path string `mapstructure:"path" yaml:"path"`
} }
// Bing 默认配置 (内置) // Bing 默认配置 (内置)
@@ -193,10 +194,15 @@ func Init(configPath string) error {
targetConfigPath = "data/config.yaml" targetConfigPath = "data/config.yaml"
} }
fmt.Printf("Config file not found, creating default config at %s\n", targetConfigPath) fmt.Printf("Config file not found, creating default config at %s\n", targetConfigPath)
if err := v.SafeWriteConfigAs(targetConfigPath); err != nil {
var defaultCfg Config
if err := v.Unmarshal(&defaultCfg); err == nil {
data, _ := yaml.Marshal(&defaultCfg)
if err := os.WriteFile(targetConfigPath, data, 0644); err != nil {
fmt.Printf("Warning: Failed to create default config file: %v\n", err) fmt.Printf("Warning: Failed to create default config file: %v\n", err)
} }
} }
}
var cfg Config var cfg Config
if err := v.Unmarshal(&cfg); err != nil { if err := v.Unmarshal(&cfg); err != nil {
@@ -237,18 +243,29 @@ func GetConfig() *Config {
} }
func SaveConfig(cfg *Config) error { func SaveConfig(cfg *Config) error {
v.Set("server", cfg.Server) configLock.Lock()
v.Set("log", cfg.Log) defer configLock.Unlock()
v.Set("api", cfg.API)
v.Set("cron", cfg.Cron) // 1. 使用 yaml.v3 序列化,它会尊重结构体字段顺序及 yaml 标签
v.Set("retention", cfg.Retention) data, err := yaml.Marshal(cfg)
v.Set("db", cfg.DB) if err != nil {
v.Set("storage", cfg.Storage) return fmt.Errorf("failed to marshal config: %v", err)
v.Set("admin", cfg.Admin) }
v.Set("token", cfg.Token)
v.Set("feature", cfg.Feature) // 2. 获取当前使用的配置文件路径
v.Set("web", cfg.Web) targetPath := v.ConfigFileUsed()
return v.WriteConfig() if targetPath == "" {
targetPath = "data/config.yaml" // 默认回退路径
}
// 3. 直接写入文件,绕过 viper 的字母序排序逻辑
if err := os.WriteFile(targetPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %v", err)
}
// 4. 同步更新内存中的全局配置对象
GlobalConfig = cfg
return nil
} }
func GetRawViper() *viper.Viper { func GetRawViper() *viper.Viper {