在ThinkPHP中下载大文件时,需要特别注意内存使用和响应处理,以避免服务器内存溢出或响应超时。以下是实现大文件下载的几种常用方法:
方法一:使用流式输出(推荐)
流式输出是处理大文件下载的方式,因为它不会将整个文件加载到内存中,而是分块读取并输出到客户端。
实现步骤
- 设置响应头:指定文件类型、文件名和下载方式。
- 分块读取文件:使用
fopen
和fread
按块读取文件内容。 - 输出文件内容:通过
echo
或fpassthru
输出文件内容。 - 关闭文件句柄:释放资源。
代码示例
public function downloadLargeFile()
{
$file = './path/to/large_file.zip'; // 文件路径
if (!file_exists($file)) {
return json(['error' => '文件不存在']);
}
// 设置响应头
$filename = basename($file);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// 清空输出缓冲区
ob_clean();
flush();
// 分块读取文件并输出
$chunkSize = 1024 * 1024; // 每次读取1MB
$handle = fopen($file, 'rb');
if ($handle === false) {
return json(['error' => '无法打开文件']);
}
while (!feof($handle)) {
echo fread($handle, $chunkSize);
flush(); // 刷新输出缓冲区
}
fclose($handle);
exit;
}
优点
- 内存占用低,适合大文件。
- 响应速度快,用户体验好。
方法二:使用 readfile
函数
readfile
是 PHP 内置函数,可以直接输出文件内容,但需要注意内存限制和超时问题。
代码示例
public function downloadLargeFile()
{
$file = './path/to/large_file.zip';
if (!file_exists($file)) {
return json(['error' => '文件不存在']);
}
// 设置响应头
$filename = basename($file);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// 清空输出缓冲区
ob_clean();
flush();
// 使用 readfile 输出文件
readfile($file);
exit;
}
缺点
- 如果文件过大,可能会占用较多内存。
- 响应时间较长时,可能触发 PHP 超时设置。
方法三:使用 X-Sendfile 或 X-Accel-Redirect(需服务器支持)
如果服务器支持 X-Sendfile
(Apache)或 X-Accel-Redirect
(Nginx),可以将文件传输任务交给服务器处理,减轻 PHP 的负担。
代码示例(Nginx)
public function downloadLargeFile()
{
$file = './path/to/large_file.zip';
if (!file_exists($file)) {
return json(['error' => '文件不存在']);
}
// 设置响应头
$filename = basename($file);
$realPath = realpath($file); // 获取文件的真实路径
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('X-Accel-Redirect: /protected/' . basename($realPath)); // Nginx 配置的别名路径
exit;
}
配置 Nginx
location /protected/ {
internal;
alias /path/to/protected/files/; # 文件真实存储路径
}
优点
- 高效,服务器直接处理文件传输。
- PHP 只负责验证和设置头信息。
缺点
- 需要服务器支持并配置。
注意事项
- 权限验证:下载前需验证用户是否有权限访问文件。
- 超时设置:调整 PHP 和 Web 服务器的超时时间,避免大文件下载中断。
- 文件路径安全:避免直接使用用户输入的文件路径,防止目录遍历攻击。
- 分块大小:流式输出时,分块大小可根据服务器性能调整,通常 1MB~4MB 为宜。
- 小文件:可以直接使用
readfile
。 - 大文件:推荐使用流式输出(方法一)。
- 服务器支持:优先使用
X-Sendfile
或X-Accel-Redirect
。
根据实际需求选择合适的方法,确保下载过程稳定、高效。