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 +} +