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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:08:15