自动下载PDF提示“Failed to load PDF document”问题咨询(附PHP代码)
咱们一步步来拆解你遇到的问题,从代码逻辑到环境配置逐一排查,帮你搞定这个PDF下载失败的问题~
可能的报错原因及解决方法
1. 数据库存储的是URL而非本地文件路径
如果你的download_pdf字段存的是完整的HTTP/HTTPS URL(比如http://yourdomain.com/uploads/pdf/file.pdf),那file_exists()和readfile()根本没法处理——这两个函数只认服务器本地的文件路径(绝对路径或相对路径),不认网络地址。
解决办法:
- 先查数据库里
download_pdf的实际值,确认是本地路径(比如uploads/pdf/file.pdf)。如果确实存的是URL,要么改成存储本地路径,要么从URL里提取出本地路径部分(比如去掉域名前缀)。
2. 手动拼接路径的逻辑太脆弱
你用explode("/")拆分路径再手动拼接的方式很容易翻车:如果存储的路径不是恰好三段(比如uploads/file.pdf这种两段结构),$file_arr[2]会是未定义的,直接导致$path无效,file_exists()返回false,最终没有输出任何PDF内容,浏览器自然提示加载失败。
解决办法:
直接用数据库取出的$file作为路径就行,不需要手动拆分。为了避免相对路径的歧义,建议转为服务器绝对路径:
$file = $row["download_pdf"]; // 转换为服务器绝对路径 $absolute_path = $_SERVER['DOCUMENT_ROOT'] . '/' . $file;
3. 响应头里的文件名参数错误
你现在把完整路径$path作为下载文件名,浏览器会把整个路径当成文件名,这会导致解析混乱,甚至无法正确识别文件格式。
解决办法:filename只需要保留文件名本身,用basename()函数就能直接提取:
header("Content-disposition: attachment; filename=" . basename($absolute_path));
4. 多余输出污染了PDF内容
如果你的PHP文件开头有BOM(字节顺序标记)、空格,或者在header()之前有任何echo、print甚至注释外的空行,都会导致header()发送失败,同时这些多余内容会被加到PDF输出的开头,让浏览器无法识别有效的PDF文件。
解决办法:
- 检查PHP文件开头,确保
<?php是文件的第一个字符,前面没有任何空格或BOM。 - 在发送
header()之前,清空输出缓冲:
ob_clean(); flush();
5. 文件权限不足
就算file_exists()返回true,PHP运行的用户(比如www-data、apache)可能没有读取该PDF文件的权限,导致readfile()无法输出文件内容,浏览器拿到空的或损坏的内容就会报错。
解决办法:
- 检查PDF文件和所在目录的权限,一般文件设为
644,目录设为755,确保PHP进程有读取权限。 - 用
is_readable()替代file_exists(),同时检查文件是否可读:
if(is_readable($absolute_path)) { // 执行下载逻辑 } else { die("错误:文件不存在或无法读取"); }
修正后的完整代码
我把所有问题点都修复了,还加上了SQL注入防护和路径安全校验,你可以直接用:
<?php // 假设$conn已经正确连接数据库 $id = $_GET['id']; // 重要:必须做SQL注入防护,用预处理语句 $select = "SELECT * FROM `success-stories` where id=?"; $stmt = $conn->prepare($select); $stmt->bind_param("i", $id); // 假设id是整数类型,非整数请调整参数类型 $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); if(!$row) { die("未找到对应的记录"); } $file = $row["download_pdf"]; // 转换为服务器绝对路径 $absolute_path = $_SERVER['DOCUMENT_ROOT'] . '/' . $file; // 路径安全校验:确保文件在允许的目录内(比如uploads目录) $allowed_dir = realpath($_SERVER['DOCUMENT_ROOT'] . '/uploads'); $real_file_path = realpath($absolute_path); if(strpos($real_file_path, $allowed_dir) !== 0) { die("非法路径请求"); } // 检查文件是否可读 if(is_readable($real_file_path)) { // 清空输出缓冲,避免多余内容干扰 ob_clean(); flush(); // 设置完整的响应头 header("Content-Description: File Transfer"); header("Content-Type: application/pdf"); header("Content-Disposition: attachment; filename=\"" . basename($real_file_path) . "\""); header("Content-Transfer-Encoding: binary"); header("Expires: 0"); header("Cache-Control: must-revalidate"); header("Pragma: public"); header("Content-Length: " . filesize($real_file_path)); // 告诉浏览器文件大小,提升体验 // 输出文件内容 readfile($real_file_path); exit; // 终止脚本,避免后续输出 } else { die("错误:文件不存在或无法读取"); } ?>
额外提醒
- SQL注入风险:你的原始代码直接把
$id拼接到SQL语句里,这是非常危险的,一定要用预处理语句(上面的代码已经修复了这个问题)。 - 路径遍历攻击:如果用户能控制
id对应的download_pdf值,可能会通过../来遍历服务器上的其他文件,所以加上路径安全校验很有必要。
内容的提问来源于stack exchange,提问作者shiva kant tiwari

