From d8e58b5f8c2db83edb4405c8cc0d60ed24f0c708 Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Sun, 31 May 2026 03:08:33 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=96=87=E7=AB=A0=E5=9B=BE=E7=89=87=20?= =?UTF-8?q?WebP=20=E8=BD=AC=E6=8D=A2=E6=94=B9=E4=B8=BA=E4=BA=8C=E5=88=86?= =?UTF-8?q?=E6=9F=A5=E6=89=BE=E8=B4=A8=E9=87=8F=EF=BC=8C=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=20=E2=89=A4=20=E5=8E=9F=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 不再仅试 quality 80,改为二分查找 (50~80) 找最高质量 ≤ 原文件大小 - 新增 encodeWebPBinary 辅助函数:先试 80,太大则二分降到 50 - 降到 50 仍超限时回退原格式,避免产生马赛克级质量 - 与头像的激进二分(最低 quality 1)区分,文章图片保底 quality 50 --- internal/controller/post_controller.go | 49 ++++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/internal/controller/post_controller.go b/internal/controller/post_controller.go index 3e83233..8a8eb68 100644 --- a/internal/controller/post_controller.go +++ b/internal/controller/post_controller.go @@ -416,22 +416,19 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { isGIF := ext == ".gif" // 强制解码验证文件是否为有效图片(防伪扩展名、图片投毒),验证失败直接拒绝 - // JPEG/PNG:解码后顺便做 WebP 压缩 + // JPEG/PNG:解码后做 WebP 压缩(二分查找质量,目标 ≤ 原文件大小) if isJPEGPNG { img, _, err := image.Decode(bytes.NewReader(data)) if err != nil { common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件") return } - // WebP 压缩存储(保留原尺寸,quality 80) - var buf bytes.Buffer - if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err == nil { - if buf.Len() > 0 && buf.Len() < len(data) { - filename := fmt.Sprintf("%d_%d.webp", uid, ts) - savePath = filepath.Join(storageDir, filename) - if err := os.WriteFile(savePath, buf.Bytes(), 0644); err == nil { - url = "/uploads/posts/" + filename - } + webpData := encodeWebPBinary(img, len(data)) + if webpData != nil && len(webpData) < len(data) { + filename := fmt.Sprintf("%d_%d.webp", uid, ts) + savePath = filepath.Join(storageDir, filename) + if err := os.WriteFile(savePath, webpData, 0644); err == nil { + url = "/uploads/posts/" + filename } } } @@ -452,7 +449,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) { } } - // 原格式存储(WebP / GIF / 转换后反而更大) + // 原格式存储(GIF / 已有 WebP / 二分压缩仍超原文件大小) if savePath == "" { filename := fmt.Sprintf("%d_%d%s", uid, ts, ext) savePath = filepath.Join(storageDir, filename) @@ -474,3 +471,33 @@ func (ctrl *PostController) applyShortcode(post *model.Post) { } } +// encodeWebPBinary 二分查找 WebP 质量,找 ≤ targetBytes 的最高质量(quality 50~80) +// 返回 nil 表示最低质量仍超限,应由调用方回退原格式 +func encodeWebPBinary(img image.Image, targetBytes int) []byte { + var buf bytes.Buffer + if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err != nil { + return nil + } + if buf.Len() <= targetBytes { + return buf.Bytes() + } + + low, high := 50, 80 + var best []byte + for low <= high { + mid := (low + high) / 2 + buf.Reset() + if err := webp.Encode(&buf, img, &webp.Options{Quality: float32(mid)}); err != nil { + return nil + } + if buf.Len() <= targetBytes { + best = make([]byte, buf.Len()) + copy(best, buf.Bytes()) + low = mid + 1 + } else { + high = mid - 1 + } + } + return best +} +