
本文介绍如何在php+mysql应用中,从数据库安全读取二进制文件(如pdf、jpeg),并在独立页面(如后台管理页)触发浏览器下载,涵盖http头设置、字符编码处理及常见陷阱规避。
本文介绍如何在php+mysql应用中,从数据库安全读取二进制文件(如pdf、jpeg),并在独立页面(如后台管理页)触发浏览器下载,涵盖http头设置、字符编码处理及常见陷阱规避。
在Web开发中,常需将用户上传的文件(如合同PDF、证件照片)持久化存储于MySQL的BLOB或MEDIUMBLOB字段,并在管理员后台等独立页面提供下载功能。关键在于:不能直接输出文件内容到HTML页面,而必须通过正确的HTTP响应头强制浏览器启动下载流程。 以下是一个完整、安全、可复用的实现方案。
✅ 核心步骤解析
- 数据库准备:确保文件以二进制形式存入BLOB字段(推荐使用MEDIUMBLOB支持最大16MB),同时单独存储原始文件名、MIME类型和大小(提升性能与安全性);
- 下载页面逻辑:接收文件ID(如?id=123),查询数据库获取文件内容、名称及类型;
- HTTP头精准设置:清除缓冲、声明内容类型、指定附件下载行为,并正确处理中文/特殊字符文件名;
- 输出与终止:输出二进制流后立即exit(),防止后续输出污染响应体。
? 完整示例代码(download.php)
<?php
// download.php —— 独立下载入口页(如 admin/download.php)
require_once 'db.php'; // 你的数据库连接配置
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
http_response_code(400);
die('Invalid file ID.');
}
$fileId = (int)$_GET['id'];
// 1. 安全查询:只取必要字段,避免SQL注入(使用预处理)
$stmt = $pdo->prepare("SELECT filename, mime_type, file_data, file_size FROM uploads WHERE id = ? AND status = 'active'");
$stmt->execute([$fileId]);
$file = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$file) {
http_response_code(404);
die('File not found or unavailable.');
}
$fileName = $file['filename']; // 如 "用户协议_张三.pdf"
$mimeType = $file['mime_type'] ?: 'application/octet-stream';
$fileData = $file['file_data'];
$fileSize = $file['file_size'] ?: strlen($fileData);
// 2. 清除所有可能的输出缓冲
ob_end_clean();
// 3. 设置关键HTTP响应头(顺序重要!)
header('Content-Description: File Transfer');
header('Content-Type: ' . $mimeType);
header('Content-Length: ' . $fileSize);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
// ✅ 安全处理非ASCII文件名(兼容Chrome/Firefox/Safari)
// 使用 RFC 5987 格式:filename*=UTF-8''{encoded}
$encodedName = rawurlencode($fileName);
header("Content-Disposition: attachment; filename*=UTF-8''{$encodedName}");
// 4. 输出二进制数据并终止脚本
echo $fileData;
exit();⚠️ 关键注意事项
- 绝不存储路径,只存二进制:若将文件路径存入数据库再readfile(),会暴露服务器结构且无法控制权限;
- 禁用输出缓冲干扰:ob_end_clean() 必须在header()前调用,否则“Headers already sent”错误将导致下载失败;
- MIME类型要准确:从数据库读取mime_type字段(上传时已通过finfo_file()校验),而非依赖扩展名;
- 文件大小必须精确:Content-Length 头必须与实际输出字节数一致,否则Chrome可能中断下载;
- 权限与验证:生产环境务必添加身份校验(如$_SESSION['role'] === 'admin')和文件归属检查(如user_id = ?);
- 大文件优化:若文件超50MB,建议改用readfile()配合fpassthru()流式输出,避免内存溢出。
✅ 最佳实践总结
将文件存入数据库是可行方案,但仅适用于中小文件(<20MB)及高一致性要求场景。对于高频下载或大文件,更推荐「数据库存元数据 + 文件系统存实体」架构。无论哪种方式,Content-Disposition: attachment + Content-Type + Content-Length 的黄金三头组合,始终是可靠下载的基石。
通过以上实现,你可在任意页面(如后台列表页)生成形如 <a href="download.php?id=123">下载</a> 的链接,点击即触发安全、兼容、专业的文件下载体验。



















