【问题标题】:why doesn't download window appear in browser for long when I export data为什么我导出数据时浏览器中没有长时间出现下载窗口
【发布时间】:2012-04-24 12:20:38
【问题描述】:

我尝试将数据从数据库导出到 .csv。当我单击导出链接时,如果数据量很大,我很长一段时间都看不到浏览器中的保存窗口。如果脚本看起来像挂了一段时间并且在很长一段时间后可以在浏览器中看到保存窗口,那可能会很混乱。 控制器中的代码是这样的:

$this->_helper->layout->disableLayout();
        $this->_helper->viewRenderer->setNoRender();
        $fileName = $list->list_name . '.csv';

        $this->getResponse()->setHeader('Content-Type', 'text/csv; charset=utf-8')
                            ->setHeader('Content-Disposition', 'attachment; filename="'. $fileName . '"');      

        $contacts = new Contact();
        $contacts->export($listId);

Export 方法一个接一个地读取记录并打印如下:

    $fp = fopen('php://output', 'w');        
    foreach ($mongodbCursor as $subscriber) {
           $row = formRow($subscriber);
       fputcsv($fp, $row);
        }       

我在某些应用程序上看到保存窗口几乎立即出现,当您单击保存时,您会看到下载进度。

我尝试替换:

    $this->getResponse()->setHeader('Content-Type', 'text/csv; charset=utf-8')
                        ->setHeader('Content-Disposition', 'attachment; filename="'. $fileName . '"');

用这个:

    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="'. $fileName . '"');

到目前为止没有帮助。 我想知道是否可以在从数据库中一一读取所有数据之前发送标头? 感谢您的帮助。

【问题讨论】:

    标签: php zend-framework export export-to-csv


    【解决方案1】:

    嗯,我不熟悉 php://output,我的应用程序用 fopen,fwrite,fclose 将我的信息写入一个临时文件,然后我用类似的 header();选项。

                $filesize = filesize("tmp/export.csv");
                header("Content-Type: text/csv");
                header("Content-Disposition: attachment; filename=\"export.csv\"");
                header("Conent-Length: $filesize");
                readfile("tmp/export.csv");
                unlink("tmp/export.csv");
                exit;
    

    这会立即提供您浏览器的下载窗口。

    【讨论】:

    • 你这样导出的文件最大大小是多少?
    • 我现在不明白你,什么意思?这段代码是为了给cvs上面写几行同样的脚本吧。最大文件大小是无限的(在我的情况下,它读取数据库条目,即使一次导出数千个条目也没有问题)
    • 在您的代码中,我只看到某些文件的输出内容,对吧?没有从数据库中获取它的代码。我的意思是,如果从数据库中获取所有数据很长,则将其写入您稍后输出的临时文件可能需要很长时间,对吧?因此,在从数据库中获取所有数据之前,用户不会看到下载窗口。
    • 并非如此,测试了大约 2000 个库存项目(大约 30 个 varchar(128) 的数据库字段)CSV 在一秒钟内生成。
    • 如果您有 500K 或 1M 个项目,就会出现问题。在从数据库中读取所有数据之前,用户不会看到下载窗口。
    【解决方案2】:

    您可以尝试这样做:

    • 调用header函数而不是$this->getResponse()->setHeader()(响应内容可能保存在缓冲区中,仅在完成时输出-导出完成的时间)
    • 尝试直接回显内容,而不是写入 php://output(如果您在此之前设置标题,您回显的所有内容都将放在生成的 CSV 文件中)

    编辑

    fputcsv 替换为下面的print_row 之类的函数

    function print_row($row) {
            echo '"' . implode('","', $row) . '"' . "\r\n"; 
    }
    

    该函数将第一个参数作为数组获取,添加", 并回显到内容。

    【讨论】:

    • 你能告诉我如何替换: $fp = fopen('php://output', 'w'); fputcsv($fp, $row);直接回显是什么意思?您的意思是手动回显字段值、命令、新行吗?
    • 是的,我的意思是您可以直接回显每个 CSV 行,例如 "value","value2"\r\n 。我将编辑答案并添加一个您可以使用的示例函数。
    • 我尝试用头函数代替响应类的方法,现在没有帮助。
    • 好的,很好。尝试将fputcsv也替换为php://output上面的函数,看看是否有更显着的改进。
    • 我尝试将其替换为 echo '"' . implode('","', $row) . '"' 。 “\r\n”;。我没有快速显示下载窗口,页面挂了一段时间,只有在加载数据时,才会显示下载窗口,我点击确定并在它之后立即获取文件。
    【解决方案3】:
    // $file = path of file
    
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename='.basename($file));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    ob_clean();
    flush();
    readfile($file);
    exit;
    

    来源:http://php.net/manual/en/function.readfile.php

    【讨论】:

    • 您能解释一下您的想法以及我的代码有什么问题吗?
    • 我的想法强制从文件链接下载,我不知道你的代码有什么问题。
    • 它似乎没有回答我的问题。
    • 你能告诉我为什么在手册中他们 ob_clean();冲洗();设置标题后?
    【解决方案4】:

    尝试在您的 Content-Type 值中使用 application/force-download 而不是 text/csv

    header("Content-Type: application/force-download; name=\"" . $fileName . "\""); 
    

    这将强制一个实际的下载框,而不是仅仅在浏览器中显示内容

    这是来自MIME Application的一些文档:

    Mime application/force-download,通常表示为 mime application/x-force-download,通常与 PHP 代码结合使用以触发下载框,而不是用于在 Web 浏览器中显示内容。

    例如,如果您的网站上有一个文件希望用户下载而不是在 Web 浏览器中查看,您可以在文件的标题内容类型字段中输入适当的 mime 应用程序/强制下载代码。请注意,mime application/force-download 不是已注册的 MIME 类型。此外,在 PHP 代码中使用时,mime application/force-download 的首选拼写包含“x-”前缀。

    【讨论】:

    • 你的意思是使用这样的标题: header('Content-Type: application/force-download; charset=utf-8'); header('Content-Disposition: attachment; filename="'. $fileName . '"');
    • 问题似乎是我的标头仅在从数据库获取数据后才发送,在此期间标头不会发送到客户端。所以我不确定这是某种标题的问题,因为它们很长一段时间都没有发送。这似乎不是一个标准的 mime,可能会导致很多问题,
    • 当然,一旦页面完成渲染,就会发送标题。您似乎在这里面临两个不同的问题。最初的问题是如何提示下载框,我回答了。如果您想改进 CSV 生成时间,您应该发布另一个问题并加入任何相关代码。但是,是的,首先,PHP 从您的数据库中获取数据,然后生成一个 CSV 文件。 只有在一切都完成后才会发送标头。
    • 你写:“最初的问题是如何提示下载框,我回答了。”我不明白您的标头提供的优势,我尝试了您的建议: header('Content-Type: application/force-download; charset=utf-8'); header('Content-Disposition: attachment; filename="'. $fileName . '"');而且我看不到它们对我使用的任何优势: header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="'. $fileName . '"');
    【解决方案5】:

    我不知道这是否是一个好的解决方案,我没有进行太多探索,但它似乎正在工作。 在我将一些数据添加到标题之后的缓冲区之后,它会在导出之前进行控制。

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="'. $fileName . '"');
    
        echo str_pad('', ini_get('output_buffering'));      
        ob_flush();
        flush();
    
        Model_Subscriber1::exportList($listId);
    

    为了让它在我在 Zend Bootstrap.php 中添加的控制器中工作:

    /**
     * hint to the dispatcher that it should not use output buffering to capture output generated by action controllers.
     *  By default, the dispatcher captures any output and appends it to the response object body content. 
     */
    protected function _initFront()
    {
        $frontController = Zend_Controller_Front::getInstance();
        $frontController->setParam('disableOutputBuffering', 1);        
    }
    

    在这种情况下,我在浏览器中快速获取下载窗口,然后导出数据,这可能需要很长时间。我不知道这个解决方案现在是否可以接受。我很乐意在这里发表您对此的看法。 它会导致将空数据添加到导出文件时出现问题。 所以,我把它的控制器改成。

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="'. $fileName . '"');
    
        Model_Subscriber1::exportList($listId);
    

    还有 chang 函数用于导出到这样的东西:

      foreach ($mongodbCursor as $subscriber) {
               $row = formRow($subscriber);
        echo '"' . implode('","', $row) . '"' . "\r\n";
        ob_flush();
        flush();                    
    
      }       
    

    【讨论】:

      【解决方案6】:

      您的函数可能希望在 long 操作之前调用 flush / ob_flush 并让 HTTP 标头在 long 处理之前发送给客户端。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-28
        相关资源
        最近更新 更多