基于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

