orderBy('created_at', 'desc'); // 筛选条件 if ($request->filled('user_id')) { $query->where('user_id', $request->user_id); } if ($request->filled('module')) { $query->where('module', $request->module); } if ($request->filled('purpose')) { $query->where('purpose', $request->purpose); } if ($request->filled('mime_type')) { $query->where('mime_type', 'like', "%{$request->mime_type}%"); } if ($request->filled('original_name')) { $query->where('original_name', 'like', "%{$request->original_name}%"); } if ($request->filled('status')) { $query->where('status', $request->status); } if ($request->filled('date_range')) { $dates = explode(',', $request->date_range); if (count($dates) === 2) { $query->whereBetween('created_at', [$dates[0], $dates[1]]); } } if ($request->filled('keyword')) { $keyword = $request->keyword; $query->where(function ($q) use ($keyword) { $q->where('original_name', 'like', "%{$keyword}%") ->orWhere('description', 'like', "%{$keyword}%") ->orWhere('module', 'like', "%{$keyword}%") ->orWhere('purpose', 'like', "%{$keyword}%"); }); } $perPage = $request->input('limit', 20); $files = $query->paginate($perPage); // 添加额外信息 $files->getCollection()->transform(function ($file) { $file->url = $file->getUrl(); $file->size_formatted = $file->size_formatted; $file->icon = $file->icon; $file->exists = $file->exists(); return $file; }); return response()->json([ 'code' => 200, 'data' => [ 'list' => $files->items(), 'total' => $files->total(), 'current_page' => $files->currentPage(), 'last_page' => $files->lastPage(), ], 'message' => 'success' ]); } /** * 文件详情 */ public function show(string $id) { $file = File::with('user')->find($id); if (!$file) { return response()->json([ 'code' => 404, 'message' => '文件不存在' ], 404); } $file->url = $file->getUrl(); $file->size_formatted = $file->size_formatted; $file->icon = $file->icon; $file->exists = $file->exists(); return response()->json([ 'code' => 200, 'data' => $file, 'message' => 'success' ]); } /** * 上传文件 */ public function upload(FileUploadRequest $request) { try { $file = $request->file('file'); $module = $request->input('module', 'default'); $purpose = $request->input('purpose', 'upload'); $description = $request->input('description'); // 生成存储文件名 $originalName = $file->getClientOriginalName(); $extension = $file->getClientOriginalExtension(); $mimeType = $file->getMimeType(); $size = $file->getSize(); $storageName = Str::uuid() . '.' . $extension; $path = 'uploads/' . date('Y/m/d'); // 存储文件 $disk = config('filesystems.default', 'public'); $filePath = $file->storeAs($path, $storageName, $disk); // 创建文件记录 $fileRecord = File::create([ 'user_id' => auth()->id(), 'original_name' => $originalName, 'storage_name' => $storageName, 'path' => $path, 'url' => Storage::disk($disk)->url($filePath), 'mime_type' => $mimeType, 'size' => $size, 'extension' => $extension, 'disk' => $disk, 'module' => $module, 'purpose' => $purpose, 'description' => $description, 'status' => 'active', ]); $fileRecord->url = $fileRecord->getUrl(); $fileRecord->size_formatted = $fileRecord->size_formatted; $fileRecord->icon = $fileRecord->icon; return response()->json([ 'code' => 200, 'data' => $fileRecord, 'message' => '文件上传成功' ]); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '文件上传失败: ' . $e->getMessage() ], 500); } } /** * 批量上传文件 */ public function batchUpload(Request $request) { $request->validate([ 'files' => 'required|array|min:1|max:10', 'files.*' => 'file|max:10240', // 10MB per file 'module' => 'nullable|string|max:50', 'purpose' => 'nullable|string|max:50', ]); $uploadedFiles = []; $failedFiles = []; foreach ($request->file('files') as $file) { try { $originalName = $file->getClientOriginalName(); $extension = $file->getClientOriginalExtension(); $storageName = Str::uuid() . '.' . $extension; $path = 'uploads/' . date('Y/m/d'); $disk = config('filesystems.default', 'public'); $filePath = $file->storeAs($path, $storageName, $disk); $fileRecord = File::create([ 'user_id' => auth()->id(), 'original_name' => $originalName, 'storage_name' => $storageName, 'path' => $path, 'url' => Storage::disk($disk)->url($filePath), 'mime_type' => $file->getMimeType(), 'size' => $file->getSize(), 'extension' => $extension, 'disk' => $disk, 'module' => $request->input('module', 'default'), 'purpose' => $request->input('purpose', 'upload'), 'status' => 'active', ]); $uploadedFiles[] = $fileRecord; } catch (\Exception $e) { $failedFiles[] = [ 'name' => $originalName ?? '未知文件', 'error' => $e->getMessage(), ]; } } return response()->json([ 'code' => 200, 'data' => [ 'uploaded_count' => count($uploadedFiles), 'failed_count' => count($failedFiles), 'uploaded_files' => $uploadedFiles, 'failed_files' => $failedFiles, ], 'message' => "批量上传完成,成功 {$uploadedCount} 个,失败 {$failedCount} 个" ]); } /** * 更新文件信息 */ public function update(Request $request, string $id) { $file = File::find($id); if (!$file) { return response()->json([ 'code' => 404, 'message' => '文件不存在' ], 404); } $request->validate([ 'original_name' => 'nullable|string|max:255', 'description' => 'nullable|string|max:500', 'module' => 'nullable|string|max:50', 'purpose' => 'nullable|string|max:50', 'status' => 'nullable|string|in:active,inactive,deleted', ]); try { $file->update($request->only([ 'original_name', 'description', 'module', 'purpose', 'status' ])); $file->url = $file->getUrl(); $file->size_formatted = $file->size_formatted; $file->icon = $file->icon; return response()->json([ 'code' => 200, 'data' => $file, 'message' => '文件信息更新成功' ]); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '更新失败: ' . $e->getMessage() ], 500); } } /** * 删除文件 */ public function destroy(string $id) { $file = File::find($id); if (!$file) { return response()->json([ 'code' => 404, 'message' => '文件不存在' ], 404); } try { // 删除物理文件 $file->deletePhysicalFile(); // 删除数据库记录 $file->delete(); return response()->json([ 'code' => 200, 'message' => '文件删除成功' ]); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '删除失败: ' . $e->getMessage() ], 500); } } /** * 批量删除文件 */ public function batchDelete(Request $request) { $request->validate([ 'file_ids' => 'required|array|min:1', 'file_ids.*' => 'integer|exists:files,id', ]); $successCount = 0; $failedFiles = []; try { foreach ($request->file_ids as $fileId) { $file = File::find($fileId); if (!$file) { $failedFiles[] = ['id' => $fileId, 'reason' => '文件不存在']; continue; } try { $file->deletePhysicalFile(); $file->delete(); $successCount++; } catch (\Exception $e) { $failedFiles[] = [ 'id' => $fileId, 'name' => $file->original_name, 'reason' => $e->getMessage(), ]; } } return response()->json([ 'code' => 200, 'data' => [ 'success_count' => $successCount, 'failed_files' => $failedFiles, ], 'message' => "批量删除成功,成功 {$successCount} 个" ]); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '批量删除失败: ' . $e->getMessage() ], 500); } } /** * 下载文件 */ public function download(string $id) { $file = File::find($id); if (!$file) { return response()->json([ 'code' => 404, 'message' => '文件不存在' ], 404); } if (!$file->exists()) { return response()->json([ 'code' => 404, 'message' => '物理文件不存在' ], 404); } try { $path = $file->getFullPathAttribute(); $disk = $file->disk; return Storage::disk($disk)->download($path, $file->original_name); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '文件下载失败: ' . $e->getMessage() ], 500); } } /** * 预览文件 */ public function preview(string $id) { $file = File::find($id); if (!$file) { return response()->json([ 'code' => 404, 'message' => '文件不存在' ], 404); } if (!$file->exists()) { return response()->json([ 'code' => 404, 'message' => '物理文件不存在' ], 404); } // 检查文件类型是否支持预览 $previewableTypes = [ 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/svg+xml', 'image/webp', 'application/pdf', 'text/plain', 'text/html', 'text/css', 'text/javascript', 'application/json', 'application/xml', ]; if (!in_array($file->mime_type, $previewableTypes)) { return response()->json([ 'code' => 400, 'message' => '该文件类型不支持预览' ], 400); } try { $path = $file->getFullPathAttribute(); $disk = $file->disk; $content = Storage::disk($disk)->get($path); return response($content) ->header('Content-Type', $file->mime_type) ->header('Content-Disposition', 'inline; filename="' . $file->original_name . '"'); } catch (\Exception $e) { return response()->json([ 'code' => 500, 'message' => '文件预览失败: ' . $e->getMessage() ], 500); } } /** * 获取文件统计 */ public function statistics(Request $request) { $userId = $request->input('user_id'); $stats = File::getStatistics($userId); return response()->json([ 'code' => 200, 'data' => $stats, 'message' => 'success' ]); } /** * 获取模块列表 */ public function getModules() { $modules = File::select('module') ->distinct() ->orderBy('module', 'asc') ->pluck('module'); return response()->json([ 'code' => 200, 'data' => $modules, 'message' => 'success' ]); } /** * 清理过期文件 */ public function cleanup(Request $request) { $request->validate([ 'days' => 'nullable|integer|min:1|max:365', ]); $days = $request->input('days', 30); $result = File::cleanupExpired($days); return response()->json([ 'code' => 200, 'data' => $result, 'message' => "清理完成,删除 {$result['deleted_count']} 个文件" ]); } /** * 获取上传配置 */ public function getUploadConfig() { $config = [ 'max_size' => 10240, // 10MB in KB 'allowed_types' => [ 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/svg+xml', 'image/webp', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'text/plain', 'text/csv', 'application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/json', 'application/xml', ], 'allowed_extensions' => [ 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'zip', 'rar', '7z', 'json', 'xml', ], 'max_files_per_request' => 10, ]; return response()->json([ 'code' => 200, 'data' => $config, 'message' => 'success' ]); } }