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 {
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 {