feat: 媒体库 blob 去重存储与品牌/注册等前端调整

新增:
- blob_store 二进制去重存储 + blob_dedup 测试

其它:
- 后端 settings/auth/media_library/upload/operations 服务与 handler 调整
- 前端品牌(BrandLockup/BrandCropModal/BrandSeoPanel)、注册、媒体库、分析详情页、Header/SiteChrome 等更新
This commit is contained in:
2026-10-03 02:38:11 +08:00
parent 8cfdfcbbf0
commit 46e0cdc0b3
32 changed files with 1242 additions and 228 deletions

View File

@@ -362,12 +362,13 @@ type PostContentUnlock struct {
// PostAttachment 帖子文件附件(不走公开静态目录,经 API 鉴权下载)
type PostAttachment struct {
ObjectID string `gorm:"size:64;index" json:"-"`
ObjectID string `gorm:"size:96;index" json:"-"`
ID uint `gorm:"primaryKey" json:"id"`
PostID uint `gorm:"index;not null;default:0" json:"post_id"` // 0=草稿未绑定
UserID uint `gorm:"index;not null" json:"user_id"`
Name string `gorm:"size:256;not null" json:"name"` // 原始文件名
StoredName string `gorm:"size:64;not null" json:"-"` // 磁盘文件名
Name string `gorm:"size:256;not null" json:"name"` // 原始文件名
StoredName string `gorm:"size:64;not null" json:"-"` // 磁盘文件名(blob 去重后即内容 hash)
BlobHash string `gorm:"size:64;not null;default:'';index" json:"-"` // 内容寻址 hash;空=历史文件,不走引用计数
MIME string `gorm:"size:128;not null;default:application/octet-stream" json:"mime"`
Size int `gorm:"not null;default:0" json:"size"`
PricePoints int `gorm:"not null;default:0" json:"price_points"` // 0=免费
@@ -666,13 +667,14 @@ const (
AttachmentSourceLibraryContent = "library_content" // 书库正文内容插图
)
// Attachment 用户上传的附件(当前仅头像,全部为 WebP)
// Attachment 用户上传的附件(头像与帖子插图等公开图片)
type Attachment struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index:idx_attachment_user_kind,priority:1;not null" json:"user_id"`
Kind string `gorm:"size:16;index:idx_attachment_user_kind,priority:2;not null" json:"kind"`
Source string `gorm:"size:24;not null;default:'post'" json:"source"`
URL string `gorm:"size:512;not null" json:"url"`
BlobHash string `gorm:"size:64;not null;default:'';index" json:"-"` // 内容寻址 hash;空=历史文件,不走引用计数
MIME string `gorm:"size:32;not null;default:image/webp" json:"mime"`
Size int `gorm:"not null;default:0" json:"size"` // 裁剪后文件字节数(大小限制以此为准)
Width int `gorm:"not null;default:0" json:"width"`
@@ -680,6 +682,20 @@ type Attachment struct {
CreatedAt time.Time `gorm:"index" json:"created_at"`
}
// Blob 内容寻址物理文件:对【最终存储字节】算 SHA-256,同内容多次上传共享一份底层存储,
// 业务行(Attachment/PostAttachment 及未来的聊天文件消息)只引用 hash,按引用计数回收。
// 公开(帖子插图/头像)与私有(付费附件、未来私聊文件)是两个互不可见的命名空间:
// 同字节也各自存一份,避免私有文件借公开链接(/api/media/<id>)被越权读取。
type Blob struct {
Hash string `gorm:"primaryKey;size:64" json:"-"` // 最终存储字节的 SHA-256 hex(图片为转码后的字节)
Public bool `gorm:"primaryKey" json:"-"`
RefCount int64 `gorm:"not null;default:0" json:"-"`
Size int `gorm:"not null;default:0" json:"-"`
MIME string `gorm:"size:128;not null;default:''" json:"-"`
ObjectID string `gorm:"size:96;not null;default:''" json:"-"` // S3 后端的 StoredObject ID;本地后端为空
CreatedAt time.Time `json:"-"`
}
// UserBoard 板块管理员的板块授权(多对多;仅 role=board_admin 的行生效)
type UserBoard struct {
ID uint `gorm:"primaryKey" json:"id"`