Response::download失败主因是路径非绝对物理路径、中文名未适配UA、大文件未流式传输及输出缓冲/BOM污染;须校验路径、编码文件名、分块读取、清理缓冲并显式设Content-Length。

Response::download 方法调用失败,提示“file not found”
常见原因是传入的文件路径不是服务器上可访问的**绝对物理路径**,而是相对路径、URL 或未解析的模板变量。ThinkPHP 的 Response::download 不支持 HTTP 链接,只认本地文件系统路径。
- 确保路径通过
app()->getRuntimePath()、root_path()或__DIR__拼接,例如:root_path() . 'public/uploads/file.pdf' - 下载前先用
is_file()和is_readable()校验,避免静默失败 - Windows 下注意反斜杠转义问题,统一用
str_replace('\', '/', $path)或直接用dirname(__FILE__) . DS . 'file.txt' - 如果文件在
public/下,别误用url()生成的地址 —— 那是给浏览器访问的,不是给download()用的
中文文件名乱码或被截断
浏览器对 Content-Disposition 中的中文 filename 参数兼容不一,尤其 IE 和旧版 Edge 会直接崩溃。ThinkPHP 默认没做 UA 适配,得手动干预。
- 不要直接传中文名进
download()第二个参数,先用rawurlencode()编码,再按 RFC 5987 构造 header 值 - 更稳妥的做法:用
mb_convert_encoding($name, 'UTF-8', 'auto')统一源编码,再判断 UA 决定用filename=还是filename*= - 示例片段:
header('Content-Disposition: attachment; filename="' . rawurlencode($name) . '"; filename*=UTF-8''' . rawurlencode($name)); - Chrome/Firefox 支持
filename*,IE 只吃filename,所以最好同时提供两个字段(ThinkPHP 6.1+ 原生支持该写法)
大文件下载卡死或内存溢出
Response::download 默认会把整个文件读进内存再输出,遇到几百 MB 的文件极易触发 Allowed memory size exhausted 或超时。
- 务必设置
ob_end_clean()+flush()清空输出缓冲,防止 header 被拦截 - 改用流式传输:用
fopen($file, 'rb')+fread()分块读取(如每次 8192 字节),边读边echo - 禁用输出压缩:
ini_set('zlib.output_compression', 'Off'),否则流式传输会被压缩层缓存住 - 记得设超时:
set_time_limit(0),但也要加断点校验(比如每 10MB 检查一次连接是否还活着)
下载后文件内容损坏或大小不对
本质是响应体和响应头不一致:比如 header 声明了 Content-Length,但实际输出字节数不符;或中间有意外输出(Warning、Notice、BOM)混入二进制流。
立即学习“PHP免费学习笔记(深入)”;
- 绝对禁止在
download()前有任何echo、var_dump、未捕获的异常输出 - 检查文件是否有 UTF-8 BOM:用
hexdump -C $file | head看开头是否为ef bb bf,有就删掉 - 不要依赖 ThinkPHP 自动计算
Content-Length,大文件建议自己filesize($file)并显式设置:header('Content-Length: ' . filesize($file)) - PHP 开启
output_buffering时,必须在download()前调用ob_end_clean(),否则缓冲区残留内容会污染文件头


















