Response::download()不支持断点续传,因其底层调用readfile()或内存读取,强制返回200 OK、完整Content-Length,忽略Range头且不设Accept-Ranges/Content-Range,无法响应206状态与字节区间。

Response::download() 本身不支持断点续传,它只做一次性附件响应;要支持 Range 请求和 206 状态码,必须手动实现流式响应并处理 HTTP 头。
Response::download() 为什么不能断点续传
这个方法底层调用 readfile() 或内存读取,直接输出整个文件体,并固定返回 200 OK 状态码,不检查 Range 请求头,也不动态计算 Content-Range 和 Accept-Ranges。浏览器发起断点下载(如下载中断后重试)时会带 Range: bytes=1000-,而 download() 完全忽略它,导致续传失败或从头开始。
- 它不会设置
Accept-Ranges: bytes响应头,客户端无法判断服务端是否支持续传 - 不解析
$_SERVER['HTTP_RANGE'],更不会按字节区间切片输出 - 强制设
Content-Length为全文件大小,与部分响应冲突
如何手动实现支持断点续传的下载
需绕过 Response::download(),改用 Response::stream()(TP6.1+)或原生 PHP 流式逻辑,核心是:校验 Range、计算区间、设 206 状态、输出对应字节块。
- 先用
is_file()和is_readable()校验文件,再用filesize()获取总大小 - 读取
$_SERVER['HTTP_RANGE'],用正则提取起始/结束偏移量,例如bytes=1024-2047 - 若存在有效 Range,返回
206 Partial Content,并设Content-Range: bytes 1024-2047/1048576和Accept-Ranges: bytes - 用
fopen($file, 'rb')+fseek()定位起点,fread()按区间长度读取,边读边echo+flush() - 若无 Range,退化为完整下载:返回
200 OK,设完整Content-Length和Content-Disposition
中文文件名 + 断点续传的组合坑
断点续传逻辑本身不干预文件名,但 Content-Disposition 头在含中文时仍需 UA 适配,否则 Safari 可能拒绝解析 filename*= 导致下载失败。
立即学习“PHP免费学习笔记(深入)”;
- 不要只写
filename*=UTF-8''...,IE/Edge 旧版只认filename="..." - 推荐同时提供两个字段:
Content-Disposition: attachment; filename="file.pdf"; filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf - 编码前统一源字符集:
mb_convert_encoding($name, 'UTF-8', 'auto'),再rawurlencode() - 注意:
filename字段值不能带路径,仅保留 basename,否则部分浏览器会截断
大文件 + 断点续传的性能关键点
单纯支持 Range 不等于能稳定服务 GB 级文件——漏掉缓冲、超时或 Web 服务器配置,照样卡死或 502。
- 必须调用
ob_end_clean()清除所有输出缓冲,否则fread()输出被拦截 - 禁用 zlib 压缩:
ini_set('zlib.output_compression', 'Off'),否则流式数据被缓存住 - Nginx 需显式开启范围支持:
underscores_in_headers on;(某些版本需要),并确认未启用gzip_static干扰 - PHP 超时设为 0:
set_time_limit(0),但建议每 10MB 检查一次连接是否活跃(connection_status() === CONNECTION_NORMAL) - Apache 用户注意
mod_headers和mod_expires可能覆盖你手动设的Accept-Ranges,需显式Header set Accept-Ranges bytes
断点续传不是加个 header 就完事,它要求整个链路(PHP 输出、Web 服务器转发、客户端请求)都对 Range 敏感且不破坏字节流。最容易被忽略的是 Nginx 的默认 proxy_buffering on —— 它会把分块响应攒成整包再发,直接废掉续传能力。



















