mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 08:59:33 +08:00
数据库迁移优化,增强外键约束控制,改进日志输出
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -54,3 +54,4 @@ desktop.ini
|
|||||||
/bing_paper.db
|
/bing_paper.db
|
||||||
/req.txt
|
/req.txt
|
||||||
/BingPaper
|
/BingPaper
|
||||||
|
/temp/
|
||||||
|
|||||||
@@ -37,26 +37,25 @@ func Init(webFS embed.FS, configPath string) *gin.Engine {
|
|||||||
util.InitLogger(cfg.Log.Level)
|
util.InitLogger(cfg.Log.Level)
|
||||||
|
|
||||||
// 输出配置信息
|
// 输出配置信息
|
||||||
util.Logger.Info("Application configuration loaded",
|
util.Logger.Info("Application configuration loaded")
|
||||||
zap.String("config_file", config.GetRawViper().ConfigFileUsed()),
|
util.Logger.Info("├─ Config file", zap.String("path", config.GetRawViper().ConfigFileUsed()))
|
||||||
zap.String("db_type", cfg.DB.Type),
|
util.Logger.Info("├─ Database ", zap.String("type", cfg.DB.Type))
|
||||||
zap.String("storage_type", cfg.Storage.Type),
|
util.Logger.Info("├─ Storage ", zap.String("type", cfg.Storage.Type))
|
||||||
zap.Int("server_port", cfg.Server.Port),
|
util.Logger.Info("└─ Server ", zap.Int("port", cfg.Server.Port))
|
||||||
)
|
|
||||||
|
|
||||||
// 根据存储类型输出更多信息
|
// 根据存储类型输出更多信息
|
||||||
switch cfg.Storage.Type {
|
switch cfg.Storage.Type {
|
||||||
case "s3":
|
case "s3":
|
||||||
util.Logger.Info("S3 storage info",
|
util.Logger.Info("S3 storage detail",
|
||||||
zap.String("endpoint", cfg.Storage.S3.Endpoint),
|
zap.String("endpoint", cfg.Storage.S3.Endpoint),
|
||||||
zap.String("bucket", cfg.Storage.S3.Bucket),
|
zap.String("bucket", cfg.Storage.S3.Bucket),
|
||||||
)
|
)
|
||||||
case "webdav":
|
case "webdav":
|
||||||
util.Logger.Info("WebDAV storage info",
|
util.Logger.Info("WebDAV storage detail",
|
||||||
zap.String("url", cfg.Storage.WebDAV.URL),
|
zap.String("url", cfg.Storage.WebDAV.URL),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
util.Logger.Info("Local storage info",
|
util.Logger.Info("Local storage detail",
|
||||||
zap.String("root", cfg.Storage.Local.Root),
|
zap.String("root", cfg.Storage.Local.Root),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ type Image struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
Variants []ImageVariant `gorm:"foreignKey:ImageID" json:"variants"`
|
Variants []ImageVariant `gorm:"foreignKey:ImageID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"variants"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageVariant struct {
|
type ImageVariant struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
ImageID uint `gorm:"uniqueIndex:idx_image_variant_format" json:"image_id"`
|
ImageID uint `gorm:"index;uniqueIndex:idx_image_variant_format" json:"image_id"`
|
||||||
Variant string `gorm:"uniqueIndex:idx_image_variant_format;type:varchar(20)" json:"variant"` // UHD, 1920x1080, etc.
|
Variant string `gorm:"uniqueIndex:idx_image_variant_format;type:varchar(20)" json:"variant"` // UHD, 1920x1080, etc.
|
||||||
Format string `gorm:"uniqueIndex:idx_image_variant_format;type:varchar(10)" json:"format"` // jpg, webp
|
Format string `gorm:"uniqueIndex:idx_image_variant_format;type:varchar(10)" json:"format"` // jpg, webp
|
||||||
StorageKey string `json:"storage_key"`
|
StorageKey string `json:"storage_key"`
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ func InitDB() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gormConfig := &gorm.Config{
|
gormConfig := &gorm.Config{
|
||||||
Logger: logger.Default.LogMode(logger.Info),
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := gorm.Open(dialector, gormConfig)
|
db, err := gorm.Open(dialector, gormConfig)
|
||||||
@@ -40,8 +41,12 @@ func InitDB() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 针对 MySQL 的额外处理:如果数据库不存在,GORM 的 mysql 驱动通常无法直接创建库。
|
||||||
|
// 但此处假设 DSN 中指定的数据库已经存在。AutoMigrate 会负责创建表。
|
||||||
|
|
||||||
// 迁移
|
// 迁移
|
||||||
if err := db.AutoMigrate(&model.Image{}, &model.ImageVariant{}, &model.Token{}); err != nil {
|
if err := db.AutoMigrate(&model.Image{}, &model.ImageVariant{}, &model.Token{}); err != nil {
|
||||||
|
util.Logger.Error("Database migration failed", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ import (
|
|||||||
"BingPaper/internal/repo"
|
"BingPaper/internal/repo"
|
||||||
"BingPaper/internal/storage"
|
"BingPaper/internal/storage"
|
||||||
"BingPaper/internal/util"
|
"BingPaper/internal/util"
|
||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BingResponse struct {
|
type BingResponse struct {
|
||||||
@@ -109,10 +111,22 @@ func (f *Fetcher) processImage(ctx context.Context, bingImg BingImage) error {
|
|||||||
Quiz: bingImg.Quiz,
|
Quiz: bingImg.Quiz,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.DB.Create(&dbImg).Error; err != nil {
|
if err := repo.DB.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "date"}},
|
||||||
|
DoNothing: true,
|
||||||
|
}).Create(&dbImg).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 再次检查 dbImg.ID 是否被填充,如果没有被填充(说明由于冲突未插入),则需要查询出已有的 ID
|
||||||
|
if dbImg.ID == 0 {
|
||||||
|
var existing model.Image
|
||||||
|
if err := repo.DB.Where("date = ?", dateStr).First(&existing).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbImg = existing
|
||||||
|
}
|
||||||
|
|
||||||
// 保存各种分辨率
|
// 保存各种分辨率
|
||||||
variants := []struct {
|
variants := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -194,27 +208,38 @@ func (f *Fetcher) saveVariant(ctx context.Context, img *model.Image, variant, fo
|
|||||||
Size: int64(len(data)),
|
Size: int64(len(data)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return repo.DB.Create(&vRecord).Error
|
return repo.DB.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "image_id"}, {Name: "variant"}, {Name: "format"}},
|
||||||
|
DoNothing: true,
|
||||||
|
}).Create(&vRecord).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher) saveDailyFiles(srcImg image.Image, originalData []byte) {
|
func (f *Fetcher) saveDailyFiles(srcImg image.Image, originalData []byte) {
|
||||||
util.Logger.Info("Saving daily files")
|
util.Logger.Info("Saving daily files")
|
||||||
localRoot := config.GetConfig().Storage.Local.Root
|
localRoot := config.GetConfig().Storage.Local.Root
|
||||||
if config.GetConfig().Storage.Type != "local" {
|
if config.GetConfig().Storage.Type != "local" {
|
||||||
// 如果不是本地存储,保存在临时目录或指定缓存目录
|
// 如果不是本地存储,保存在静态资源目录
|
||||||
localRoot = "static"
|
localRoot = "static"
|
||||||
}
|
}
|
||||||
os.MkdirAll(filepath.Join(localRoot, "static"), 0755)
|
|
||||||
|
if err := os.MkdirAll(localRoot, 0755); err != nil {
|
||||||
|
util.Logger.Error("Failed to create directory", zap.String("path", localRoot), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// daily.jpeg (quality 95)
|
// daily.jpeg (quality 95)
|
||||||
jpegPath := filepath.Join(localRoot, "static", "daily.jpeg")
|
jpegPath := filepath.Join(localRoot, "daily.jpeg")
|
||||||
fJpeg, _ := os.Create(jpegPath)
|
fJpeg, err := os.Create(jpegPath)
|
||||||
if fJpeg != nil {
|
if err != nil {
|
||||||
|
util.Logger.Error("Failed to create daily.jpeg", zap.Error(err))
|
||||||
|
} else {
|
||||||
jpeg.Encode(fJpeg, srcImg, &jpeg.Options{Quality: 95})
|
jpeg.Encode(fJpeg, srcImg, &jpeg.Options{Quality: 95})
|
||||||
fJpeg.Close()
|
fJpeg.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// original.jpeg (quality 100)
|
// original.jpeg (quality 100)
|
||||||
originalPath := filepath.Join(localRoot, "static", "original.jpeg")
|
originalPath := filepath.Join(localRoot, "original.jpeg")
|
||||||
os.WriteFile(originalPath, originalData, 0644)
|
if err := os.WriteFile(originalPath, originalData, 0644); err != nil {
|
||||||
|
util.Logger.Error("Failed to write original.jpeg", zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,14 @@ func CleanupOldImages(ctx context.Context) error {
|
|||||||
util.Logger.Warn("Failed to delete storage object", zap.String("key", v.StorageKey), zap.Error(err))
|
util.Logger.Warn("Failed to delete storage object", zap.String("key", v.StorageKey), zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 删除 DB 记录 (级联删除由代码处理,或者 GORM 会处理已加载的关联吗?)
|
// 删除关联记录(逻辑外键控制)
|
||||||
// 简单起见,手动删除关联
|
if err := repo.DB.Where("image_id = ?", img.ID).Delete(&model.ImageVariant{}).Error; err != nil {
|
||||||
repo.DB.Where("image_id = ?", img.ID).Delete(&model.ImageVariant{})
|
util.Logger.Error("Failed to delete variants", zap.Uint("image_id", img.ID), zap.Error(err))
|
||||||
repo.DB.Delete(&img)
|
}
|
||||||
|
// 删除主表记录
|
||||||
|
if err := repo.DB.Delete(&img).Error; err != nil {
|
||||||
|
util.Logger.Error("Failed to delete image", zap.Uint("id", img.ID), zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
util.Logger.Info("Cleanup task completed", zap.Int("deleted_count", len(images)))
|
util.Logger.Info("Cleanup task completed", zap.Int("deleted_count", len(images)))
|
||||||
|
|||||||
Reference in New Issue
Block a user