feat: 新增全站统一上传文件表 files,迁移 attachments 数据(type=attachments)

- models/file.go:File 模型,字段与 Attachment 对齐并新增 Type 归属类型字段
- models/db.go:AutoMigrate 注册 File;启动时幂等迁移 attachments → files
  (按主键 id 对齐跳过已迁移行,含软删除行一并复制)
- scripts/add_files_table.sql:MariaDB 幂等迁移脚本(建表 + INSERT...SELECT)
- 线上库 blog_go:files 表已建立,15 条 attachments 记录已迁入,type='attachments'
This commit is contained in:
dsh
2026-08-28 19:16:42 +08:00
parent 99ea9427bd
commit 09bf35d0dd
3 files changed
+107 -1

No files matched your search

+27 -1
View File
@@ -85,10 +85,13 @@ func InitDB(cfg *config.Config) *gorm.DB {
}
// 自动迁移数据表(幂等操作)。
if err := db.AutoMigrate(&User{}, &Article{}, &SiteSetting{}, &UploadConfig{}, &UploadFileType{}, &DownloadBaseURL{}, &Attachment{}, &Comment{}, &CommentConfig{}, &ArticleView{}, &NavLink{}, &Tag{}, &ArticleTag{}); err != nil {
if err := db.AutoMigrate(&User{}, &Article{}, &SiteSetting{}, &UploadConfig{}, &UploadFileType{}, &DownloadBaseURL{}, &Attachment{}, &File{}, &Comment{}, &CommentConfig{}, &ArticleView{}, &NavLink{}, &Tag{}, &ArticleTag{}); err != nil {
log.Fatalf("Failed to auto-migrate database: %v", err)
}
// 数据迁移:attachments → 全站统一 files 表(type='attachments'),幂等。
migrateAttachmentsToFiles(db)
// 首次运行时初始化站点平台配置。
seedSiteSettings(db)
seedUploadConfig(db)
@@ -132,3 +135,26 @@ func InitDB(cfg *config.Config) *gorm.DB {
DB = db
return db
}
// migrateAttachmentsToFiles 将 attachments 表中的历史数据复制到统一的
// files 表,type 一律标记为 "attachments"。
//
// 幂等策略:以主键 id 对齐——files 中已存在同 id 的行视为已迁移并跳过,
// 因此 InitDB 每次启动重复执行也不会产生重复数据(含软删除行一并复制)。
func migrateAttachmentsToFiles(db *gorm.DB) {
res := db.Exec(`
INSERT INTO files
(id, type, article_id, session_token, uploader_id, filename, stored_name, ext, mime, size, category, created_at, updated_at, deleted_at)
SELECT
a.id, 'attachments', a.article_id, a.session_token, a.uploader_id, a.filename,
a.stored_name, a.ext, a.mime, a.size, a.category, a.created_at, a.updated_at, a.deleted_at
FROM attachments a
WHERE NOT EXISTS (SELECT 1 FROM files f WHERE f.id = a.id)`)
if res.Error != nil {
log.Printf("Migration attachments -> files failed: %v", res.Error)
return
}
if res.RowsAffected > 0 {
log.Printf("Migration: copied %d attachment(s) into files table (type=attachments)", res.RowsAffected)
}
}
+41
View File
@@ -0,0 +1,41 @@
package models
import (
"time"
"gorm.io/gorm"
)
// File 是全站统一的上传文件登记表,用于管理所有类型的上传文件
// (附件、头像、Logo 等)。字段与 Attachment 对齐,额外通过 Type
// 字段区分文件归属类型。
//
// 说明:
// - attachments 表中的历史数据在启动迁移时复制到本表,Type 一律
// 标记为 "attachments"(见 db.go 的 migrateAttachmentsToFiles)。
// - 磁盘去重与引用计数沿用附件策略:StoredName 为内容 SHA-256
// 删除时仅当没有任何行引用时才删磁盘文件。
type File struct {
ID uint `gorm:"primarykey" json:"id"`
Type string `gorm:"size:32;index;default:attachments" json:"type"` // 归属类型:attachments/avatar/logo/...
ArticleID uint `gorm:"index" json:"article_id"` // 在创建页面上待绑定时为 0
SessionToken string `gorm:"size:64;index" json:"-"` // 创建页面上的临时归属令牌
UploaderID uint `gorm:"index" json:"uploader_id"`
Filename string `gorm:"size:255" json:"filename"` // 原始文件名
StoredName string `gorm:"size:64;index" json:"stored_name"` // SHA-256 十六进制字符串,磁盘文件名
Ext string `gorm:"size:32" json:"ext"`
MIME string `gorm:"size:128" json:"mime"`
Size int64 `gorm:"default:0" json:"size"`
Category string `gorm:"size:32" json:"category"` // image/document/archive/video/other
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}
// TableName 覆盖 GORM 默认的表名。
func (File) TableName() string {
return "files"
}
// FileTypeAttachment 是文件归属类型常量:来自 attachments 表的历史附件。
const FileTypeAttachment = "attachments"