Node.js+Express:解决awaitZip构建忽略问题,实现GridFS图标压缩路由服务
解决方案:修复异步流程并正确生成Zip包
你遇到的核心问题是异步流程没有正确等待——原来的db.Category.find().exec()用了async回调,但这个回调的执行完全脱离了getZipFile函数的异步上下文,导致函数在zip包还没构建完成就提前结束了。另外,node-native-zip的异步处理方式也容易出现这类“构建被忽略”的问题,换成更可靠的流式zip库(比如archiver)会更稳妥。
1. 先安装必要依赖
确保你安装了需要的包:
npm install archiver mongoose gridfs-stream
2. 重构后的完整代码
const archiver = require('archiver'); const mongoose = require('mongoose'); const Grid = require('gridfs-stream'); const express = require('express'); const app = express(); // 初始化GridFSStream(注意:要在mongoose连接成功后执行) let gfs; mongoose.connection.once('open', () => { gfs = Grid(mongoose.connection.db, mongoose.mongo); gfs.collection('icons'); // 替换成你的GridFS集合名 }); // 生成Zip包的异步函数 async function generateIconZip() { try { // 1. 查询分类并关联图标文件(用await替代回调) const cats = await db.Category.find({}).populate('icons.file'); if (!cats.length) { throw new Error('No categories or icons found'); } // 2. 初始化archiver实例(创建zip包) const archive = archiver('zip', { zlib: { level: 9 } }); // 最高压缩级别 // 3. 遍历每个分类的图标,从GridFS读取并添加到zip for (const cat of cats) { // 假设icons.file是GridFS的文件文档,包含_id字段 for (const icon of cat.icons) { const fileId = icon.file._id; // 从GridFS获取文件流 const readStream = gfs.createReadStream({ _id: fileId }); // 添加文件到zip,文件名自定义(比如用分类名+图标名) archive.append(readStream, { name: `${cat.name}_${icon.name || fileId}.png` }); } } // 结束zip构建 archive.finalize(); return archive; } catch (err) { throw new Error(`Failed to generate zip: ${err.message}`); } } // Express路由:提供压缩后的图标zip app.get('/icons/download', async (req, res) => { try { const zipArchive = await generateIconZip(); // 设置响应头,让浏览器识别为下载文件 res.setHeader('Content-Type', 'application/zip'); res.setHeader('Content-Disposition', 'attachment; filename="icons.zip"'); // 将zip流pipe到响应对象 zipArchive.pipe(res); // 处理zip构建中的错误 zipArchive.on('error', (err) => { res.status(500).send({ success: false, message: err.message }); }); } catch (err) { res.status(500).send({ success: false, message: err.message }); } }); // 启动服务器 app.listen(3000, () => console.log('Server running on port 3000'));
3. 关键修复点说明
- 异步流程修正:用
await db.Category.find()替代回调式的exec(async function()),确保generateIconZip函数会等待数据库查询完成后再继续执行zip构建,彻底解决异步操作被忽略的问题。 - 替换Zip库:用
archiver替代node-native-zip,它支持流式处理,不仅内存效率更高,还能更好地和Express的响应流结合,避免内存溢出和异步构建丢失的问题。 - GridFS文件读取:使用
gridfs-stream创建读取流,直接将文件流添加到zip包,不需要先把文件内容读到内存中,适合处理大量图标文件。 - 错误处理:在生成zip和路由处理中都添加了完整的try/catch,确保错误能被正确捕获并返回给客户端。
4. 如果坚持使用node-native-zip的修复方案
如果你不想换库,需要用Promise包裹整个流程,确保getZipFile会等待zip构建完成后才返回结果:
var zip = require("node-native-zip"); async function getZipFile() { return new Promise((resolve, reject) => { db.Category.find({}).populate('icons.file').exec(async (err, cats) => { if (err) { return reject({ success: false, message: err }); } const archive = new zip(); const filesToAdd = []; try { for (const cat of cats) { for (const icon of cat.icons) { // 读取GridFS文件内容到Buffer const fileBuffer = await new Promise((res, rej) => { gfs.createReadStream({ _id: icon.file._id }) .on('end', (data) => res(data)) .on('error', rej); }); filesToAdd.push({ name: `${cat.name}_${icon.name}.png`, buffer: fileBuffer }); } } archive.addFiles(filesToAdd, (err) => { if (err) { reject({ success: false, message: err }); } else { const zipBuffer = archive.toBuffer(); resolve({ success: true, data: zipBuffer }); } }); } catch (err) { reject({ success: false, message: err }); } }); }); } // 在Express路由中使用 app.get('/icons/download', async (req, res) => { try { const result = await getZipFile(); res.setHeader('Content-Type', 'application/zip'); res.setHeader('Content-Disposition', 'attachment; filename="icons.zip"'); res.send(result.data); } catch (err) { res.status(500).send(err); } });
内容的提问来源于stack exchange,提问作者Nop0x
相关产品推荐
相关产品推荐

