第一步需用curl验证网盘直链是否支持range请求,响应含accept-ranges: bytes且状态码为206才支持断点续传;百度网盘分享链接须经解析工具提取真实cdn地址。

确认服务端是否支持Range请求
在TP6.0中实现断点续传,第一步不是写代码,而是验证网盘直链是否真正支持字节范围请求——不满足这个前提,后续所有逻辑都会失效。
用curl命令测试:curl -I -H "Range: bytes=0-999" "https://your-netdisk-link.com/file.zip"。若响应头含 【Accept-Ranges: bytes】 且状态码为206,则服务端已就绪;若返回200且无Accept-Ranges,说明该链接是“假直链”,强行续传会覆盖写入或报错。
注意:百度网盘分享页生成的链接默认不支持Range,必须经解析工具(如baidu-wangpan-parse)提取出真实CDN地址后才可用。
定义下载控制器并注入Request对象
在app/controller目录下新建Download.php:
use think\Controller; use think\Request; class Download extends Controller { protected $request; public function __construct(Request $request) { $this->request = $request; } }
这一步必须显式注入Request对象,否则无法获取客户端发来的Range头信息——TP6.0的依赖注入机制在此处不可绕过。
读取文件并校验基础信息
在Download控制器中添加download方法:
public function download($filename) { $filePath = root_path() . 'public/uploads/' . $filename; if (!is_file($filePath)) { return $this->error('文件不存在'); } $fileSize = filesize($filePath); $mimeType = mime_content_type($filePath); }
务必使用filesize()而非SplFileInfo::getSize(),后者在大文件场景下可能因缓存导致数值错误;同时避免用fopen()提前打开句柄,此处仅需元数据。
解析Range请求头并计算分段参数
第一步:从Request中提取Range头
$range = $this->request->header('range');
第二步:正则匹配起始字节位置
if (empty($range) || !preg_match('/^bytes=(\d+)-(\d+)?$/', $range, $matches)) { header('HTTP/1.1 200 OK'); header("Content-Type: {$mimeType}"); header("Content-Length: {$fileSize}"); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Accept-Ranges: bytes'); readfile($filePath); exit; }
第三步:计算当前请求范围
$start = (int)$matches[1]; $end = isset($matches[2]) ? (int)$matches[2] : $fileSize - 1; $length = $end - $start + 1;
若客户端只传bytes=1048576-(无结束值),必须将$end设为$fileSize-1,否则fpassthru()会截断末尾数据。
设置响应头并流式输出
方法一:使用fopen + fread逐块输出
header('HTTP/1.1 206 Partial Content'); header("Content-Type: {$mimeType}"); header("Content-Length: {$length}"); header("Content-Range: bytes {$start}-{$end}/{$fileSize}"); header('Accept-Ranges: bytes'); header('Content-Disposition: attachment; filename="' . $filename . '"'); ob_end_clean(); $handle = fopen($filePath, 'rb'); fseek($handle, $start); while ($length > 0 && !feof($handle)) { $readLength = min(8192, $length); echo fread($handle, $readLength); $length -= $readLength; flush(); } fclose($handle); exit;
方法二:用fpassthru替代循环(更简洁)
fseek($handle, $start); fpassthru($handle); fclose($handle); exit;
【必须调用ob_end_clean()】 否则TP6.0默认开启的输出缓冲会把206状态码吞掉,变成200响应,前端无法识别为分段响应。











