From cfd7c605af0b25ebb0c59268522ae2c3437aaede Mon Sep 17 00:00:00 2001 From: hanxuanyu <2252193204@qq.com> Date: Tue, 27 Jan 2026 12:20:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E9=85=8D=E7=BD=AE=E7=83=AD=E6=9B=B4=E6=96=B0=E4=B8=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=BF=81=E7=A7=BB=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=9B=B8=E5=85=B3=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONFIG.md | 5 +++++ go.mod | 5 +++-- go.sum | 6 ++++++ internal/bootstrap/bootstrap.go | 10 ++++++++++ internal/config/config.go | 14 ++++++++++++++ internal/http/router.go | 9 +++++++++ internal/repo/db.go | 29 +++++++++++++++++++---------- 7 files changed, 66 insertions(+), 12 deletions(-) diff --git a/CONFIG.md b/CONFIG.md index 0e59bc3..6071c01 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -51,6 +51,11 @@ BingPaper 支持通过配置文件(YAML)和环境变量进行配置。 - MySQL 示例: `user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local` - Postgres 示例: `host=localhost user=user password=pass dbname=db port=5432 sslmode=disable TimeZone=Asia/Shanghai` +**注意:** BingPaper 支持数据库配置的热更新。如果你在程序运行时修改了 `db.type` 或 `db.dsn`,程序会自动尝试将当前数据库中的所有数据(图片记录、变体信息、Token)迁移到新的数据库中。 +- 在迁移开始前,程序会**清空**目标数据库中的相关表以防止数据冲突。 +- 迁移过程在事务中执行,确保数据一致性。 +- 迁移完成后,程序将无缝切换到新的数据库连接。 + #### storage (存储配置) - `type`: 存储类型,可选 `local`, `s3`, `webdav`。默认 `local`。 - **local (本地存储)**: diff --git a/go.mod b/go.mod index 3f5242d..01771b3 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,8 @@ require ( github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/cors v1.7.6 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -63,7 +64,7 @@ require ( github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7cf2059..2346970 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,10 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -100,6 +104,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index dfde33b..78449eb 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -65,6 +65,16 @@ func Init(webFS embed.FS, configPath string) *gin.Engine { util.Logger.Fatal("Failed to initialize database") } + // 注册数据库配置变更回调,支持热迁移 + config.OnDBConfigChange = func(newCfg *config.Config) { + util.Logger.Info("Database configuration change detected, initiating migration...") + if err := repo.MigrateDataToNewDB(repo.DB, newCfg); err != nil { + util.Logger.Error("Automatic data migration failed", zap.Error(err)) + } else { + util.Logger.Info("Automatic data migration finished") + } + } + // 4. 初始化存储 var s storage.Storage var err error diff --git a/internal/config/config.go b/internal/config/config.go index fda6722..9870b6e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -127,6 +127,9 @@ var ( GlobalConfig *Config configLock sync.RWMutex v *viper.Viper + + // OnDBConfigChange 当数据库配置发生变更时的回调函数 + OnDBConfigChange func(newCfg *Config) ) func Init(configPath string) error { @@ -206,8 +209,19 @@ func Init(configPath string) error { var newCfg Config if err := v.Unmarshal(&newCfg); err == nil { configLock.Lock() + oldDBConfig := GlobalConfig.DB GlobalConfig = &newCfg + newDBConfig := newCfg.DB configLock.Unlock() + + // 检查数据库配置是否发生变更 + if oldDBConfig.Type != newDBConfig.Type || oldDBConfig.DSN != newDBConfig.DSN { + // 触发数据库迁移逻辑 + // 这里由于循环依赖问题,我们可能需要通过回调或者一个统一的 Reload 函数来处理 + if OnDBConfigChange != nil { + OnDBConfigChange(&newCfg) + } + } } }) v.WatchConfig() diff --git a/internal/http/router.go b/internal/http/router.go index 7c74505..89e3190 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -14,6 +14,7 @@ import ( "BingPaper/internal/http/handlers" "BingPaper/internal/http/middleware" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" @@ -22,6 +23,14 @@ import ( func SetupRouter(webFS embed.FS) *gin.Engine { r := gin.Default() + // CORS 配置:更宽松的配置以解决 Vue 等前端的预检请求问题 + corsConfig := cors.DefaultConfig() + corsConfig.AllowAllOrigins = true + corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} + corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "Accept", "X-Requested-With"} + corsConfig.AllowCredentials = true + r.Use(cors.New(corsConfig)) + // Swagger r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/internal/repo/db.go b/internal/repo/db.go index 7626111..9f3abbc 100644 --- a/internal/repo/db.go +++ b/internal/repo/db.go @@ -76,21 +76,20 @@ func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (stri } } -func InitDB() error { - cfg := config.GetConfig() - var dialector gorm.Dialector - - switch cfg.DB.Type { +func GetDialector(dbType, dsn string) (gorm.Dialector, error) { + switch dbType { case "mysql": - dialector = mysql.Open(cfg.DB.DSN) + return mysql.Open(dsn), nil case "postgres": - dialector = postgres.Open(cfg.DB.DSN) + return postgres.Open(dsn), nil case "sqlite": - dialector = sqlite.Open(cfg.DB.DSN) + return sqlite.Open(dsn), nil default: - return fmt.Errorf("unsupported db type: %s", cfg.DB.Type) + return nil, fmt.Errorf("unsupported db type: %s", dbType) } +} +func GetGormConfig(cfg *config.Config) *gorm.Config { gormLogLevel := logger.Info switch cfg.Log.DBLogLevel { case "debug": @@ -105,13 +104,23 @@ func InitDB() error { gormLogLevel = logger.Silent } - gormConfig := &gorm.Config{ + return &gorm.Config{ Logger: &gormLogger{ ZapLogger: util.DBLogger, LogLevel: gormLogLevel, }, DisableForeignKeyConstraintWhenMigrating: true, } +} + +func InitDB() error { + cfg := config.GetConfig() + dialector, err := GetDialector(cfg.DB.Type, cfg.DB.DSN) + if err != nil { + return err + } + + gormConfig := GetGormConfig(cfg) db, err := gorm.Open(dialector, gormConfig) if err != nil {