ensurePublicDiskReady()) { return $early; } if (! $request->hasFile('file')) { return response()->json([ 'message' => '未收到文件,请使用 multipart 表单字段名 file。', ], 422); } $uploaded = $request->file('file'); if (! $uploaded->isValid()) { return response()->json([ 'message' => '上传未通过校验:'.$uploaded->getErrorMessage(), ], 422); } try { $stored = $this->storeUploadedFileAsUniqueName($uploaded, 'uploads', 1024); } catch (ValidationException $e) { throw $e; } catch (Throwable $e) { report($e); Log::error('upload_putfile_failed', [ 'message' => $e->getMessage(), 'public_path' => storage_path('app/public'), ]); return response()->json([ 'message' => '文件保存失败', 'detail' => config('app.debug') ? $e->getMessage() : null, ], 500); } return response()->json([ 'path' => $stored['path'], 'url' => url('/storage/'.$stored['path']), 'mime' => self::jsonSafeString($stored['mime']), 'size' => $uploaded->getSize(), ]); } /** 避免 mime 等字段含非法 UTF-8 导致 json_encode 抛错成 500 */ private static function jsonSafeString(string $value): string { if ($value === '') { return ''; } if (function_exists('mb_scrub')) { return mb_scrub($value, 'UTF-8'); } $clean = @iconv('UTF-8', 'UTF-8//IGNORE', $value); return $clean !== false ? $clean : 'application/octet-stream'; } /** * 删除 public 磁盘下已上传文件(仅 uploads/ 根目录内扁平文件)。仅超级管理员。 */ public function remove(Request $request): JsonResponse { abort_unless($request->user()?->isSuperAdmin(), 403, '仅超级管理员可删除上传文件'); $data = $request->validate([ 'path' => ['required', 'string', 'max:500'], ]); $path = str_replace('\\', '/', trim($data['path'])); if (str_contains($path, '..') || ! str_starts_with($path, 'uploads/')) { return response()->json(['message' => '无效路径'], 422); } if (! preg_match('#^uploads/[a-zA-Z0-9][a-zA-Z0-9._-]*$#', $path)) { return response()->json(['message' => '无效路径'], 422); } if (! Storage::disk('public')->exists($path)) { return response()->json(['message' => '文件不存在'], 404); } Storage::disk('public')->delete($path); return response()->json(['message' => '已删除']); } }