FileUploadController.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\Storage;
  6. use Intervention\Image\Facades\Image;
  7. use Illuminate\Support\Str;
  8. class FileUploadController extends Controller
  9. {
  10. // 允许的文件类型
  11. private $allowedMimes = [
  12. 'image' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
  13. 'document' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'],
  14. 'video' => ['mp4', 'avi', 'mov', 'wmv'],
  15. 'audio' => ['mp3', 'wav', 'ogg'],
  16. ];
  17. // 文件大小限制(单位:MB)
  18. private $maxSizes = [
  19. 'image' => 10,
  20. 'document' => 20,
  21. 'video' => 100,
  22. 'audio' => 50,
  23. ];
  24. /**
  25. * 获取当前认证的用户
  26. */
  27. protected function getAuthenticatedUser()
  28. {
  29. return request()->user; // 从中间件注入
  30. }
  31. /**
  32. * 获取用户类型
  33. */
  34. protected function getUserType()
  35. {
  36. return request()->user_type; // 从中间件注入
  37. }
  38. /**
  39. * 获取用户ID
  40. */
  41. protected function getUserId()
  42. {
  43. $user = $this->getAuthenticatedUser();
  44. return $user ? $user->id : null;
  45. }
  46. /**
  47. * 保存文件记录
  48. */
  49. private function saveFileRecord($request, $file, $path, $url, $type)
  50. {
  51. $user = $this->getAuthenticatedUser();
  52. $userType = $this->getUserType();
  53. if (!$user) {
  54. throw new \Exception('User not authenticated');
  55. }
  56. // 创建文件记录
  57. $fileRecord = \App\Models\File::create([
  58. 'user_id' => $user->id,
  59. 'user_type' => $userType,
  60. 'original_name' => $file->getClientOriginalName(),
  61. 'path' => $path,
  62. 'url' => $url,
  63. 'type' => $type,
  64. 'mime_type' => $file->getMimeType(),
  65. 'size' => $file->getSize(),
  66. 'disk' => $this->getDisk(),
  67. 'ip_address' => $request->ip(),
  68. 'user_agent' => $request->userAgent(),
  69. ]);
  70. return $fileRecord;
  71. }
  72. /**
  73. * 删除文件
  74. */
  75. public function delete(Request $request)
  76. {
  77. $request->validate([
  78. 'path' => 'required|string',
  79. ]);
  80. $path = $request->input('path');
  81. $user = $this->getAuthenticatedUser();
  82. $userType = $this->getUserType();
  83. // 验证用户权限
  84. if ($userType !== 'admin') {
  85. $fileRecord = \App\Models\File::where('path', $path)
  86. ->where('user_id', $user->id)
  87. ->where('user_type', $userType)
  88. ->first();
  89. if (!$fileRecord) {
  90. return response()->json([
  91. 'code' => 403,
  92. 'message' => '文件不存在或权限不足',
  93. 'data' => null
  94. ], 403);
  95. }
  96. }
  97. try {
  98. if (Storage::disk($this->getDisk())->exists($path)) {
  99. Storage::disk($this->getDisk())->delete($path);
  100. // 删除数据库记录
  101. \App\Models\File::where('path', $path)->delete();
  102. return response()->json([
  103. 'code' => 200,
  104. 'message' => '文件删除成功',
  105. 'data' => null
  106. ]);
  107. }
  108. return response()->json([
  109. 'code' => 404,
  110. 'message' => '文件不存在',
  111. 'data' => null
  112. ], 404);
  113. } catch (\Exception $e) {
  114. return response()->json([
  115. 'code' => 500,
  116. 'message' => '删除失败: ' . $e->getMessage(),
  117. 'data' => null
  118. ], 500);
  119. }
  120. }
  121. /**
  122. * 获取文件列表(管理员用)
  123. */
  124. public function list(Request $request)
  125. {
  126. // 验证管理员权限
  127. if ($this->getUserType() !== 'admin') {
  128. return response()->json([
  129. 'code' => 403,
  130. 'message' => '需要管理员权限',
  131. 'data' => null
  132. ], 403);
  133. }
  134. $query = \App\Models\File::query();
  135. // 过滤条件
  136. if ($type = $request->input('type')) {
  137. $query->where('type', $type);
  138. }
  139. if ($userType = $request->input('user_type')) {
  140. $query->where('user_type', $userType);
  141. }
  142. if ($userId = $request->input('user_id')) {
  143. $query->where('user_id', $userId);
  144. }
  145. if ($startDate = $request->input('start_date')) {
  146. $query->where('created_at', '>=', $startDate);
  147. }
  148. if ($endDate = $request->input('end_date')) {
  149. $query->where('created_at', '<=', $endDate);
  150. }
  151. $perPage = $request->input('per_page', 20);
  152. $files = $query->orderBy('created_at', 'desc')->paginate($perPage);
  153. return response()->json([
  154. 'code' => 200,
  155. 'message' => '获取成功',
  156. 'data' => $files
  157. ]);
  158. }
  159. /**
  160. * 上传单个文件
  161. */
  162. public function upload(Request $request)
  163. {
  164. $request->validate([
  165. 'file' => 'required|file',
  166. 'type' => 'in:image,document,video,audio,other',
  167. 'folder' => 'nullable|string|max:100',
  168. ]);
  169. $file = $request->file('file');
  170. $type = $request->input('type', $this->getFileType($file));
  171. $folder = $request->input('folder', 'uploads');
  172. // 验证文件类型和大小
  173. $validation = $this->validateFile($file, $type);
  174. if (!$validation['valid']) {
  175. return response()->json([
  176. 'code' => 422,
  177. 'message' => $validation['message'],
  178. 'data' => null
  179. ], 422);
  180. }
  181. // 生成文件名
  182. $extension = $file->getClientOriginalExtension();
  183. $fileName = Str::random(40) . '.' . $extension;
  184. // 生成路径
  185. $path = $this->generatePath($folder, $type);
  186. $fullPath = $path . '/' . $fileName;
  187. // 存储文件
  188. try {
  189. // 如果是图片,可以压缩处理
  190. if (in_array($extension, $this->allowedMimes['image'])) {
  191. $this->handleImageUpload($file, $fullPath);
  192. } else {
  193. Storage::disk($this->getDisk())->putFileAs($path, $file, $fileName);
  194. }
  195. // 获取文件URL
  196. $url = Storage::disk($this->getDisk())->url($fullPath);
  197. // 保存到数据库(如果需要)
  198. $fileRecord = $this->saveFileRecord($request, $file, $fullPath, $url, $type);
  199. return $this->success(
  200. [
  201. // 'url' => $url,
  202. 'path' => $fullPath,
  203. 'full_url' => full_url($fullPath),
  204. 'name' => $file->getClientOriginalName(),
  205. 'size' => $file->getSize(),
  206. 'mime_type' => $file->getMimeType(),
  207. 'id' => $fileRecord->id ?? null,
  208. ],
  209. '上传成功'
  210. );
  211. } catch (\Exception $e) {
  212. return response()->json([
  213. 'code' => 500,
  214. 'message' => '上传失败: ' . $e->getMessage(),
  215. 'data' => null
  216. ], 500);
  217. }
  218. }
  219. /**
  220. * 辅助方法
  221. */
  222. private function getFileType($file)
  223. {
  224. $extension = strtolower($file->getClientOriginalExtension());
  225. foreach ($this->allowedMimes as $type => $extensions) {
  226. if (in_array($extension, $extensions)) {
  227. return $type;
  228. }
  229. }
  230. return 'other';
  231. }
  232. private function validateFile($file, $type)
  233. {
  234. $extension = strtolower($file->getClientOriginalExtension());
  235. // 检查文件类型
  236. if ($type !== 'other' && !in_array($extension, $this->allowedMimes[$type] ?? [])) {
  237. return [
  238. 'valid' => false,
  239. 'message' => "File type not allowed for $type"
  240. ];
  241. }
  242. // 检查文件大小
  243. $maxSize = ($this->maxSizes[$type] ?? 5) * 1024 * 1024;
  244. if ($file->getSize() > $maxSize) {
  245. return [
  246. 'valid' => false,
  247. 'message' => "File size exceeds maximum allowed size for $type"
  248. ];
  249. }
  250. return ['valid' => true, 'message' => ''];
  251. }
  252. // private function handleImageUpload($file, $path)
  253. // {
  254. // $image = Image::make($file);
  255. // // 限制最大尺寸
  256. // if ($image->width() > 2000 || $image->height() > 2000) {
  257. // $image->resize(2000, 2000, function ($constraint) {
  258. // $constraint->aspectRatio();
  259. // $constraint->upsize();
  260. // });
  261. // }
  262. // // 压缩图片质量
  263. // $image->save(storage_path('app/public/' . $path), 80);
  264. // }
  265. private function handleImageUpload($file, $path)
  266. {
  267. $extension = strtolower($file->getClientOriginalExtension());
  268. $tempPath = $file->getRealPath();
  269. // 获取图片信息
  270. $imageInfo = getimagesize($tempPath);
  271. if (!$imageInfo) {
  272. throw new \Exception('Invalid image file');
  273. }
  274. // 根据图片类型创建资源
  275. switch ($imageInfo[2]) {
  276. case IMAGETYPE_JPEG:
  277. $sourceImage = imagecreatefromjpeg($tempPath);
  278. break;
  279. case IMAGETYPE_PNG:
  280. $sourceImage = imagecreatefrompng($tempPath);
  281. // 保留透明度
  282. imagealphablending($sourceImage, false);
  283. imagesavealpha($sourceImage, true);
  284. break;
  285. case IMAGETYPE_GIF:
  286. $sourceImage = imagecreatefromgif($tempPath);
  287. break;
  288. case IMAGETYPE_WEBP:
  289. if (function_exists('imagecreatefromwebp')) {
  290. $sourceImage = imagecreatefromwebp($tempPath);
  291. } else {
  292. // 如果不支持webp,直接存储原文件
  293. Storage::disk($this->getDisk())->putFileAs(dirname($path), $file, basename($path));
  294. return;
  295. }
  296. break;
  297. default:
  298. // 其他格式直接存储
  299. Storage::disk($this->getDisk())->putFileAs(dirname($path), $file, basename($path));
  300. return;
  301. }
  302. // 原始尺寸
  303. $originalWidth = imagesx($sourceImage);
  304. $originalHeight = imagesy($sourceImage);
  305. // 新尺寸(保持宽高比)
  306. $maxWidth = 2000;
  307. $maxHeight = 2000;
  308. if ($originalWidth <= $maxWidth && $originalHeight <= $maxHeight) {
  309. // 如果图片小于限制尺寸,不调整大小
  310. $newWidth = $originalWidth;
  311. $newHeight = $originalHeight;
  312. } else {
  313. // 计算新尺寸
  314. $ratio = $originalWidth / $originalHeight;
  315. if ($maxWidth / $maxHeight > $ratio) {
  316. $newWidth = $maxHeight * $ratio;
  317. $newHeight = $maxHeight;
  318. } else {
  319. $newWidth = $maxWidth;
  320. $newHeight = $maxWidth / $ratio;
  321. }
  322. $newWidth = (int) $newWidth;
  323. $newHeight = (int) $newHeight;
  324. // 创建新图片
  325. $newImage = imagecreatetruecolor($newWidth, $newHeight);
  326. // 处理PNG透明度
  327. if ($imageInfo[2] === IMAGETYPE_PNG) {
  328. imagealphablending($newImage, false);
  329. imagesavealpha($newImage, true);
  330. $transparent = imagecolorallocatealpha($newImage, 255, 255, 255, 127);
  331. imagefilledrectangle($newImage, 0, 0, $newWidth, $newHeight, $transparent);
  332. }
  333. // 调整图片大小
  334. imagecopyresampled($newImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight);
  335. imagedestroy($sourceImage);
  336. $sourceImage = $newImage;
  337. }
  338. // 保存到临时文件
  339. $tempFile = tempnam(sys_get_temp_dir(), 'img_');
  340. switch ($extension) {
  341. case 'jpg':
  342. case 'jpeg':
  343. imagejpeg($sourceImage, $tempFile, 80); // 80% 质量
  344. break;
  345. case 'png':
  346. imagepng($sourceImage, $tempFile, 8); // 压缩级别 8
  347. break;
  348. case 'gif':
  349. imagegif($sourceImage, $tempFile);
  350. break;
  351. case 'webp':
  352. if (function_exists('imagewebp')) {
  353. imagewebp($sourceImage, $tempFile, 80);
  354. } else {
  355. imagejpeg($sourceImage, $tempFile, 80);
  356. }
  357. break;
  358. }
  359. imagedestroy($sourceImage);
  360. // 保存到存储
  361. $content = file_get_contents($tempFile);
  362. Storage::disk($this->getDisk())->put($path, $content);
  363. // 清理临时文件
  364. unlink($tempFile);
  365. }
  366. private function generatePath($folder, $type)
  367. {
  368. $date = date('Y/m/d');
  369. return "{$folder}/{$type}/{$date}";
  370. }
  371. private function getDisk()
  372. {
  373. return config('filesystems.default', 'public');
  374. }
  375. public function getImageUrl()
  376. {
  377. return config('app.image_url', null);
  378. }
  379. }