如何使用Angular和Node下载Blob存储中的虚拟目录?探讨逐个下载Blob并打包Zip的可行性方案
Great question! This approach is fully feasible — in fact, it's the standard workaround for this exact scenario. Blob storage's "virtual directories" are just a logical abstraction (they don't exist as physical folders; blobs are grouped via naming prefixes like folder/subfolder/file.txt), so fetching all blobs under a specific prefix, zipping them, and serving the zip file is the reliable way to build directory download functionality.
分步实现方案
1. Node.js 后端(核心逻辑)
We'll use the @azure/storage-blob package to interact with blob storage, and archiver to create zip archives (we'll stream content directly to avoid storing all files on disk, which is memory-efficient for large directories).
Setup & Dependencies
First install the required packages:
npm install @azure/storage-blob archiver express cors
Core Code Example
const express = require('express'); const cors = require('cors'); const { BlobServiceClient } = require('@azure/storage-blob'); const archiver = require('archiver'); const app = express(); app.use(cors()); const PORT = 3000; // Initialize Blob client (use your storage connection string or SAS token) const blobServiceClient = BlobServiceClient.fromConnectionString('<YOUR_BLOB_STORAGE_CONNECTION_STRING>'); const containerName = '<YOUR_CONTAINER_NAME>'; app.get('/download-directory', async (req, res) => { const directoryPrefix = req.query.prefix; // e.g., 'project-docs/v1.0/' if (!directoryPrefix) { return res.status(400).send('Directory prefix is required'); } try { const containerClient = blobServiceClient.getContainerClient(containerName); const zip = archiver('zip', { zlib: { level: 9 } }); // High compression // Set response headers for download res.setHeader('Content-Type', 'application/zip'); res.setHeader('Content-Disposition', `attachment; filename="${directoryPrefix.replace(/\//g, '-')}.zip"`); // Pipe the zip stream directly to the response zip.pipe(res); // Fetch all blobs under the target prefix for await (const blob of containerClient.listBlobsFlat({ prefix: directoryPrefix })) { const blobClient = containerClient.getBlobClient(blob.name); const downloadStream = await blobClient.download(); // Preserve the original directory structure in the zip const relativeFilePath = blob.name.substring(directoryPrefix.length); zip.append(downloadStream.readableStreamBody, { name: relativeFilePath }); } // Finalize the zip archive await zip.finalize(); } catch (error) { console.error('Error processing directory download:', error); res.status(500).send('Failed to download directory. Please try again later.'); } }); app.listen(PORT, () => { console.log(`Directory download service running on port ${PORT}`); });
Key Notes
- Use
listBlobsFlatwith aprefixto target all blobs in your virtual directory (including subdirectories) - Streaming avoids storing entire files on your server, making this approach scalable for large directories
- We preserve the original folder structure by stripping the root prefix from blob names before adding them to the zip
2. Angular Frontend
Call the backend endpoint to trigger the download, and handle the blob response to save it locally.
Setup (Optional)
Install file-saver to simplify download handling:
npm install file-saver
Component Code
import { Component } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { saveAs } from 'file-saver'; @Component({ selector: 'app-directory-download', templateUrl: './directory-download.component.html' }) export class DirectoryDownloadComponent { constructor(private http: HttpClient) {} downloadTargetDirectory(prefix: string) { const encodedPrefix = encodeURIComponent(prefix); const apiUrl = `http://localhost:3000/download-directory?prefix=${encodedPrefix}`; this.http.get(apiUrl, { responseType: 'blob' }) .subscribe({ next: (zipBlob) => { const zipFileName = `${prefix.replace(/\//g, '-')}.zip`; saveAs(zipBlob, zipFileName); }, error: (err) => { console.error('Download failed:', err); alert('Oops! Something went wrong with your download.'); } }); } }
Template Example
<button (click)="downloadTargetDirectory('project-docs/v1.0/')"> Download Project Docs v1.0 </button>
Extra Optimization Tips
- Error Handling: Add retry logic for failed blob downloads, and log issues instead of failing the entire zip
- Security: Use SAS tokens instead of full connection strings if you want to limit backend permissions, or generate SAS tokens client-side and pass them to the backend
- Progress Tracking: For large directories, add a progress bar by listening to the response's download progress event in Angular
- Cleanup: If you ever need to store temporary files (avoided here via streaming), add logic to delete them after the download completes
内容的提问来源于stack exchange,提问作者sonuD

