mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 07:29:33 +08:00
优化数据库初始化逻辑,新增配置热更新与自动迁移支持,升级相关依赖
This commit is contained in:
@@ -51,6 +51,11 @@ BingPaper 支持通过配置文件(YAML)和环境变量进行配置。
|
|||||||
- MySQL 示例: `user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local`
|
- 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`
|
- Postgres 示例: `host=localhost user=user password=pass dbname=db port=5432 sslmode=disable TimeZone=Asia/Shanghai`
|
||||||
|
|
||||||
|
**注意:** BingPaper 支持数据库配置的热更新。如果你在程序运行时修改了 `db.type` 或 `db.dsn`,程序会自动尝试将当前数据库中的所有数据(图片记录、变体信息、Token)迁移到新的数据库中。
|
||||||
|
- 在迁移开始前,程序会**清空**目标数据库中的相关表以防止数据冲突。
|
||||||
|
- 迁移过程在事务中执行,确保数据一致性。
|
||||||
|
- 迁移完成后,程序将无缝切换到新的数据库连接。
|
||||||
|
|
||||||
#### storage (存储配置)
|
#### storage (存储配置)
|
||||||
- `type`: 存储类型,可选 `local`, `s3`, `webdav`。默认 `local`。
|
- `type`: 存储类型,可选 `local`, `s3`, `webdav`。默认 `local`。
|
||||||
- **local (本地存储)**:
|
- **local (本地存储)**:
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -51,7 +51,8 @@ require (
|
|||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.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/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // 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-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // 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/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
|||||||
6
go.sum
6
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/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 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
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 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
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/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 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
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 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ func Init(webFS embed.FS, configPath string) *gin.Engine {
|
|||||||
util.Logger.Fatal("Failed to initialize database")
|
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. 初始化存储
|
// 4. 初始化存储
|
||||||
var s storage.Storage
|
var s storage.Storage
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ var (
|
|||||||
GlobalConfig *Config
|
GlobalConfig *Config
|
||||||
configLock sync.RWMutex
|
configLock sync.RWMutex
|
||||||
v *viper.Viper
|
v *viper.Viper
|
||||||
|
|
||||||
|
// OnDBConfigChange 当数据库配置发生变更时的回调函数
|
||||||
|
OnDBConfigChange func(newCfg *Config)
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(configPath string) error {
|
func Init(configPath string) error {
|
||||||
@@ -206,8 +209,19 @@ func Init(configPath string) error {
|
|||||||
var newCfg Config
|
var newCfg Config
|
||||||
if err := v.Unmarshal(&newCfg); err == nil {
|
if err := v.Unmarshal(&newCfg); err == nil {
|
||||||
configLock.Lock()
|
configLock.Lock()
|
||||||
|
oldDBConfig := GlobalConfig.DB
|
||||||
GlobalConfig = &newCfg
|
GlobalConfig = &newCfg
|
||||||
|
newDBConfig := newCfg.DB
|
||||||
configLock.Unlock()
|
configLock.Unlock()
|
||||||
|
|
||||||
|
// 检查数据库配置是否发生变更
|
||||||
|
if oldDBConfig.Type != newDBConfig.Type || oldDBConfig.DSN != newDBConfig.DSN {
|
||||||
|
// 触发数据库迁移逻辑
|
||||||
|
// 这里由于循环依赖问题,我们可能需要通过回调或者一个统一的 Reload 函数来处理
|
||||||
|
if OnDBConfigChange != nil {
|
||||||
|
OnDBConfigChange(&newCfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
v.WatchConfig()
|
v.WatchConfig()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"BingPaper/internal/http/handlers"
|
"BingPaper/internal/http/handlers"
|
||||||
"BingPaper/internal/http/middleware"
|
"BingPaper/internal/http/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
swaggerFiles "github.com/swaggo/files"
|
swaggerFiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
@@ -22,6 +23,14 @@ import (
|
|||||||
func SetupRouter(webFS embed.FS) *gin.Engine {
|
func SetupRouter(webFS embed.FS) *gin.Engine {
|
||||||
r := gin.Default()
|
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
|
// Swagger
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
|
||||||
|
|||||||
@@ -76,21 +76,20 @@ func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDB() error {
|
func GetDialector(dbType, dsn string) (gorm.Dialector, error) {
|
||||||
cfg := config.GetConfig()
|
switch dbType {
|
||||||
var dialector gorm.Dialector
|
|
||||||
|
|
||||||
switch cfg.DB.Type {
|
|
||||||
case "mysql":
|
case "mysql":
|
||||||
dialector = mysql.Open(cfg.DB.DSN)
|
return mysql.Open(dsn), nil
|
||||||
case "postgres":
|
case "postgres":
|
||||||
dialector = postgres.Open(cfg.DB.DSN)
|
return postgres.Open(dsn), nil
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
dialector = sqlite.Open(cfg.DB.DSN)
|
return sqlite.Open(dsn), nil
|
||||||
default:
|
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
|
gormLogLevel := logger.Info
|
||||||
switch cfg.Log.DBLogLevel {
|
switch cfg.Log.DBLogLevel {
|
||||||
case "debug":
|
case "debug":
|
||||||
@@ -105,13 +104,23 @@ func InitDB() error {
|
|||||||
gormLogLevel = logger.Silent
|
gormLogLevel = logger.Silent
|
||||||
}
|
}
|
||||||
|
|
||||||
gormConfig := &gorm.Config{
|
return &gorm.Config{
|
||||||
Logger: &gormLogger{
|
Logger: &gormLogger{
|
||||||
ZapLogger: util.DBLogger,
|
ZapLogger: util.DBLogger,
|
||||||
LogLevel: gormLogLevel,
|
LogLevel: gormLogLevel,
|
||||||
},
|
},
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
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)
|
db, err := gorm.Open(dialector, gormConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user