使用 readfile() - 推荐(最常用、最安全)

这是最推荐的方法,因为它最安全,并且性能最好,它不会将整个文件内容读入服务器的内存,而是直接以流的方式将文件发送给客户端,这对于大文件尤其重要。

网页服务器 php代码下载
(图片来源网络,侵删)

工作原理

  1. 设置必要的 HTTP 头信息,告诉浏览器这是一个文件下载,而不是一个网页。
  2. 使用 readfile() 函数,打开文件并将其内容直接输出到输出缓冲区。

代码示例 (download.php)

<?php
// 1. 指定要下载的文件路径
//    !!! 重要: 请务必使用绝对路径,并且路径要安全 !!!
$file = '/var/www/html/uploads/my_document.pdf';
// 2. 检查文件是否存在
if (!file_exists($file)) {
    die('文件不存在!');
}
// 3. 获取文件名(用于下载提示)
$filename = basename($file);
// 4. 设置 HTTP 头信息
//    Content-Disposition: attachment; filename="..."  表示这是一个附件,浏览器会弹出下载框
//    filename*=utf-8''... 用于处理非英文字符文件名,确保兼容性
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"; filename*=utf-8\'' . $filename);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
// 5. 清空并关闭输出缓冲区,确保没有多余内容输出
ob_clean();
flush();
// 6. 将文件内容读取并输出到浏览器
readfile($file);
// 7. 结束脚本执行
exit;
?>

如何使用

  1. 将上面的代码保存为 download.php 文件。
  2. 将您要下载的文件(my_document.pdf)放到服务器的某个目录,/var/www/html/uploads/
  3. 修改 $file 变量的值为你的文件的绝对路径
  4. 在浏览器中访问 http://你的域名/download.php,浏览器就会自动下载 my_document.pdf

使用 file_get_contents() - 简单但不推荐大文件

这种方法非常简单,直接将整个文件内容读入一个字符串变量,然后再输出。

⚠️ 重要警告

这种方法对于大文件非常危险! 它会将整个文件(例如一个 500MB 的视频)完全加载到服务器的内存中,如果同时有多个用户下载大文件,服务器内存很快就会被耗尽,导致服务器崩溃。

代码示例 (download_fc.php)

<?php
$file = '/var/www/html/uploads/my_document.pdf';
$filename = basename($file);
if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    // 读取整个文件到内存
    echo file_get_contents($file);
    exit;
} else {
    die('文件不存在!');
}
?>

除非你确定文件非常小(小于几MB),否则请避免使用这种方法


提供下载链接(最用户友好)

很多时候,我们并不想直接弹出下载框,而是希望用户点击一个链接后再下载,这非常简单,只需要一个标准的 HTML <a>

代码示例 (index.html)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">文件下载示例</title>
</head>
<body>
    <h1>我的文件</h1>
    <p>请点击下面的链接下载文件:</p>
    <!-- 
      直接指向文件路径即可。
      但注意:直接暴露文件路径可能会有安全隐患,用户可能猜到其他文件的路径。
      更好的做法是像方法一那样,通过一个 PHP 脚本来处理下载请求。
    -->
    <a href="/uploads/my_document.pdf" download>下载 my_document.pdf</a>
    <hr>
    <!-- 更安全的做法:通过 PHP 脚本中转 -->
    <p>或者,通过一个安全的 PHP 脚本来下载(推荐):</p>
    <a href="download.php">安全下载 my_document.pdf</a>
</body>
</html>

说明:

网页服务器 php代码下载
(图片来源网络,侵删)
  • <a href="...">:直接链接到文件,浏览器会根据文件类型决定是打开还是下载(PDF可能会在浏览器内打开)。
  • download 属性:告诉浏览器这是一个下载链接,通常会直接弹出下载框。
  • 安全提示:直接暴露文件上传目录(如 /uploads/)是不安全的,最佳实践是像方法一那样,创建一个专门的 PHP 下载处理脚本(如 download.php),然后在 HTML 中链接到这个脚本,这样,文件的实际路径就不会被用户直接看到。

安全性最佳实践

无论使用哪种方法,安全性都是最重要的。

  1. 绝对路径:始终使用文件的绝对路径(/var/www/html/files/...),而不是相对路径(../files/...),以防止路径遍历攻击。
  2. 验证文件所有权:确保用户有权下载该文件,检查用户是否已登录,或者该文件是否属于该用户。
  3. 不要暴露真实路径:不要在 HTML 或 URL 中直接显示服务器的文件系统路径,使用一个唯一的、不可猜测的下载 ID 或通过 PHP 脚本中转请求。
  4. 文件类型检查:在提供下载前,可以检查文件的 MIME 类型,确保它是一个合法的文件类型,而不是一个可执行的 PHP 脚本。
  5. 限制下载速度:对于大文件,可以限制下载速度,防止服务器带宽被恶意用户耗尽,这可以通过在 readfile() 循环中加入 usleep() 来实现。
方法 优点 缺点 适用场景
readfile() 安全、高效、内存友好 代码稍多 所有场景,尤其是大文件下载
file_get_contents() 代码简单 内存消耗大,有服务器崩溃风险 仅适用于非常小的文件
HTML <a> 用户最友好,实现简单 可能暴露路径,浏览器行为不可控 作为下载入口,但最终下载逻辑建议用 PHP 处理

对于绝大多数情况,请优先使用方法一 (readfile()),它兼顾了安全性、性能和可靠性。