use crate::config::StorageConfig; use image::GenericImageView; use std::fs; use std::path::Path; use thiserror::Error; use uuid::Uuid; #[derive(Error, Debug)] pub enum ImageProcessorError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Image processing error: {0}")] Image(#[from] image::ImageError), #[error("Invalid image data")] InvalidData, } // 手动实现 From 以支持 ? 操作符 impl From for ImageProcessorError { fn from(err: reqwest::Error) -> Self { ImageProcessorError::Io(std::io::Error::new( std::io::ErrorKind::Other, err.to_string(), )) } } impl ImageProcessorError { pub fn is_send(&self) -> bool { true } } pub struct ImageProcessor { pub config: StorageConfig, } impl ImageProcessor { pub fn new(config: StorageConfig) -> Self { // 确保目录存在 fs::create_dir_all(&config.images_dir).unwrap_or(()); Self { config } } pub async fn save_image( &self, image_data: &[u8], filename: &str, ) -> Result { let image_id = Uuid::new_v4(); let file_extension = Path::new(filename) .extension() .and_then(|ext| ext.to_str()) .unwrap_or("jpg"); let new_filename = format!("{}.{}", image_id, file_extension); let image_path = self.config.images_dir.join(&new_filename); // 保存原始图片 fs::write(&image_path, image_data)?; // 获取图片信息 let img = image::load_from_memory(image_data)?; let (width, height) = img.dimensions(); let file_size = image_data.len() as i64; // 不再生成缩略图,直接返回原图信息 Ok(ImageInfo { id: image_id, filename: new_filename, width: width as i32, height: height as i32, file_size, mime_type: self.detect_mime_type(file_extension), }) } /// 生成缩略图并保存到指定路径(使用统一尺寸) /// 此功能已被移除,直接返回成功 pub fn generate_thumbnail( &self, _img: &image::DynamicImage, _output_path: &Path, _max_width: u32, _max_height: u32, ) -> Result<(), ImageProcessorError> { // 缩略图功能已移除,此函数不再执行任何操作 Ok(()) } pub fn detect_mime_type(&self, extension: &str) -> String { match extension.to_lowercase().as_str() { "jpg" | "jpeg" => "image/jpeg".to_string(), "png" => "image/png".to_string(), "gif" => "image/gif".to_string(), "webp" => "image/webp".to_string(), _ => "application/octet-stream".to_string(), } } /// 快速调整图片尺寸(最大宽度) pub async fn resize_image_to_max(&self, image_data: &[u8], max_width: u32) -> Vec { match image::load_from_memory(image_data) { Ok(img) => { let (width, height) = img.dimensions(); if width <= max_width { return image_data.to_vec(); // 无需调整 } let new_height = (height as f32 * max_width as f32 / width as f32) as u32; let resized = img.thumbnail(max_width, new_height); let mut output = Vec::new(); if resized .write_to( &mut std::io::Cursor::new(&mut output), image::ImageOutputFormat::Jpeg(75), ) .is_ok() { output } else { image_data.to_vec() // 失败时返回原图 } } Err(_) => image_data.to_vec(), // 解码失败时返回原图 } } pub fn get_image_url(&self, filename: &str) -> String { format!("/images/{}", filename) } pub async fn download_and_save_image( &self, url: &str, filename: &str, ) -> Result { let response = reqwest::get(url).await?; let image_data = response.bytes().await?; self.save_image(&image_data, filename).await } /// 检测图片格式 fn detect_image_format(&self, image_data: &[u8]) -> Option { if image_data.len() < 12 { return None; } match &image_data[0..12] { b"\xff\xd8\xff" => Some("JPEG".to_string()), b"\x89PNG\r\n\x1a\n" => Some("PNG".to_string()), b"GIF87a" => Some("GIF87a".to_string()), b"GIF89a" => Some("GIF89a".to_string()), b"BM" => Some("BMP".to_string()), b"RIFF" if image_data.len() > 12 && &image_data[8..12] == b"WEBP" => { Some("WebP".to_string()) } _ => None, } } } #[derive(Debug)] pub struct ImageInfo { pub id: Uuid, pub filename: String, pub width: i32, pub height: i32, pub file_size: i64, pub mime_type: String, }