feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等

新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
This commit is contained in:
2026-10-01 03:06:05 +08:00
parent 7fbd6ba699
commit ff2ab286fb
141 changed files with 7527 additions and 1407 deletions

View File

@@ -18,7 +18,7 @@ func newLibraryTestService(t *testing.T) (*LibraryService, string) {
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(&model.LibraryDoc{}, &model.LibraryFile{}, &model.LibrarySection{}); err != nil {
if err := db.AutoMigrate(&model.User{}, &model.LibraryDoc{}, &model.LibraryFile{}, &model.LibrarySection{}); err != nil {
t.Fatalf("migrate: %v", err)
}
dir := t.TempDir()
@@ -45,6 +45,7 @@ func TestLibraryNormalize(t *testing.T) {
{"标题为空", func(i *LibraryInput) { i.Title = " " }, "标题不能为空"},
{"标题超限", func(i *LibraryInput) { i.Title = strings.Repeat("书", 201) }, "标题不能超过"},
{"介绍超限", func(i *LibraryInput) { i.Description = strings.Repeat("介", 20001) }, "介绍不能超过"},
{"作者超限", func(i *LibraryInput) { i.Author = strings.Repeat("作", 101) }, "作者不能超过"},
{"合法", func(i *LibraryInput) {}, ""},
}
for _, tc := range cases {
@@ -63,6 +64,58 @@ func TestLibraryNormalize(t *testing.T) {
}
}
func TestLibraryAuthorAndCreator(t *testing.T) {
s, _ := newLibraryTestService(t)
user := &model.User{Username: "curator", Nickname: "馆长", Avatar: "/a.png"}
if err := s.db.Create(user).Error; err != nil {
t.Fatalf("create user: %v", err)
}
pub := true
doc, err := s.Create(&LibraryInput{
Slug: "authored", Title: "署名本", Author: " 鲁迅 ", Published: &pub,
}, user.ID)
if err != nil {
t.Fatalf("create: %v", err)
}
if doc.Author != "鲁迅" || doc.CreatorID != user.ID {
t.Fatalf("作者应裁剪、创建者应落库,got %+v", doc)
}
list, err := s.ListPublished()
if err != nil {
t.Fatalf("list: %v", err)
}
if len(list) != 1 || list[0].Author != "鲁迅" {
t.Fatalf("列表作者异常: %+v", list)
}
if c := list[0].Creator; c == nil || c.ID != user.ID || c.Nickname != "馆长" {
t.Fatalf("列表创建者摘要异常: %+v", c)
}
detail, err := s.GetPublishedBySlug("authored")
if err != nil {
t.Fatalf("detail: %v", err)
}
if detail.Creator == nil || detail.Creator.Username != "curator" {
t.Fatalf("详情创建者摘要异常: %+v", detail.Creator)
}
// 作者可清空;更新不改变创建者归属
if _, err := s.Update(doc.ID, &LibraryInput{
Slug: "authored", Title: "署名本", Author: "", Published: &pub,
}); err != nil {
t.Fatalf("update: %v", err)
}
var again model.LibraryDoc
if err := s.db.First(&again, doc.ID).Error; err != nil {
t.Fatalf("reload: %v", err)
}
if again.Author != "" || again.CreatorID != user.ID {
t.Fatalf("更新后作者/创建者异常: %+v", again)
}
}
func TestLibrarySlugUnique(t *testing.T) {
s, _ := newLibraryTestService(t)
if _, err := s.Create(&LibraryInput{Slug: "epub-latest", Title: "A"}); err != nil {