response()->download() 实现文件下载需传服务器绝对路径、设正确文件名防乱码、大文件用 streamdownload() 避免内存溢出,并确保文件可读及 nginx 配置兼容。

直接用 response()->download() 就能实现,但路径错、头没设对、大文件卡死——这三类问题占了 90% 的下载失败。
response()->download() 必须传绝对路径
它不认 URL,也不接受相对路径或 /storage/xxx 这种 Web 可访问路径。传进去的必须是服务器上真实存在的物理路径。
- 文件在
public/下:用public_path('uploads/report.pdf') - 文件在
storage/app/下:用storage_path('app/reports/2024.pdf'),别写成./storage/app/...或storage/app/... - 软链接(
php artisan storage:link)只影响前端请求,后端读取仍需真实路径 - PHP 进程得有该路径的读权限,尤其在 Linux 上注意用户组(如
www-data)
下载名乱码或变成 “download”?检查第二个参数和 Content-Disposition
浏览器用什么名字保存,完全取决于 response()->download($path, $filename) 的第二个参数,不是原文件名,也不是路由里的变量名。
- 中文或空格会触发 Safari / Edge 截断或乱码,建议统一处理:
Str::slug($userInput) . '.pdf' - 不要手动
header('Content-Disposition: ...'),Laravel 已封装,重复设置可能冲突 - 如果用了
response()->file(),它默认是inline,浏览器会尝试打开而非下载;要下载必须用download()
大文件下载超时、内存爆掉?换 streamDownload() 或绕过 PHP
response()->download() 会把整个文件读进内存再吐出去,100MB+ 文件极易触发 memory_limit 或 Nginx fastcgi_read_timeout。
- 改用
response()->streamDownload(),配合fopen($path, 'rb')流式读取,每次只读 8KB:
response()->streamDownload(function () use ($path) {
$fp = fopen($path, 'rb');
while (!feof($fp)) {
echo fread($fp, 8192);
}
fclose($fp);
}, $filename);
- Nginx 环境下务必加
fastcgi_buffering off;,否则它会等完整响应才转发 - 更稳方案:让 Nginx 直接发文件,用
X-Accel-Redirect(私有文件)或直接返回静态 URL(公开文件),彻底绕过 Laravel
API 路由里下载失败?重点看中间件和输出截断
API 路由默认不走 web 中间件栈,但真正影响下载的是响应是否被中途终止或覆盖。
- 别在控制器里写
return response()->stream(...); exit;——exit会跳过 Laravel 响应生命周期,导致头信息不全或连接异常关闭 - 避免在下载逻辑前后调用
dd()、dump()或其他输出函数,它们会污染二进制流 - 如果用了自定义中间件,确认它没调用
ob_start()或修改了输出缓冲行为
最常被忽略的一点:文件存在性校验不能只靠 file_exists(),还要确认 is_readable(),因为某些部署环境下文件可存在但不可读——比如 SELinux 启用时,或 Docker 容器挂载权限没配对。











