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

基于Sharp与AWS的Web/APP图片处理存储问题排查及方案咨询

问题描述

我正在开发一款用于存储商城Web图片和APP图片的API,需求是将图片压缩后转换为WebP格式并存储至AWS,但目前遇到报错:

Error: Error compressing and converting the image: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined.
at readFile (fs.js:343:10)
at internal/util.js:341:30
at new Promise ()
at readFile (internal/util.js:340:12)
at convertAndCompress

我使用Sharp库进行图片转换,该库需要通过本地文件路径读取图片,但线上环境无法访问本地路径。以下是我的图片转换中间件代码,本地环境运行正常,但线上环境报错:

add_marketplace_banner_images: async (req, res, next) => {
      // const admin_id = req.user.user_id;
      const admin_id = 12;
      try {
        const upload = multer({
          storage: Config.environment === "local" ? storage_marketplace_banner_images : multerS3Config,
          limits: {
            fileSize: 6242880, // Compliant: 5MB
          },
          fileFilter: (req, file, cb) => {
            const allowedExtensions = ['.png', '.jpg', '.webp', '.jpeg'];
            const ext = path.extname(file.originalname).toLowerCase();
            if (allowedExtensions.includes(ext)) {
              const validateImage = validatingImage(module.exports.schemas.marketplace_banner_data);
              if (validateImage) {
                cb(null, true);
              }
            } else {
              cb(new Error('Only images with .png, .jpg, .webp, and .jpeg extensions are allowed!'), false);
            }
          },
        }).fields([
          { name: "web_image", maxCount: 1 },
          { name: "app_image", maxCount: 1 },
        ]);

        upload(req, res, async function (err) {
          if (err) {
            return res.status(400).json({ status: 2, errors: { file: err.message } });
          }

          const processImage = async (fieldName) => {
            if (req.files && req.files[fieldName]) {
              const originalImage = req.files[fieldName][0];
              const compressedImage = await sharp(originalImage.path)
                .webp({ quality: 80 })
                .toBuffer()
                .catch((err) => {
                  console.error("Error compressing and converting the image:", err);
                  throw err;
                });

              if (Config.environment === "local") {
                const outputPath = `${originalImage.destination}/${originalImage.filename}.webp`;
                await fs.promises.writeFile(outputPath, compressedImage);
                fs.unlinkSync(originalImage.path); // Remove the original image file

                req.files[fieldName] = [{
                  fieldname: fieldName,
                  originalname: `${path.parse(originalImage.originalname).name}.webp`,
                  encoding: '7bit',
                  mimetype: 'image/webp',
                  destination: path.dirname(originalImage.destination),
                  filename: path.basename(outputPath),
                  path: outputPath,
                  size: fs.statSync(outputPath).size,
                }];
              } else {
                const image_name = `${Math.floor(Date.now() / 1000)}-${admin_id}-${uuid()}`;
                const s3Params = {
                  Bucket: Config.aws.bucketName,
                  Key: `uploads/marketplace_banner_image/${image_name}.webp`,
                  Body: compressedImage,
                  ContentType: "image/webp",
                };
                await s3.putObject(s3Params).promise();
              }
            }
          };

          const processedImages = await Promise.allSettled([
            req.files.web_image && processImage("web_image"),
            req.files.app_image && processImage("app_image"),
          ]);

          const rejectedImages = processedImages.filter((result) => result.status === "rejected");
          if (rejectedImages.length > 0) {
            console.error("Error processing images:", rejectedImages.map((result) => result.reason));
            return res.status(500).json({ status: 3, message: "Error processing images" });
          }

          next();
        });
      } catch (err) {
        console.error("Server error:", err);
        res.status(500).json({ status: 3, message: "Server error" });
      }
    },

请问是否有无需先存储图片再转换的方案来实现该需求?


解决方案

报错原因

线上环境使用multerS3Config作为存储时,req.files[fieldName][0].path是undefined——因为multer-s3不会把文件保存到本地磁盘,自然没有本地路径。代码里直接用sharp(originalImage.path)读取,就触发了类型错误。

无需先存储的实现方案

完全可以直接用文件的Buffer处理,不需要依赖本地路径。Sharp支持直接接收Buffer作为输入,核心调整点:

  • 线上环境下,multer-s3会把文件的Buffer存在originalImage.buffer字段(本地环境用磁盘存储时,buffer字段同样存在)
  • 用sharp(originalImage.buffer)替代sharp(originalImage.path),跳过本地文件读取步骤
  • 本地环境可以保留原逻辑,或者统一用Buffer处理保持代码一致性

修改后的核心代码

const processImage = async (fieldName) => {
  if (req.files && req.files[fieldName]) {
    const originalImage = req.files[fieldName][0];
    // 直接使用文件Buffer,不再依赖本地路径
    const compressedImage = await sharp(originalImage.buffer)
      .webp({ quality: 80 })
      .toBuffer()
      .catch((err) => {
        console.error("Error compressing and converting the image:", err);
        throw err;
      });

    if (Config.environment === "local") {
      const outputPath = `${originalImage.destination}/${originalImage.filename}.webp`;
      await fs.promises.writeFile(outputPath, compressedImage);
      fs.unlinkSync(originalImage.path); // Remove the original image file

      req.files[fieldName] = [{
        fieldname: fieldName,
        originalname: `${path.parse(originalImage.originalname).name}.webp`,
        encoding: '7bit',
        mimetype: 'image/webp',
        destination: path.dirname(originalImage.destination),
        filename: path.basename(outputPath),
        path: outputPath,
        size: fs.statSync(outputPath).size,
      }];
    } else {
      const image_name = `${Math.floor(Date.now() / 1000)}-${admin_id}-${uuid()}`;
      const s3Params = {
        Bucket: Config.aws.bucketName,
        Key: `uploads/marketplace_banner_image/${image_name}.webp`,
        Body: compressedImage,
        ContentType: "image/webp",
      };
      await s3.putObject(s3Params).promise();
    }
  }
};

额外优化建议

  • 统一本地和线上的处理逻辑,都用Buffer处理,避免环境分支带来的不一致问题
  • 本地环境也可以直接用Buffer写入文件,不需要依赖originalImage.path,减少代码冗余

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:29:50