React+Laravel实现PDF下载时文件损坏的问题排查
问题场景
使用React前端+Laravel后端架构,尝试下载存储在public/uploads目录下的PDF文件,前后端代码如下,但下载的文件损坏且字节数远小于原文件。
前端代码
const downloadFile = async (attachmentName) => { try { const response = await fetch(`/attachments/${attachmentName}`); // Check if the response is OK if (!response.ok) { throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); } // Create a blob URL for the file content const url = window.URL.createObjectURL(await response.blob()); // Create a temporary link element to trigger the download const link = document.createElement('a'); link.href = url; link.setAttribute('download', attachmentName); // Append the link to the document body and trigger the download document.body.appendChild(link); link.click(); // Clean up document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error) { console.error('Error downloading attachment:', error); } }
<a href="#" onClick={() => downloadFile(job.attachments)}> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#b72424" class="bi bi-file-earmark-pdf-fill" viewBox="0 0 16 16"> <path d="M5.523 12.424q.21-.124.459-.238a8 8 0 0 1-.45.606c-.28.337-.498.516-.635.572l-.035.012a.3.3 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548m2.455-1.647q-.178.037-.356.078a21 21 0 0 0 .5-1.05 12 12 0 0 0 .51.858q-.326.048-.654.114m2.525.939a4 4 0 0 1-.435-.41q.344.007.612.054c.317.057.466.147.518.209a.1.1 0 0 1 .026.064.44.44 0 0 1-.06.2.3.3 0 0 1-.094.124.1.1 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256M8.278 6.97c-.04.244-.108.524-.2.829a5 5 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.5.5 0 0 1 .145-.04c.013.03.028.092.032.198q.008.183-.038.465z"/> <path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2m5.5 1.5v2a1 1 0 0 0 1 1h2zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.7 11.7 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.86.86 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.84.84 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.8 5.8 0 0 0-1.335-.05 11 11 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.24 1.24 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a20 20 0 0 1-1.062 2.227 7.7 7.7 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103"/> </svg> {getAttachmentName(job.attachments)} </a>
后端代码
路由
Route::get('/attachments/{attachment}', [AttachmentController::class, 'show'])->name('attachments.show');
控制器
public function show($attachment) { // Check if the file exists if (!Storage::disk('public')->exists($attachment)) { abort(404); // Or return a suitable error response } // Retrieve the file contents $fileContents = Storage::disk('public')->get($attachment); // Return the file as a response with the appropriate headers return response($fileContents, 200) ->header('Content-Type', 'application/pdf') ->header('Content-Disposition', 'attachment; filename="' . basename($attachment) . '"'); }
问题
文件可触发下载,但下载后损坏,字节数远小于原文件。
原因分析及解决方案
1. 存储路径不匹配
文件存放在项目根目录的public/uploads,但Laravel的Storage::disk('public')默认指向storage/app/public目录,而非项目根的public文件夹。如果未执行php artisan storage:link创建软链接,或传入的attachmentName未包含uploads/前缀,会导致读取错误路径的文件,返回非预期内容。
解决方案:
- 若文件确实在
public/uploads,改用public_path()直接定位文件:
public function show($attachment) { $filePath = public_path("uploads/{$attachment}"); if (!file_exists($filePath)) { abort(404); } return response()->download($filePath, basename($filePath), [ 'Content-Type' => 'application/pdf', ]); }
- 若要使用
Storage,将文件移至storage/app/public/uploads,执行php artisan storage:link创建软链接,同时修改控制器路径:
public function show($attachment) { $filePath = "uploads/{$attachment}"; if (!Storage::disk('public')->exists($filePath)) { abort(404); } return Storage::disk('public')->download($filePath); }
2. 二进制文件读取方式错误
Storage::get()会将文件内容当作字符串读取,PDF这类二进制文件易出现编码转换或截断问题,导致文件损坏。
解决方案:
使用Laravel内置的response()->download()或Storage::download()方法,它们会自动处理二进制文件的读取和响应头设置,避免手动读取内容的错误。
3. 前端响应处理优化
可添加Accept头明确告知后端需要二进制文件,避免潜在的内容协商问题:
const response = await fetch(`/attachments/${attachmentName}`, { headers: { 'Accept': 'application/pdf' } });
内容的提问来源于stack exchange,提问作者Felicity
相关产品推荐
相关产品推荐

