在PHP中显示(LONGBLOB)内容

时间:2019-04-08 22:36:39

标签: php mysql

我正在尝试显示存储在LONGBLOB中的PDF文件。当我执行代码时,我只会得到文件的名称。我想在查看器中显示PDF文件。

有人知道我该如何解决吗?

这是我的剧本:

          <?php
            $tpurchase_id = $_GET['tpurchase_id'];
            $conn = new mysqli("localhost","user","","db");
            $sql = "SELECT * FROM temp_purchase WHERE tpurchase_id= '$tpurchase_id'";

            $result = $conn->query($sql);
            if ($result->num_rows > 0) {
              while($row = $result->fetch_assoc()) {
                header("Content-type:application/pdf");

                $a=$row['content'];
                echo '<object data="data:application/pdf;base64,';
                echo base64_decode($a); 
                echo '" type="application/pdf" style="height:200px;width:60%"></object>';
              }
            }
            $conn->close();

          ?>

4 个答案:

答案 0 :(得分:2)

我认为这项艰辛的工作没有问题,提供了更多标题信息

header("Content-type: application/pdf");
header('Content-disposition: attachment; filename="thing.pdf"');
header("Content-Length: " . strlen($row['content']));

print $row['content'];

答案 1 :(得分:2)

某些操作改进和优化顺序。 假设数据库正确存储了LOB数据的全部内容。

使用ob_startob_cleanob_end_flush进行输出缓冲将提供对脚本中所需响应内容的更多控制。这将有助于减轻错误的空间或发出的警告,避免其包含在二进制输出中。 此外,这使您可以控制在响应中发送哪些header数据。

由于数据库的响应应该包含单个while($row = $result->fetch_assoc())数据行的全部,因此无需使用LONGBLOB

使用mysqli_stmt::bind_resultmysqli_stmt::fetch将减少由于提取到关联数组而导致的一些开销,因为它仅需要检索内容。如果没有结果/数据,mysqli_stmt::fetch将返回NULL或错误时返回false

我还建议使用prepared statements来防止SQL注入,并建议使用filter_var来确保用户提供的输入具有预期的数据类型并且有效。

如果浏览器可以理解content-disposition: inline,则使用content-type将要求浏览器尝试加载它,否则它将下载它。

最后,您不需要使用可能会导致意外行为的?>结尾的代码,而只需使用exit;。除非要从PHP转换到同一文件中的纯文本或标记,否则最好在PHP脚本文件中排除结束标记。

我在还使用LONGBLOB存储PDF文件并可以正常运行的MySQL数据库表上测试了以下内容。

<?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。无需输出缓冲。但是,您需要换出base64_decode才能使用base64_encode。解码采用base64编码的字符串,并将其转换为原始格式。您实际想要从数据库中获取二进制数据并将其转换为base64编码的字符串的位置,供浏览器稍后进行解码。如果文件内容已经由上传处理脚本base64_encode处理过,则不需要base64_encodebase64_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数据时出现问题。

upload-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管理物理文件的绝对路径以供查看/操作。例如,当创建可从公共位置缓存和提供缩略图的缩略图时。

答案 2 :(得分:0)

如果要使用浏览器PDF查看器,请注意,一次只能查看一个PDF。

您的代码如下:

<?php
  header("Content-type:application/pdf");
  $tpurchase_id = $_GET['tpurchase_id'];
  $conn = new mysqli("localhost","user","","db");
  $sql = "SELECT * FROM temp_purchase WHERE tpurchase_id= '$tpurchase_id'";

  $result = $conn->query($sql);
  if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc())
      ob_clean();
      flush();
      echo $row['content'];
      $conn->close();
      exit();
    }
  }
?>

答案 3 :(得分:0)

这是一个解决方案:它得到编码 LONGBLOB并解码,然后在设置标题后将其显示。它基于fyrye的答案。

<?php
    $tpurchase_id = $_GET['tpurchase_id'];
    $connection = new mysqli("localhost","user","","db");
    $sql = "SELECT content FROM temp_purchase WHERE tpurchase_id = ?";

    $statement = $connection->prepare($sql);
    $statement->bind_param("s", $tpurchase_id);
    $statement->execute();
    $statement->bind_result($pdf_encoded);

    if($statement->fetch()){
        $pdf_decoded = base64_decode($pdf_encoded);
    }
    $statement->close();

    ob_start();
    ob_clean(); //Clear the buffer
    header('content-type: application/pdf');
    echo($pdf_decoded);
    ob_end_flush(); //Output only the buffered content to the client

?>