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

Mongoose/Express NodeJS API仅返回单张图片:如何返回全部图片?

问题

我有一个Buffer数组,希望将其转换为图片并返回给用户,但当前代码仅能返回一张图片。相关代码如下:

路由代码

const express = require("express");
const asyncHandler = require("express-async-handler");
const Image = require('../models/Image');
const router = express.Router();
const multer = require('multer');
const sharp = require('sharp');

router.get("/", asyncHandler(async (req, res) => {
  const imagesInfo = await Image.find();
  const imagesBuffers = imagesInfo.map((img) => img.data);

  const promises = imagesBuffers.map((buffer) =>
    sharp(buffer).toFormat('jpeg').toBuffer()
  );
  Promise.all(promises)
    .then((outputBuffers) => {
      res.contentType('image/jpeg');
      outputBuffers.forEach((outputBuffer) => {
        res.write(outputBuffer, 'binary');
      });

      res.end();
    })
    .catch((err) => {
      console.error('Error:', err);
      res.status(500).send('Error.');
    });
}));
module.exports = router;

Image模型定义

const mongoose = require("mongoose");
const ImageSchema = new mongoose.Schema({
  filename: { type: String, },
  data: { type: Buffer, },
}, { timestamps: true });
const Image = mongoose.model("Image", ImageSchema);
module.exports = Image;

解决方案

原代码问题在于:单个HTTP响应只能对应一个合法的媒体资源,直接拼接多个JPEG Buffer会导致二进制数据格式无效,浏览器只能解析出第一张图片。以下是几种可行的修改方案:

方案1:返回多部分(multipart)响应

通过HTTP多部分格式,在一个响应中返回多个独立的图片资源,兼容大部分客户端:

const express = require("express");
const asyncHandler = require("express-async-handler");
const Image = require('../models/Image');
const router = express.Router();
const sharp = require('sharp');

router.get("/", asyncHandler(async (req, res) => {
  const imagesInfo = await Image.find();
  const imagesBuffers = imagesInfo.map((img) => img.data);

  const promises = imagesBuffers.map((buffer) =>
    sharp(buffer).toFormat('jpeg').toBuffer()
  );
  
  try {
    const outputBuffers = await Promise.all(promises);
    // 生成唯一边界符分隔各个图片
    const boundary = `image-sep-${Date.now()}`;
    res.setHeader('Content-Type', `multipart/mixed; boundary=${boundary}`);
    
    outputBuffers.forEach((buffer, index) => {
      // 写入边界分隔符
      res.write(`--${boundary}\r\n`);
      // 设置当前图片的响应头
      res.write(`Content-Type: image/jpeg\r\n`);
      res.write(`Content-Disposition: attachment; filename="image-${index + 1}.jpg"\r\n\r\n`);
      // 写入图片二进制数据
      res.write(buffer, 'binary');
      res.write(`\r\n`);
    });
    // 写入结束标记
    res.write(`--${boundary}--\r\n`);
    res.end();
  } catch (err) {
    console.error('Error:', err);
    res.status(500).send('Error.');
  }
}));

module.exports = router;

方案2:返回Base64编码的图片数组(适合前端展示)

将每个图片转换为Base64字符串,封装成JSON返回,前端可直接通过data URI渲染图片:

const express = require("express");
const asyncHandler = require("express-async-handler");
const Image = require('../models/Image');
const router = express.Router();
const sharp = require('sharp');

router.get("/", asyncHandler(async (req, res) => {
  const imagesInfo = await Image.find();
  const imagesBuffers = imagesInfo.map((img) => img.data);

  const promises = imagesBuffers.map(async (buffer) => {
    const jpegBuffer = await sharp(buffer).toFormat('jpeg').toBuffer();
    return `data:image/jpeg;base64,${jpegBuffer.toString('base64')}`;
  });
  
  try {
    const base64Images = await Promise.all(promises);
    res.json({ images: base64Images });
  } catch (err) {
    console.error('Error:', err);
    res.status(500).send('Error.');
  }
}));

module.exports = router;

方案3:返回ZIP压缩包(适合批量下载)

安装archiver包后,将所有图片打包成ZIP返回,方便用户一次性下载:

  1. 先安装依赖:
npm install archiver
  1. 修改路由代码:
const express = require("express");
const asyncHandler = require("express-async-handler");
const Image = require('../models/Image');
const router = express.Router();
const sharp = require('sharp');
const archiver = require('archiver');

router.get("/", asyncHandler(async (req, res) => {
  const imagesInfo = await Image.find();
  const imagesBuffers = imagesInfo.map((img) => img.data);

  const promises = imagesBuffers.map(async (buffer, index) => {
    const jpegBuffer = await sharp(buffer).toFormat('jpeg').toBuffer();
    return {
      filename: imagesInfo[index].filename || `image-${index + 1}.jpg`,
      data: jpegBuffer
    };
  });
  
  try {
    const imageFiles = await Promise.all(promises);
    // 设置压缩包响应头
    res.setHeader('Content-Type', 'application/zip');
    res.setHeader('Content-Disposition', 'attachment; filename="all-images.zip"');
    
    const archive = archiver('zip', { zlib: { level: 9 } });
    archive.pipe(res);
    
    // 将每个图片添加到压缩包
    imageFiles.forEach(file => {
      archive.append(file.data, { name: file.filename });
    });
    
    await archive.finalize();
  } catch (err) {
    console.error('Error:', err);
    res.status(500).send('Error.');
  }
}));

module.exports = router;

方案4:分接口返回单图与图片列表(推荐生产环境)

提供两个接口:一个返回所有图片的URL列表,另一个根据ID返回单张图片,这种方式更符合RESTful设计,也方便前端灵活加载:

const express = require("express");
const asyncHandler = require("express-async-handler");
const Image = require('../models/Image');
const router = express.Router();
const sharp = require('sharp');

// 接口1:根据ID获取单张图片
router.get("/:id", asyncHandler(async (req, res) => {
  const image = await Image.findById(req.params.id);
  if (!image) {
    return res.status(404).send('Image not found');
  }
  
  try {
    const jpegBuffer = await sharp(image.data).toFormat('jpeg').toBuffer();
    res.contentType('image/jpeg');
    res.send(jpegBuffer);
  } catch (err) {
    console.error('Error:', err);
    res.status(500).send('Error.');
  }
}));

// 接口2:获取所有图片的URL列表
router.get("/", asyncHandler(async (req, res) => {
  const imagesInfo = await Image.find({}, '_id filename');
  const imageUrls = imagesInfo.map(img => ({
    id: img._id,
    filename: img.filename,
    url: `${req.protocol}://${req.get('host')}/images/${img._id}` // 替换为你的实际路由前缀
  }));
  res.json({ images: imageUrls });
}));

module.exports = router;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:35:22