['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'], 'document' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'], 'video' => ['mp4', 'avi', 'mov', 'wmv'], 'audio' => ['mp3', 'wav', 'ogg'], ]; // 文件大小限制(单位:MB) private $maxSizes = [ 'image' => 10, 'document' => 20, 'video' => 100, 'audio' => 50, ]; /** * 获取当前认证的用户 */ protected function getAuthenticatedUser() { return request()->user; // 从中间件注入 } /** * 获取用户类型 */ protected function getUserType() { return request()->user_type; // 从中间件注入 } /** * 获取用户ID */ protected function getUserId() { $user = $this->getAuthenticatedUser(); return $user ? $user->id : null; } /** * 保存文件记录 */ private function saveFileRecord($request, $file, $path, $url, $type) { $user = $this->getAuthenticatedUser(); $userType = $this->getUserType(); if (!$user) { throw new \Exception('User not authenticated'); } // 创建文件记录 $fileRecord = \App\Models\File::create([ 'user_id' => $user->id, 'user_type' => $userType, 'original_name' => $file->getClientOriginalName(), 'path' => $path, 'url' => $url, 'type' => $type, 'mime_type' => $file->getMimeType(), 'size' => $file->getSize(), 'disk' => $this->getDisk(), 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), ]); return $fileRecord; } /** * 删除文件 */ public function delete(Request $request) { $request->validate([ 'path' => 'required|string', ]); $path = $request->input('path'); $user = $this->getAuthenticatedUser(); $userType = $this->getUserType(); // 验证用户权限 if ($userType !== 'admin') { $fileRecord = \App\Models\File::where('path', $path) ->where('user_id', $user->id) ->where('user_type', $userType) ->first(); if (!$fileRecord) { return response()->json([ 'code' => 403, 'message' => '文件不存在或权限不足', 'data' => null ], 403); } } try { if (Storage::disk($this->getDisk())->exists($path)) { Storage::disk($this->getDisk())->delete($path); // 删除数据库记录 \App\Models\File::where('path', $path)->delete(); return response()->json([ 'code' => 200, 'message' => '文件删除成功', 'data' => null ]); } return response()->json([ 'code' => 404, 'message' => '文件不存在', 'data' => null ], 404); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '删除失败: ' . $e->getMessage(), 'data' => null ], 500); } } /** * 获取文件列表(管理员用) */ public function list(Request $request) { // 验证管理员权限 if ($this->getUserType() !== 'admin') { return response()->json([ 'code' => 403, 'message' => '需要管理员权限', 'data' => null ], 403); } $query = \App\Models\File::query(); // 过滤条件 if ($type = $request->input('type')) { $query->where('type', $type); } if ($userType = $request->input('user_type')) { $query->where('user_type', $userType); } if ($userId = $request->input('user_id')) { $query->where('user_id', $userId); } if ($startDate = $request->input('start_date')) { $query->where('created_at', '>=', $startDate); } if ($endDate = $request->input('end_date')) { $query->where('created_at', '<=', $endDate); } $perPage = $request->input('per_page', 20); $files = $query->orderBy('created_at', 'desc')->paginate($perPage); return response()->json([ 'code' => 200, 'message' => '获取成功', 'data' => $files ]); } /** * 上传单个文件 */ public function upload(Request $request) { $request->validate([ 'file' => 'required|file', 'type' => 'in:image,document,video,audio,other', 'folder' => 'nullable|string|max:100', ]); $file = $request->file('file'); $type = $request->input('type', $this->getFileType($file)); $folder = $request->input('folder', 'uploads'); // 验证文件类型和大小 $validation = $this->validateFile($file, $type); if (!$validation['valid']) { return response()->json([ 'code' => 422, 'message' => $validation['message'], 'data' => null ], 422); } // 生成文件名 $extension = $file->getClientOriginalExtension(); $fileName = Str::random(40) . '.' . $extension; // 生成路径 $path = $this->generatePath($folder, $type); $fullPath = $path . '/' . $fileName; // 存储文件 try { // 如果是图片,可以压缩处理 if (in_array($extension, $this->allowedMimes['image'])) { $this->handleImageUpload($file, $fullPath); } else { Storage::disk($this->getDisk())->putFileAs($path, $file, $fileName); } // 获取文件URL $url = Storage::disk($this->getDisk())->url($fullPath); // 保存到数据库(如果需要) $fileRecord = $this->saveFileRecord($request, $file, $fullPath, $url, $type); return $this->success( [ // 'url' => $url, 'path' => $fullPath, 'full_url' => full_url($fullPath), 'name' => $file->getClientOriginalName(), 'size' => $file->getSize(), 'mime_type' => $file->getMimeType(), 'id' => $fileRecord->id ?? null, ], '上传成功' ); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '上传失败: ' . $e->getMessage(), 'data' => null ], 500); } } /** * 辅助方法 */ private function getFileType($file) { $extension = strtolower($file->getClientOriginalExtension()); foreach ($this->allowedMimes as $type => $extensions) { if (in_array($extension, $extensions)) { return $type; } } return 'other'; } private function validateFile($file, $type) { $extension = strtolower($file->getClientOriginalExtension()); // 检查文件类型 if ($type !== 'other' && !in_array($extension, $this->allowedMimes[$type] ?? [])) { return [ 'valid' => false, 'message' => "File type not allowed for $type" ]; } // 检查文件大小 $maxSize = ($this->maxSizes[$type] ?? 5) * 1024 * 1024; if ($file->getSize() > $maxSize) { return [ 'valid' => false, 'message' => "File size exceeds maximum allowed size for $type" ]; } return ['valid' => true, 'message' => '']; } // private function handleImageUpload($file, $path) // { // $image = Image::make($file); // // 限制最大尺寸 // if ($image->width() > 2000 || $image->height() > 2000) { // $image->resize(2000, 2000, function ($constraint) { // $constraint->aspectRatio(); // $constraint->upsize(); // }); // } // // 压缩图片质量 // $image->save(storage_path('app/public/' . $path), 80); // } private function handleImageUpload($file, $path) { $extension = strtolower($file->getClientOriginalExtension()); $tempPath = $file->getRealPath(); // 获取图片信息 $imageInfo = getimagesize($tempPath); if (!$imageInfo) { throw new \Exception('Invalid image file'); } // 根据图片类型创建资源 switch ($imageInfo[2]) { case IMAGETYPE_JPEG: $sourceImage = imagecreatefromjpeg($tempPath); break; case IMAGETYPE_PNG: $sourceImage = imagecreatefrompng($tempPath); // 保留透明度 imagealphablending($sourceImage, false); imagesavealpha($sourceImage, true); break; case IMAGETYPE_GIF: $sourceImage = imagecreatefromgif($tempPath); break; case IMAGETYPE_WEBP: if (function_exists('imagecreatefromwebp')) { $sourceImage = imagecreatefromwebp($tempPath); } else { // 如果不支持webp,直接存储原文件 Storage::disk($this->getDisk())->putFileAs(dirname($path), $file, basename($path)); return; } break; default: // 其他格式直接存储 Storage::disk($this->getDisk())->putFileAs(dirname($path), $file, basename($path)); return; } // 原始尺寸 $originalWidth = imagesx($sourceImage); $originalHeight = imagesy($sourceImage); // 新尺寸(保持宽高比) $maxWidth = 2000; $maxHeight = 2000; if ($originalWidth <= $maxWidth && $originalHeight <= $maxHeight) { // 如果图片小于限制尺寸,不调整大小 $newWidth = $originalWidth; $newHeight = $originalHeight; } else { // 计算新尺寸 $ratio = $originalWidth / $originalHeight; if ($maxWidth / $maxHeight > $ratio) { $newWidth = $maxHeight * $ratio; $newHeight = $maxHeight; } else { $newWidth = $maxWidth; $newHeight = $maxWidth / $ratio; } $newWidth = (int) $newWidth; $newHeight = (int) $newHeight; // 创建新图片 $newImage = imagecreatetruecolor($newWidth, $newHeight); // 处理PNG透明度 if ($imageInfo[2] === IMAGETYPE_PNG) { imagealphablending($newImage, false); imagesavealpha($newImage, true); $transparent = imagecolorallocatealpha($newImage, 255, 255, 255, 127); imagefilledrectangle($newImage, 0, 0, $newWidth, $newHeight, $transparent); } // 调整图片大小 imagecopyresampled($newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight); imagedestroy($sourceImage); $sourceImage = $newImage; } // 保存到临时文件 $tempFile = tempnam(sys_get_temp_dir(), 'img_'); switch ($extension) { case 'jpg': case 'jpeg': imagejpeg($sourceImage, $tempFile, 80); // 80% 质量 break; case 'png': imagepng($sourceImage, $tempFile, 8); // 压缩级别 8 break; case 'gif': imagegif($sourceImage, $tempFile); break; case 'webp': if (function_exists('imagewebp')) { imagewebp($sourceImage, $tempFile, 80); } else { imagejpeg($sourceImage, $tempFile, 80); } break; } imagedestroy($sourceImage); // 保存到存储 $content = file_get_contents($tempFile); Storage::disk($this->getDisk())->put($path, $content); // 清理临时文件 unlink($tempFile); } private function generatePath($folder, $type) { $date = date('Y/m/d'); return "{$folder}/{$type}/{$date}"; } private function getDisk() { return config('filesystems.default', 'public'); } public function getImageUrl() { return config('app.image_url', null); } }