You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel图片压缩下载速度优化方案咨询

批量图片压缩下载速度优化方案(Laravel)

问题背景

基于Laravel实现的批量图片压缩下载功能,浏览器显示"Content Download"阶段耗时过长,现有实现代码如下:

API路由(api.php)

Route::post('/download-images', [DownloadController::class, 'downloadImages'])->name('api.download.images');

Controller代码

public function downloadImages(Request $request)
{
    $zipFileName = $this->service->downloadImages($request);

    return response()->download($zipFileName, null, ['Content-Type: application/octet-stream','Content-Length: '. filesize($zipFileName)])->deleteFileAfterSend(true);
}

Service代码

public function downloadImages(Request $request)
{
    $imageUrls = $request->get('images');
    $type = $request->get('type') ?? 'images';

    $zip = new ZipArchive();
    $zipFileName = $type . '.zip';

    $zip = new ZipArchive();

    if ($zip->open($zipFileName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
        foreach ($imageUrls as $imageUrl) {
            $imageContent = file_get_contents($imageUrl);
            $filename = basename($imageUrl);
            $zip->addFromString($filename, $imageContent);
        }
        $zip->close();

        return $zipFileName;
    }

    return $zipFileName;
}

前端AJAX调用

function downloadImages(eleClass) {
    $("div.spanner").addClass("show");
    $("div.overlay").addClass("show");
    const imageLinks = [];
    $('ul.'+ eleClass +' li img').each(function() {
        var imageLink = $(this).attr('src');
        imageLinks.push(imageLink);
    });
    if (imageLinks.length == 0) {
        $("div.spanner").removeClass("show");
        $("div.overlay").removeClass("show");

        return;
    }

    $.ajax({
      url: '/api/download-images',
      method: 'POST',
      data: { images: imageLinks },
      xhrFields: {
        responseType: 'blob' // Set the response type to 'blob'
      },
      success: function (data, status, xhr) {
        // Handle success, e.g., show a success message
        console.log('Images downloaded successfully.');
        // Create a temporary anchor element
        var downloadLink = document.createElement('a');
        downloadLink.href = window.URL.createObjectURL(data); // Create a Blob URL for the response
        downloadLink.download = eleClass + '.zip'; // Set the desired file name

        // Programmatically trigger the download
        downloadLink.click();
        $("div.spanner").removeClass("show");
        $("div.overlay").removeClass("show");
      },
      error: function (xhr, status, error) {
        // Handle error, e.g., display an error message
        console.error('Error downloading images:', error);
        $("div.spanner").removeClass("show");
        $("div.overlay").removeClass("show");
      }
   });
}

性能瓶颈分析

  1. 同步串行下载图片:file_get_contents同步逐个下载图片,总耗时等于所有图片下载时间之和
  2. 本地磁盘IO开销:先将所有图片下载到本地并生成Zip文件,再输出给浏览器,等待时间长
  3. 前端Blob处理冗余:AJAX将响应转为Blob后再触发下载,大文件会占用大量浏览器内存,拖慢下载流程

优化方案

方案一:并行下载+流式Zip输出(核心优化)

使用Guzzle并行请求图片,同时用ZipStream直接流式输出Zip包,无需生成本地文件,彻底消除磁盘IO等待,同时并行下载大幅缩短图片获取时间。

步骤1:安装依赖

composer require maennchen/zipstream-php guzzlehttp/guzzle

步骤2:修改Controller代码

use ZipStream\ZipStream;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;

public function downloadImages(Request $request)
{
    $imageUrls = $request->get('images');
    $type = $request->get('type') ?? 'images';

    // 初始化Guzzle客户端,支持并行请求
    $client = new Client(['timeout' => 10]);

    // 创建异步请求队列
    $promises = [];
    foreach ($imageUrls as $url) {
        // 用临时文件存储下载的图片,避免占用内存
        $promises[$url] = $client->getAsync($url, ['sink' => tmpfile()]);
    }

    // 等待所有请求完成(并行执行,总耗时接近最慢的那张图片下载时间)
    $responses = Promise\Utils::unwrap($promises);

    // 流式输出Zip包,边打包边发送给浏览器
    return response()->stream(function () use ($responses, $type) {
        $zip = new ZipStream("$type.zip");
        foreach ($responses as $url => $response) {
            $filename = basename($url);
            // 从临时文件读取内容添加到Zip
            rewind($response->getBody()->getMetadata('uri'));
            $zip->addFileFromStream($filename, $response->getBody()->getMetadata('uri'));
        }
        $zip->finish();
    }, 200, [
        'Content-Type' => 'application/zip',
        'Content-Disposition' => "attachment; filename=\"$type.zip\"",
    ]);
}

方案二:前端改用表单提交(消除Blob开销)

放弃AJAX+Blob的方式,直接用动态表单提交,让浏览器原生处理下载流程,避免内存占用和Blob转换耗时。

修改前端代码

function downloadImages(eleClass) {
    $("div.spanner").addClass("show");
    $("div.overlay").addClass("show");
    const imageLinks = [];
    $('ul.'+ eleClass +' li img').each(function() {
        var imageLink = $(this).attr('src');
        imageLinks.push(imageLink);
    });
    if (imageLinks.length == 0) {
        $("div.spanner").removeClass("show");
        $("div.overlay").removeClass("show");
        return;
    }

    // 创建隐藏表单提交请求
    const form = document.createElement('form');
    form.method = 'POST';
    form.action = '/api/download-images';
    form.style.display = 'none';

    // 添加CSRF令牌(Laravel必填)
    const csrfInput = document.createElement('input');
    csrfInput.type = 'hidden';
    csrfInput.name = '_token';
    csrfInput.value = $('meta[name="csrf-token"]').attr('content');
    form.appendChild(csrfInput);

    // 批量添加图片URL参数
    imageLinks.forEach((url, index) => {
        const input = document.createElement('input');
        input.type = 'hidden';
        input.name = `images[${index}]`;
        input.value = url;
        form.appendChild(input);
    });

    // 添加type参数
    const typeInput = document.createElement('input');
    typeInput.type = 'hidden';
    typeInput.name = 'type';
    typeInput.value = eleClass;
    form.appendChild(typeInput);

    document.body.appendChild(form);
    form.submit();
    document.body.removeChild(form);

    // 隐藏加载动画(可根据实际下载时间调整,或结合后端通知优化)
    setTimeout(() => {
        $("div.spanner").removeClass("show");
        $("div.overlay").removeClass("show");
    }, 3000);
}

方案三:图片预压缩+CDN加速(辅助优化)

如果图片存储在自有服务器:

  1. 提前用工具(如Intervention Image)对图片进行压缩:
// 示例:下载图片后压缩再打包(仅适用于自有图片)
use Intervention\Image\Facades\Image;

// 在原有Service代码中修改
$imageContent = file_get_contents($imageUrl);
$compressedImg = Image::make($imageContent)->encode('jpg', 70); // 压缩至70%质量
$zip->addFromString($filename, $compressedImg->__toString());
  1. 将图片托管到CDN,利用CDN的边缘节点加速图片下载,减少后端获取图片的耗时。

总结

优先组合使用方案一+方案二,可大幅缩短"Content Download"阶段耗时:

  • 并行下载图片将总下载时间从串行求和变为接近单张最长耗时
  • 流式输出Zip无需等待本地文件生成,浏览器可立即开始接收数据
  • 表单提交消除前端Blob处理的内存开销和转换耗时

内容的提问来源于stack exchange,提问作者Dinh Luong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:18:05