diff --git a/app/Http/Controllers/Api/UploadController.php b/app/Http/Controllers/Api/UploadController.php index 163e56e..777c92b 100644 --- a/app/Http/Controllers/Api/UploadController.php +++ b/app/Http/Controllers/Api/UploadController.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; use Throwable; class UploadController extends Controller @@ -89,4 +90,33 @@ class UploadController extends Controller 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' => '已删除']); + } } diff --git a/routes/api.php b/routes/api.php index bca7edd..7c247ba 100644 --- a/routes/api.php +++ b/routes/api.php @@ -85,6 +85,7 @@ Route::middleware(['auth:sanctum', 'audit.log'])->group(function () { Route::put('/dict-items/{dictItem}', [DictItemController::class, 'update']); Route::delete('/dict-items/{dictItem}', [DictItemController::class, 'destroy']); Route::post('/upload', [UploadController::class, 'store']); + Route::post('/upload/delete', [UploadController::class, 'remove']); Route::get('/map/search', [MapController::class, 'search']); Route::get('/map/reverse-geocode', [MapController::class, 'reverseGeocode']); Route::get('/study-tours', [StudyTourController::class, 'index']);