feat: 媒体库 blob 去重存储与品牌/注册等前端调整
新增: - blob_store 二进制去重存储 + blob_dedup 测试 其它: - 后端 settings/auth/media_library/upload/operations 服务与 handler 调整 - 前端品牌(BrandLockup/BrandCropModal/BrandSeoPanel)、注册、媒体库、分析详情页、Header/SiteChrome 等更新
This commit is contained in:
@@ -25,7 +25,7 @@ func newMediaLibraryService(t *testing.T) (*UploadService, string) {
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Attachment{}, &model.User{}, &model.Post{}, &model.Comment{}); err != nil {
|
||||
if err := db.AutoMigrate(&model.Attachment{}, &model.Blob{}, &model.User{}, &model.Post{}, &model.Comment{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
dir := t.TempDir()
|
||||
@@ -315,6 +315,82 @@ func TestAdminMediaLibraryBookCategory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminMediaLibraryDedupAggregation(t *testing.T) {
|
||||
s, dir := newMediaLibraryService(t)
|
||||
|
||||
writeMediaFile(t, dir, "images/dup.webp", []byte("fake-webp-bytes"))
|
||||
|
||||
mkUser := func(username, nickname string) model.User {
|
||||
if err := s.db.Create(&model.User{Username: username, Nickname: nickname}).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
var u model.User
|
||||
if err := s.db.Where("username = ?", username).First(&u).Error; err != nil {
|
||||
t.Fatalf("load user: %v", err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
alice := mkUser("alice", "爱丽丝")
|
||||
bob := mkUser("bob", "阿Bob")
|
||||
|
||||
early := time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC)
|
||||
late := early.Add(2 * time.Hour)
|
||||
|
||||
// 同一张图被两人先后上传(内容寻址去重后共享同一文件/对象)
|
||||
mkAtt := func(u model.User, url string, at time.Time) {
|
||||
if err := s.db.Create(&model.Attachment{
|
||||
UserID: u.ID, Kind: model.AttachmentKindImage,
|
||||
URL: url, MIME: "image/webp",
|
||||
Size: 100, Width: 10, Height: 10, CreatedAt: at,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create attachment %s: %v", url, err)
|
||||
}
|
||||
}
|
||||
mkAtt(alice, "/uploads/images/dup.webp", early)
|
||||
mkAtt(bob, "/uploads/images/dup.webp", late)
|
||||
mkAtt(bob, "/api/media/rdup", early)
|
||||
mkAtt(alice, "/api/media/rdup", late)
|
||||
|
||||
items, _, err := s.AdminMediaLibrary()
|
||||
if err != nil {
|
||||
t.Fatalf("AdminMediaLibrary: %v", err)
|
||||
}
|
||||
|
||||
countByURL := map[string]int{}
|
||||
byURL := map[string]MediaLibraryItem{}
|
||||
for _, it := range items {
|
||||
countByURL[it.URL]++
|
||||
byURL[it.URL] = it
|
||||
}
|
||||
|
||||
// 本地去重文件:单个条目,上传者/时间取最早上传的爱丽丝,引用计数 2
|
||||
local, ok := byURL["/uploads/images/dup.webp"]
|
||||
if !ok {
|
||||
t.Fatal("dedup local file missing")
|
||||
}
|
||||
if countByURL["/uploads/images/dup.webp"] != 1 {
|
||||
t.Fatal("dedup local file should appear exactly once")
|
||||
}
|
||||
if local.Uploader != "爱丽丝" || local.RefCount != 2 {
|
||||
t.Fatalf("local dedup meta wrong: %+v", local)
|
||||
}
|
||||
if local.UploadedAt == nil || !local.UploadedAt.Equal(early) {
|
||||
t.Fatalf("local dedup time should be earliest upload, got %v", local.UploadedAt)
|
||||
}
|
||||
|
||||
// 远程对象同 URL 两条记录:也只出一个条目,取最早的 bob
|
||||
remote, ok := byURL["/api/media/rdup"]
|
||||
if !ok {
|
||||
t.Fatal("dedup remote object missing")
|
||||
}
|
||||
if countByURL["/api/media/rdup"] != 1 {
|
||||
t.Fatal("dedup remote object should appear exactly once")
|
||||
}
|
||||
if remote.Uploader != "阿Bob" || remote.RefCount != 2 {
|
||||
t.Fatalf("remote dedup meta wrong: %+v", remote)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaThumb(t *testing.T) {
|
||||
s, dir := newMediaLibraryService(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user