perf(image): cache_key 单次拼接 + detect_format 零分配后缀匹配
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled

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 语义与缓存一致性。
This commit is contained in:
xfy 2026-07-15 11:48:59 +08:00
parent b269fcf205
commit 8efd8c8021

View File

@ -143,26 +143,30 @@ impl ImageParams {
} }
fn cache_key(&self, path: &str) -> String { 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 { if let Some(w) = self.w {
parts.push(format!("w={}", w)); let _ = write!(key, "|w={}", w);
} }
if let Some(h) = self.h { if let Some(h) = self.h {
parts.push(format!("h={}", h)); let _ = write!(key, "|h={}", h);
} }
if let Some(ref thumb) = self.thumb { if let Some(ref thumb) = self.thumb {
parts.push(format!("thumb={}", thumb)); let _ = write!(key, "|thumb={}", thumb);
} }
if let Some(r) = self.rotate { if let Some(r) = self.rotate {
parts.push(format!("rotate={}", r)); let _ = write!(key, "|rotate={}", r);
} }
if let Some(ref fmt) = self.format { if let Some(ref fmt) = self.format {
parts.push(format!("format={}", fmt)); let _ = write!(key, "|format={}", fmt);
} }
if let Some(q) = self.quality { if let Some(q) = self.quality {
parts.push(format!("quality={}", q)); let _ = write!(key, "|quality={}", q);
} }
parts.join("|") key
} }
/// 校验参数合法性,返回 HTTP 400 状态码表示非法。 /// 校验参数合法性,返回 HTTP 400 状态码表示非法。
@ -208,21 +212,26 @@ impl ImageParams {
} }
#[cfg(feature = "server")] #[cfg(feature = "server")]
fn detect_format(path: &str) -> image::ImageFormat { fn detect_format(path: &str) -> ImageFmt {
let lower = path.to_lowercase(); // 仅对路径后缀做大小写不敏感匹配,避免 to_lowercase() 对整条路径的 String 分配。
if lower.ends_with(".jpg") || lower.ends_with(".jpeg") { // path 形如 `uploads/2026/06/22/abc.webp`rsplit('.') 取最后一段后缀即可。
image::ImageFormat::Jpeg let ext = path.rsplit('.').next().unwrap_or("");
} else if lower.ends_with(".png") { if ext.eq_ignore_ascii_case("jpg") || ext.eq_ignore_ascii_case("jpeg") {
image::ImageFormat::Png ImageFmt::Jpeg
} else if lower.ends_with(".webp") { } else if ext.eq_ignore_ascii_case("png") {
image::ImageFormat::WebP ImageFmt::Png
} else if lower.ends_with(".gif") { } else if ext.eq_ignore_ascii_case("webp") {
image::ImageFormat::Gif ImageFmt::WebP
} else if ext.eq_ignore_ascii_case("gif") {
ImageFmt::Gif
} else { } else {
image::ImageFormat::Jpeg ImageFmt::Jpeg
} }
} }
/// detect_format 的轻量返回类型,避免在热路径上构造 image::ImageFormat。
type ImageFmt = image::ImageFormat;
#[cfg(feature = "server")] #[cfg(feature = "server")]
fn content_type(format: image::ImageFormat) -> HeaderValue { fn content_type(format: image::ImageFormat) -> HeaderValue {
match format { match format {