feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
This commit is contained in:
@@ -1,27 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
webpenc "github.com/gen2brain/webp"
|
||||
"golang.org/x/image/draw"
|
||||
xwebp "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
// 媒体库缩略图:管理后台网格不再直连原图(全量图片一次加载网络压力大)。
|
||||
// 服务端按需生成最长边 480px 的 WebP 缩略图,落盘 .thumbs/(按 URL 哈希命名)缓存,
|
||||
// 源文件更新后(mtime 更新)自动重建。仅覆盖本地 /uploads/ 图片;
|
||||
// 远程存储对象与解码失败(如动图 WebP)由调用方回退原图。
|
||||
// 解码/缩放/编码/原子落盘共用 image_variants.go 的图片处理核心。
|
||||
|
||||
// MediaThumbMaxSide 缩略图最长边(网格单元 ~200px,2x DPR 足够)
|
||||
const MediaThumbMaxSide = 480
|
||||
@@ -70,12 +62,7 @@ func (s *UploadService) MediaThumb(url string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var img image.Image
|
||||
if ext == ".webp" {
|
||||
img, err = xwebp.Decode(bytes.NewReader(data))
|
||||
} else {
|
||||
img, _, err = image.Decode(bytes.NewReader(data))
|
||||
}
|
||||
img, err := decodeImageBytes(data, ext == ".webp")
|
||||
if err != nil {
|
||||
return nil, err // 如动图 WebP,调用方回退原图
|
||||
}
|
||||
@@ -94,21 +81,13 @@ func (s *UploadService) MediaThumb(url string) ([]byte, error) {
|
||||
nh = MediaThumbMaxSide
|
||||
nw = max(1, w*MediaThumbMaxSide/h)
|
||||
}
|
||||
dst := image.NewRGBA(image.Rect(0, 0, nw, nh))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), img, b, draw.Src, nil)
|
||||
dst := scaleImage(img, nw, nh)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := webpenc.Encode(&buf, dst, webpenc.Options{Quality: 78}); err != nil {
|
||||
out, err := encodeLossyWebP(dst, VariantWebPQuality)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 原子落缓存(失败仅影响下次重复生成,不阻断响应)
|
||||
if err := os.MkdirAll(filepath.Dir(thumbPath), 0o755); err == nil {
|
||||
tmp := thumbPath + ".partial"
|
||||
if err := os.WriteFile(tmp, buf.Bytes(), 0o644); err == nil {
|
||||
if err := os.Rename(tmp, thumbPath); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
_ = atomicWriteFile(thumbPath, out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user