V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
saveai
V2EX  ›  程序员

请问如何实现大文件夹即时浏览器下载

  •  
  •   saveai · 6 天前 · 1657 次点击

    我用的是 php ,问了一圈 AI ,告诉我使用 ZipStream ,但是我试了同样需要等待压缩的时候非常久,而不是像群晖 NAS 那样,立即开始下载,且浏览器不显示最终文件大小。 请问 v 友这是如何做到的

    public function downloadFolderNew()
    {
        $path = $this->request->param("path", "");
        if (!empty($path)) {
            // 将路径转换为项目的公共路径
            $path = str_replace("D:\\phpproject\\test\\public\\", public_path(), $path);
        }
    
        if (!is_dir($path)) {
            $this->error("路径不存在");
        }
    
        $fileName = $this->request->param("file_name", "") ?: uniqid();
        $zipFileName = $fileName . ".zip";
    
        // 设置响应头
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $zipFileName . '"');
        header('X-Accel-Buffering: no'); // 关闭 Nginx 的输出缓冲
    
        // 禁用输出缓冲区
        if (ob_get_level()) {
            ob_end_clean();
        }
        // 立即输出部分内容,浏览器开始下载
        echo "\xEF\xBB\xBF"; // 避免 PHP 输出缓冲,立即开始下载
        flush();
        // 创建一个 ZIP 流
        $zip = new ZipStream\ZipStream(null, [
            'outputStream' => 'php://output' // 将输出直接定向到浏览器
        ]);
        // 递归添加文件夹内容到 ZIP
        $this->addFolderToZip($zip, $path, '');
    
        // 结束 ZIP 流
        $zip->finish();
        exit();
    }
    
    private function addFolderToZip($zip, $folder, $zipPath)
    {
        // 创建迭代器,遍历文件夹中的文件
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );
        foreach ($files as $file) {
            $filePath = $file->getRealPath();
            $relativePath = substr($filePath, strlen($folder) + 1); // 获取文件相对路径
            if (!$file->isDir()) {
                // 确保文件路径和内容正确添加到 ZIP 中
                $zip->addFileFromPath($zipPath . $relativePath, $filePath);
            }
            // 刷新输出缓冲,逐步发送数据到浏览器
            flush();
        }
    }
    
    12 条回复    2024-09-13 08:44:40 +08:00
    kele1997
        1
    kele1997  
       6 天前
    https://go.dev/play/p/RPK_Xmq0RPK

    我用了 go 语言,是能够支持边压缩,边下载的
    dode
        2
    dode  
       6 天前
    流式处理,把 zip 的输出流设置为浏览器的输入流
    ntedshen
        3
    ntedshen  
       6 天前
    不看注释都知道是 nginx 用户。。。
    因为我写 php 的时候也没搞定那个疑似是缓存的缓存问题。。。。。。。。。。。。。。。。

    群晖没测过。。。
    mega 点击下载 zip 文件以后是前端分片下载单个文件然后前端自己打包的。。。
    我现在自己写 nas 也是造 mega 造的。。。
    okakuyang
        4
    okakuyang  
       6 天前
    因为 zip 的文件格式特性,应该是可以是实现一边压缩一边传输的。由于压缩还在进行中,程序还没有计算出最后的文件大小,所以不会开始就告诉浏览器文件大小。
    zhuangzhuang1988
        5
    zhuangzhuang1988  
       6 天前
    java 的话有个 netty
    中间插入个中间层就可以了
    https://netty.io/4.1/xref/io/netty/handler/codec/http/HttpContentEncoder.html#L194
    wxf666
        6
    wxf666  
       5 天前
    @okakuyang #4 能不能遍历下文件,提前算好总大小,以《打包存储》形式压缩,传输过程中由 gzip 压缩呢?
    billccn
        7
    billccn  
       5 天前
    你把 CompressionMethod 设置成 STORE (就是不压缩)试试?或者把 defaultDeflateLevel 调整一下。

    另外显示的大小是在 HTTP 头里的,你不提前算好所有文件+zip 开销,等 HTTP 头都发完了才开始遍历目录那肯定不会有大小的。
    leonshaw
        8
    leonshaw  
       5 天前 via Android
    有没有办法续传呢
    sunchuo
        9
    sunchuo  
       5 天前
    okakuyang
        10
    okakuyang  
       5 天前
    @wxf666 额,不是很了解 gzip 的机制,但是估计和 zip 差不多是把单个文件进行压缩,zip 设置成打包不压缩,理论上会很快生成 zip 文件,然后传输给 gzip ,感觉不是很靠谱。
    siweipancc
        11
    siweipancc  
       4 天前 via iPhone
    206 请求+队列+二进制合并
    siweipancc
        12
    siweipancc  
       4 天前 via iPhone
    需要有自定义下载条,建议楼主参考下 asmr.one 的实现。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2394 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 14:57 · PVG 22:57 · LAX 07:57 · JFK 10:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.