This commit is contained in:
lychang
2026-03-20 02:31:07 +08:00
commit 5893cd5e31
19 changed files with 5653 additions and 0 deletions

113
src/image_processor.rs Normal file
View File

@@ -0,0 +1,113 @@
use std::path::Path;
use std::fs;
use image::{GenericImageView, ImageFormat};
use uuid::Uuid;
use crate::config::StorageConfig;
pub struct ImageProcessor {
pub config: StorageConfig,
}
impl ImageProcessor {
pub fn new(config: StorageConfig) -> Self {
// 确保目录存在
fs::create_dir_all(&config.images_dir).unwrap_or(());
fs::create_dir_all(&config.thumbnails_dir).unwrap_or(());
Self { config }
}
pub async fn save_image(&self, image_data: &[u8], filename: &str) -> Result<ImageInfo, Box<dyn std::error::Error>> {
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;
// 生成缩略图
let thumbnail_path = self.config.thumbnails_dir.join(format!("thumb_{}", new_filename));
self.generate_thumbnail(&img, &thumbnail_path, 300, 200)?;
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<(), Box<dyn std::error::Error>> {
let (width, height) = img.dimensions();
// 计算缩略图尺寸
let ratio = width as f32 / height as f32;
let (new_width, new_height) = if width > height {
(max_width, (max_width as f32 / ratio) as u32)
} else {
((max_height as f32 * ratio) as u32, max_height)
};
let thumbnail = img.thumbnail(new_width, new_height);
thumbnail.save_with_format(output_path, ImageFormat::Jpeg)?;
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 fn get_image_url(&self, filename: &str) -> String {
format!("/images/{}", filename)
}
pub fn get_thumbnail_url(&self, filename: &str) -> String {
format!("/thumbnails/thumb_{}", filename)
}
pub async fn download_and_save_image(
&self,
url: &str,
filename: &str,
) -> Result<ImageInfo, Box<dyn std::error::Error>> {
let response = reqwest::get(url).await?;
let image_data = response.bytes().await?;
self.save_image(&image_data, filename).await
}
}
#[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,
}