js-common-code/docs/archive.md
2025-03-22 09:45:36 +09:00

8.0 KiB

[Node.js]ファイルをZIP圧縮してダウンロードする方法

圧縮処理について

  • 方法1:フロンサイドで圧縮データを処理しファイルを保存する(JSZip)
  • 方法2:サーバサイドで圧縮処理しファイルを出力する
  • 方法3:サーバサイドで圧縮処理しフロントでBlobによりファイルを取得する

方法1:フロンサイドで圧縮データを処理しファイルを保存する(JSZip)

  • ファイル量が多い場合(30MB以上など)はサーバーサイドで処理するほうがいい
  • 大容量データの処理には向かない
  • ユーザーのデバイスの性能に依存する
sequenceDiagram
    User->>Front: ダウンロードボタンを要求する
    loop ファイル毎に処理する
    Front->>Storage: ファイルを要求する(fetch)
    Storage-->>Front: ファイル情報を返す
    end 
    Front-->>Front: Zip処理を実行する(JSZip)
    Front-->>User: ダウンロード処理を実行する(blob)
import JSZip from "jszip";

CDNでインポートする場合

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

方法2:サーバサイドで圧縮処理しファイルを出力する

サーバーサイドでarchiverを使ってZIPを作成し、 一時ファイルとして保存してからダウンロード用のURLを返します

  • archiverモジュールで実行する
  • Zipファイルの保存のためにストレージが別で必要
    • ライフサイクルを考慮しないと不要なファイルが溜まる
  • 大容量データの圧縮に適してい
  • 複数のリクエストに対応しやすい
sequenceDiagram
    User->>Front: ダウンロードボタンを要求する
    Front->>Server: ダウンロードを要求する
    Server->>Storage: ファイルデータ取得API
    Storage->>Server: レスポンス
    Server->>Server: Zip処理を実行する(archiver)
    Server->>Storage: 作成した Zipを保存する
    Storage->>Server: レスポンス
    Server-->>Front: ファイルのURLを返す
    Front->>Storage: URLをリンクする(HTTP)
    Storage-->>User: ファイルをダウンロードする
npm install archiver

方法3:サーバサイドで圧縮処理しフロントでBlobによりファイルを取得する

ZIPを一時ファイルとして保存せずに 直接フロントエンドにストリームとして送信します。

  • archiverモジュールで実行する
  • 保存せずにファイルコンテンツを返す
  • リアルタイムでファイルをストリーミング可能
  • ストレージの管理が不要
  • クライアントがダウンロードを途中でキャンセルするとデータが失われる可能性がある
sequenceDiagram
    User->>Front: ダウンロードボタンを要求する
    Front->>Server: ダウンロードを要求する
    Server->>Storage: ファイルデータ取得API
    Storage->>Server: レスポンス
    Server->>Server: Zip処理を実行する(archiver)    
    Storage->>Front: Zipされたファイルコンテンツを返す
    Front-->>User: ダウンロード処理を実行する(blob)

サンプルソース

Blobを使用してZIPファイルを処理する(フロント側の処理)**

fetch("https://example.com/sample.zip")
    .then(response => response.blob())
    .then(blob => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = "downloaded.zip";
        document.body.appendChild(a);
        a.click();
        URL.revokeObjectURL(url);
    })
    .catch(error => console.error("Error downloading ZIP:", error));

gcloudにあるファイルをzip形式でファイルを出力するサンプルソース

(スクリプト)

const { Storage } = require('@google-cloud/storage');
const archiver = require('archiver');
const fs = require('fs');

const storage = new Storage();
const bucketName = 'your-bucket-name';
const filesToDownload = ['file1.csv', 'file2.csv']; // 圧縮したいファイルリスト
const outputZip = 'output.zip';

async function downloadAndZip() {
    const output = fs.createWriteStream(outputZip);
    const archive = archiver('zip', { zlib: { level: 9 } });

    output.on('close', () => {
        console.log(`Zip file created: ${outputZip} (${archive.pointer()} bytes)`);
    });

    archive.pipe(output);

    for (const fileName of filesToDownload) {
        const file = storage.bucket(bucketName).file(fileName);
        const [exists] = await file.exists();

        if (exists) {
            console.log(`Adding ${fileName} to archive...`);
            archive.append(file.createReadStream(), { name: fileName });
        } else {
            console.warn(`File not found: ${fileName}`);
        }
    }

    archive.finalize();
}

downloadAndZip().catch(console.error);

Zipダウンロードサーバを構築する(gcloud)

(サーバ)

/**
 * @fileoverview Google Cloud Storage (GCS) download module.
 */
const { Storage } = require('@google-cloud/storage');
const archiver = require('archiver');
const KEY_FILE_PATH = './keys/service-account.json'
const storage = new Storage({
    keyFilename: KEY_FILE_PATH});

// Load environment variables
require('dotenv').config();

// バケット名を.envから取得する
const BUCKET_NAME = process.env.BUCKET_NAME;
console.log(`BUCKET_NAME: ${BUCKET_NAME}`);


/**
 * GCStorageからファイルをダウンロードする
 * 
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 */
const downloadFilesFromGCS = async (req, res) => {
    // バケットからファイル一覧を取得する
    const [files] = await storage.bucket(BUCKET_NAME).getFiles();
    const filesToZip = files.map((file) => file.name);

    res.setHeader('Content-Disposition', 'attachment; filename="files.zip"');
    res.setHeader('Content-Type', 'application/zip');

    const archive = archiver('zip', { zlib: { level: 9 } });

    archive.on('error', (err) => res.status(500).send({ error: err.message }));
    archive.pipe(res);


    for (const fileName of filesToZip) {
        const file = storage.bucket(BUCKET_NAME).file(fileName);
        archive.append(file.createReadStream(), { name: fileName });
    }
    archive.finalize();
};
module.exports = downloadFilesFromGCS;