一些操作顺序的改进和优化。
假设数据库正确存储了LOB 数据的全部内容。
使用ob_start、ob_clean 和ob_end_flush 的输出缓冲可以更好地控制脚本中所需的响应内容。这将有助于减少二进制输出中包含错误空格或发出的警告。
此外,这允许您控制在响应中发送哪些 header 数据。
没有必要使用while($row = $result->fetch_assoc()),因为来自数据库的响应应该包含整个单个LONGBLOB 数据行。
使用mysqli_stmt::bind_result 和mysqli_stmt::fetch 将减少一些因提取到关联数组而导致的开销,因为它只需要检索内容。如果没有结果/数据,mysqli_stmt::fetch 将返回 NULL,如果出错则返回 false。
我还建议使用prepared statements 来防止SQL 注入,并使用filter_var 来确保用户提供的输入是预期的数据类型并且是有效的。
使用content-disposition: inline会要求浏览器尝试加载它,如果浏览器可以理解content-type,否则会下载它。
最后,您不需要以?> 结束代码,这可能会导致意外行为,而只需使用exit;。最好在 PHP 脚本文件中排除结束标记,除非您从 PHP 转换为同一文件中的纯文本或标记。
我针对我的 MySQL 数据库表测试了以下内容,该表也使用LONGBLOB 来存储 PDF 文件并且运行正常。
<?php /*line 1*/
ob_start(); //start output buffering immediately
$conn = new mysqli('localhost','user','','db');
if (mysqli_connect_errno()) {
exit;
}
$tpurchase_id = filter_var($_GET['tpurchase_id'], FILTER_VALIDATE_INT);
$stmt = $conn->prepare('SELECT tp.content
FROM temp_purchase AS tp
WHERE tp.tpurchase_id = ?
AND tp.content > ""'); //ensure content is not empty
if ($stmt && false !== $tpurchase_id) {
$stmt->bind_param('i', $tpurchase_id);
$stmt->execute();
$stmt->bind_result($content);
if ($stmt->fetch()) {
//a record was found, change to a PDF file
ob_clean(); //clear the buffer
header('content-type: application/pdf');
header('content-disposition: inline; filename="Test.pdf"');
echo $content;
ob_end_flush(); //output only the buffered content to the client
}
$stmt->close();
unset($content, $stmt);
}
$conn->close(); //always close the connection
while (ob_get_level() > 0) {
ob_end_clean(); //remove everything else from the buffer
}
exit;
这将导致仅将标头和内容响应发送到客户端,否则如果找不到来自数据库的结果,则会发送空白的纯文本响应。
然后可以将上述脚本用作内联对象的源代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<object data="/path/to/above_script.php?tpurchase_id=123" type="application/pdf" style="height:200px;width:60%"></object>
</body>
</html>
除了上述之外,还有其他可能导致问题的点,我们目前不知道。
- 由网络服务器(apache、nginx、IIS 等)添加或修改的标头。
- 上传表单或PHP处理脚本修改或未将完整的
LOB数据发送到数据库。
- 数据库正在截断或更改
LOB 数据。
使用上述 PHP 脚本显示内联 object(s)。不需要输出缓冲。但是,您需要更换 base64_decode 以支持使用 base64_encode。解码采用 base64 编码字符串并将其转换为原始格式。您实际上想要从数据库中获取二进制数据并将其转换为 base64 编码字符串以供浏览器稍后解码。如果文件内容已经被上传处理脚本base64_encode'd,则不需要base64_encode或base64_decode。
测试了以下内容并按预期运行。
<?php /*line 1*/
$conn = new mysqli('localhost','user','','db');
if (mysqli_connect_errno()) {
exit;
}
$tpurchase_id = filter_var($_GET['tpurchase_id'], FILTER_VALIDATE_INT);
$stmt = $conn->prepare('SELECT tp.content
FROM temp_purchase AS tp
WHERE tp.tpurchase_id = ?
AND tp.content > ""'); //ensure content is not empty
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<?php
if ($stmt && false !== $tpurchase_id) {
$stmt->bind_param('i', $tpurchase_id);
$stmt->execute();
$stmt->bind_result($content);
if ($stmt->fetch()) { ?>
<object data="data:application/pdf;base64,<?php echo base64_encode($content); ?>" type="application/pdf" style="height:200px;width:60%"></object>
<?php }
$stmt->close();
unset($content, $stmt);
}
$conn->close();
?>
</body>
</html>
为了检索多个文档,您可以选择更改 if ($stmt->fetch()) 以支持使用 while($stmt->fetch())
上传处理器建议
假设您使用问题"Auto submit is not posting data to database" 中的代码进行文件上传,我强烈建议您使用当前标准/最佳实践重写上传处理器,这也将使您的上传处理器与此答案兼容。
使用addslashes 或其他转义技术可能会导致数据库中存储的LOB 数据出现问题。我猜这就是你现在遇到的并发症的原因。
您还应该考虑 PHP 和数据库环境使用的最大数据包大小,它限制了您的应用程序可以发送或接收的数据大小,这可能导致LOB 数据被截断。由于数据包大小的限制,建议您使用send_long_data 以防止您的应用程序在传输LOB 数据时出现问题。
上传-form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<form id="target" method="post" enctype="multipart/form-data" name="frmImage" class="frmImageUpload" action="./post.php">
<input type="file" name="userfile" id="userfile" class="userfile"/>
</form>
</body>
</html>
post.php
<?php
$conn = new mysqli('localhost','user','','db');
if (mysqli_connect_errno()) {
exit;
}
if (!session_id()) {
session_start();
}
//never trust data from GLOBALS
$user_id = filter_var($_SESSION['user_id'], FILTER_VALIDATE_INT);
if (false === $user_id ||
!isset($_FILES) ||
!array_key_exists('userfile', $_FILES) ||
UPLOAD_ERR_OK !== $_FILES['userfile']['error'] ||
$_FILES['userfile']['size'] <= 0 ||
!is_uploaded_file($_FILES['userfile']['tmp_name'])
) {
//invalid user or file upload
exit;
}
//params = { 0: user_id, 1: content }
$stmt = $conn->prepare('INSERT INTO temp (user_id, content) VALUES (?, ?)');
if ($stmt) {
//bind default value as NULL
$null = null;
$stmt->bind_param('ib', $user_id, $null);
//max packet size limits can lead to partial file data being inserted
$fp = new SplFileObject($_FILES['userfile']['tmp_name'], 'rb', false);
while (!$fp->eof()) {
//use send_long_data to send the file data in chunks
//be sure the first argument matches the param index for the LOB data column
$stmt->send_long_data(1, $fp->fread(2048));
}
unset($fp);
$stmt->execute();
$stmt->close();
}
$conn->close();
作为个人推荐;多年来,我发现在数据库中存储LOB 数据会导致一些严重的问题。虽然它确实增加了应用程序内文件管理的可移植性和易用性。通过显着增加恢复数据库和硬盘驱动器 RAID 完整性所需的 I/O 时间量,它极大地阻碍了数据恢复和备份。此外,当与其他数据一起使用时,会显着增加数据库的查询和维护时间。迫使我们从SELECT * 迁移以显式避免LOB 列数据或跳过表进行优化或重新索引。最后,它还阻止了客户端缓存,而无需创建特定的 RESTful URL 来提供文件。总的来说,它变得比存储LOB 数据的努力要麻烦得多。我建议使用您的 Web 服务器来存储物理文件,并使用数据库来存储物理文件的相对路径,其中 PHP 管理物理文件的绝对路径以供查看/操作。例如,在创建可以从可公开访问的位置缓存和提供的缩略图时。