From 8efd8c802163db172da06a7e46c2d3ab80de18b8 Mon Sep 17 00:00:00 2001 From: xfy Date: Wed, 15 Jul 2026 11:48:59 +0800 Subject: [PATCH] =?UTF-8?q?perf(image):=20cache=5Fkey=20=E5=8D=95=E6=AC=A1?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=20+=20detect=5Fformat=20=E9=9B=B6=E5=88=86?= =?UTF-8?q?=E9=85=8D=E5=90=8E=E7=BC=80=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cache_key:旧实现 vec![path.to_string()] + 最多 6 个 format! + join, 最坏 9 次堆分配(path clone + 6 个参数 String + Vec 指针数组 + join String)。 改用 with_capacity + write! 直写,1 次分配。每次未命中缓存的图片请求都走。 detect_format:旧实现对整条路径 to_lowercase() 分配 String,只为查后缀。 改用 rsplit('.').next() 取后缀 + eq_ignore_ascii_case 零分配匹配。 每次图片请求调用 1-2 次。 注:rotate 在 resize 之前是刻意设计(让 w/h 作用于旋转后的最终方向), 不动以保持 URL 语义与缓存一致性。 --- src/api/image.rs | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/api/image.rs b/src/api/image.rs index c330985..af5a82e 100644 --- a/src/api/image.rs +++ b/src/api/image.rs @@ -143,26 +143,30 @@ impl ImageParams { } fn cache_key(&self, path: &str) -> String { - let mut parts = vec![path.to_string()]; + use std::fmt::Write as _; + // 旧实现 vec![path.to_string()] + 最多 6 个 format! + join,最坏 9 次堆分配。 + // 单次 with_capacity + write! 直写,1 次分配。 + let mut key = String::with_capacity(path.len() + 64); + key.push_str(path); if let Some(w) = self.w { - parts.push(format!("w={}", w)); + let _ = write!(key, "|w={}", w); } if let Some(h) = self.h { - parts.push(format!("h={}", h)); + let _ = write!(key, "|h={}", h); } if let Some(ref thumb) = self.thumb { - parts.push(format!("thumb={}", thumb)); + let _ = write!(key, "|thumb={}", thumb); } if let Some(r) = self.rotate { - parts.push(format!("rotate={}", r)); + let _ = write!(key, "|rotate={}", r); } if let Some(ref fmt) = self.format { - parts.push(format!("format={}", fmt)); + let _ = write!(key, "|format={}", fmt); } if let Some(q) = self.quality { - parts.push(format!("quality={}", q)); + let _ = write!(key, "|quality={}", q); } - parts.join("|") + key } /// 校验参数合法性,返回 HTTP 400 状态码表示非法。 @@ -208,21 +212,26 @@ impl ImageParams { } #[cfg(feature = "server")] -fn detect_format(path: &str) -> image::ImageFormat { - let lower = path.to_lowercase(); - if lower.ends_with(".jpg") || lower.ends_with(".jpeg") { - image::ImageFormat::Jpeg - } else if lower.ends_with(".png") { - image::ImageFormat::Png - } else if lower.ends_with(".webp") { - image::ImageFormat::WebP - } else if lower.ends_with(".gif") { - image::ImageFormat::Gif +fn detect_format(path: &str) -> ImageFmt { + // 仅对路径后缀做大小写不敏感匹配,避免 to_lowercase() 对整条路径的 String 分配。 + // path 形如 `uploads/2026/06/22/abc.webp`,rsplit('.') 取最后一段后缀即可。 + let ext = path.rsplit('.').next().unwrap_or(""); + if ext.eq_ignore_ascii_case("jpg") || ext.eq_ignore_ascii_case("jpeg") { + ImageFmt::Jpeg + } else if ext.eq_ignore_ascii_case("png") { + ImageFmt::Png + } else if ext.eq_ignore_ascii_case("webp") { + ImageFmt::WebP + } else if ext.eq_ignore_ascii_case("gif") { + ImageFmt::Gif } else { - image::ImageFormat::Jpeg + ImageFmt::Jpeg } } +/// detect_format 的轻量返回类型,避免在热路径上构造 image::ImageFormat。 +type ImageFmt = image::ImageFormat; + #[cfg(feature = "server")] fn content_type(format: image::ImageFormat) -> HeaderValue { match format {