//! 媒体库 Handler — 12 个 HTTP 端点(文件 CRUD + 上传/裁剪/移动 + 文件夹管理) use axum::Extension; use axum::extract::{FromRef, Json, Multipart, Path, Query, State}; use uuid::Uuid; use erp_core::error::AppError; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext}; use crate::dto::media_dto::{ BatchDeleteReq, CreateFolderReq, CropReq, FolderResp, MediaItemResp, MediaListParams, MoveMediaReq, UpdateFolderReq, UpdateMediaItemReq, }; use crate::service::media_service; use crate::state::HealthState; // --------------------------------------------------------------------------- // 本地请求结构体 // --------------------------------------------------------------------------- #[derive(Debug, serde::Deserialize)] pub struct DeleteVersionReq { pub version: i32, } // --------------------------------------------------------------------------- // 媒体文件 CRUD // --------------------------------------------------------------------------- /// 分页查询媒体文件列表 pub async fn list_media( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.list")?; let result = media_service::list_media_items(&state, ctx.tenant_id, params).await?; Ok(Json(ApiResponse::ok(result))) } /// 上传媒体文件(multipart/form-data) pub async fn upload_media( State(state): State, Extension(ctx): Extension, mut multipart: Multipart, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; let mut file_data = None; let mut folder_id: Option = None; let mut is_public = false; let mut original_name = String::new(); let mut content_type = String::new(); while let Some(field) = multipart .next_field() .await .map_err(|e| AppError::Validation(format!("读取上传数据失败: {}", e)))? { match field.name().unwrap_or("") { "file" => { original_name = field.file_name().unwrap_or("file").to_string(); content_type = field .content_type() .unwrap_or("application/octet-stream") .to_string(); file_data = Some( field .bytes() .await .map_err(|e| AppError::Validation(format!("读取文件数据失败: {}", e)))?, ); } "folder_id" => { let text = field.text().await.unwrap_or_default(); folder_id = text.parse().ok(); } "is_public" => { let text = field.text().await.unwrap_or_default(); is_public = text == "true" || text == "1"; } _ => {} } } let data = file_data.ok_or_else(|| AppError::Validation("未找到上传文件".to_string()))?; let upload_dir = std::env::var("UPLOAD_DIR").unwrap_or_else(|_| "./uploads".to_string()); let result = media_service::upload_media( &state, ctx.tenant_id, Some(ctx.user_id), &data, &original_name, &content_type, folder_id, is_public, &upload_dir, ) .await?; Ok(Json(ApiResponse::ok(result))) } /// 获取单个媒体文件详情 pub async fn get_media( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.list")?; let result = media_service::get_media_item(&state, ctx.tenant_id, id).await?; Ok(Json(ApiResponse::ok(result))) } /// 更新媒体文件元数据 pub async fn update_media( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(mut req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; req.sanitize(); let result = media_service::update_media_item(&state, ctx.tenant_id, id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) } /// 软删除媒体文件 pub async fn delete_media( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; media_service::delete_media_item(&state, ctx.tenant_id, id, Some(ctx.user_id), req.version) .await?; Ok(Json(ApiResponse::ok(()))) } /// 移动媒体文件到指定文件夹 pub async fn move_media( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; let result = media_service::move_media(&state, ctx.tenant_id, id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) } /// 批量软删除媒体文件 pub async fn batch_delete_media( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; media_service::batch_delete(&state, ctx.tenant_id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(()))) } /// 裁剪图片并重新生成缩略图 pub async fn crop_media( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; let result = media_service::crop_media(&state, ctx.tenant_id, id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) } // --------------------------------------------------------------------------- // 文件夹管理 // --------------------------------------------------------------------------- /// 获取文件夹树形结构 pub async fn list_folders( State(state): State, Extension(ctx): Extension, ) -> Result>>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.list")?; let result = media_service::list_folders(&state, ctx.tenant_id).await?; Ok(Json(ApiResponse::ok(result))) } /// 创建文件夹 pub async fn create_folder( State(state): State, Extension(ctx): Extension, Json(mut req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; req.sanitize(); let result = media_service::create_folder(&state, ctx.tenant_id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) } /// 更新文件夹信息 pub async fn update_folder( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(mut req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; req.sanitize(); let result = media_service::update_folder(&state, ctx.tenant_id, id, Some(ctx.user_id), req).await?; Ok(Json(ApiResponse::ok(result))) } /// 删除文件夹(仅空文件夹可删除) pub async fn delete_folder( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "health.media.manage")?; media_service::delete_folder(&state, ctx.tenant_id, id, Some(ctx.user_id), req.version).await?; Ok(Json(ApiResponse::ok(()))) }