0

我在使用 node.js 编写的应用程序在 docker 容器内运行时遇到了一些奇怪的行为。

它使用 react-admin ( https://marmelab.com/react-admin/ ) 前端成功上传和删除图像。

-rw-r--r--    1 root     root       87.2K Feb 13  2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r--    1 root     root       32.2K Dec  7 01:54 60283f6b7b33b304a4b6b428_1.jpg

如果扩展名不同,它甚至会用新的图像替换以前上传的图像。

-rw-r--r--    1 root     root       87.2K Feb 13  2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r--    1 root     root      674.1K Dec  7 02:26 60283f6b7b33b304a4b6b428_1.png

但是由于某种原因,如果我上传的图片与之前上传的完全不同,但扩展名相同,导致图片与第一个删除的图片同名,那么将显示该图片,而不是较新的。

-rw-r--r--    1 root     root       87.2K Feb 13  2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r--    1 root     root       32.2K Dec  7 01:54 60283f6b7b33b304a4b6b428_1.jpg

上述应用程序使用 multer 作为中间件上传文件:

const multer = require("multer");
const path = require("path");
const {
  PATH_PRODUCT_TEMP_IMAGE
} = require("../services/config");

var storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, PATH_PRODUCT_TEMP_IMAGE),
  filename: (req, file, cb) => {
    if (file.fieldname === "picture")
      cb(null, `${req.params.id}${path.extname(file.originalname)}`);
    else if (file.fieldname === "picture1")
      cb(null, `${req.params.id}_1${path.extname(file.originalname)}`);
  },
});

// Filter files with multer
const multerFilter = (req, file, cb) => {
  if (file.mimetype.startsWith("image")) {
    cb(null, true);
  } else {
    cb("Not an image! Please upload only images.", false);
  }
};

const maxFileUploadSizeMb = 15;

var upload = multer({
  storage: storage,
  limits: { fileSize: maxFileUploadSizeMb * 1024 * 1024 },
  fileFilter: multerFilter,
});

module.exports = upload;

管理文件上传的 API 控制器是:

 router.put(
  "/:id",
  [
    auth,
    upload.fields([
      { name: "picture", maxCount: 1 },
      { name: "picture1", maxCount: 1 },
    ]),
  ],
  async (req, res) => {
    try {
      let objToUpdate = buildProduct(req.body);

      const product = await Product.findById(req.params.id);
      if (!product) throw new Error(`Product ${req.params.id} doesn't exist`);

      if (req.files.picture?.length > 0) {
        objToUpdate = {
          ...objToUpdate,
          primaryImage: req.files.picture[0].filename,
        };
        removeProductImage(product.primaryImage);
        resizeProductImage(objToUpdate.primaryImage);
      }
      if (req.files.picture1?.length > 0) {
        objToUpdate = {
          ...objToUpdate,
          image1: req.files.picture1[0].filename,
        };
        removeProductImage(product?.image1);
        resizeProductImage(objToUpdate.image1);
      }
      await product.updateOne(objToUpdate);
      res.send(product);
    } catch (error) {
      sendError500(res, error);
    }
  }
);

const removeProductImage = async (imageName) => {
  if (notNullOrEmpty(imageName))
    return await removeFile(path.join(PATH_PRODUCT_IMAGE, imageName));
};

const removeFile = async (filePathName) => {
  let result = false;
  try {
    await fs.unlink(filePathName, (error) => {
      if (error) throwError(error);
      else result = true;
    });
  } catch (error) {
    throwError(error);
  }
  return result;

  function throwError(error) {
    throw new Error(`Can't delete file: ${filePathName} - ${error.message}`);
  }
};

整个项目在 docker 中运行,使用命名卷作为图像的存储。尽管如此,使用相同的代码库但使用绑定安装,它可以按预期工作。

编辑:我注意到我忘记发布该过程中涉及的函数:

const resizeProductImage = async (imageName) => {
  if (!notNullOrEmpty(imageName)) return;

  const imageTemp = path.join(PATH_PRODUCT_TEMP_IMAGE, imageName);
  const imageFinal = path.join(PATH_PRODUCT_IMAGE, imageName);

  await resizeImage({
    imageFinal,
    imageTemp,
    imageFinalSize: +IMAGE_SIZE_PRODUCT,
  });
  await removeProductTempImage(imageName);
};
    
const resizeImage = async ({
  imageFinal,
  imageTemp,
  imageFinalSize = 1024,
}) => {
  try {
    switch (path.extname(imageTemp)) {
      case ".png":
        await sharp(imageTemp)
          .resize(imageFinalSize, null)
          .png({ adaptiveFiltering: true })
          .toFile(imageFinal, null);
        break;
      case ".webp":
      case ".jpg":
      case ".jpeg":
      default:
        await sharp(imageTemp)
          .resize(imageFinalSize, null)
          .toFile(imageFinal, null);
        break;
    }
  } catch (error) {
    try {
      await fs.rename(imageTemp, imageFinal);
      throw Error(
        `Can't resize image ${imageTemp}, moving directly to ${imageFinal}, ${error.message}`
      );
    } catch (error) {
      throw Error(
        `Can't resize image ${imageTemp}, neither move to ${imageFinal}, ${error.message}`
      );
    }
  }
};
4

1 回答 1

0

我终于弄清楚了问题所在。

发生的事情是缓存问题,而不是我最初猜测的权限问题。

问题出在 resizeProductImage 内部,它使用了 resizeImage 函数,使用了Sharp节点包(https://www.npmjs.com/package/sharp)。

该组件默认启用缓存,导致不处理(在这种情况下调整大小)最近处理的具有相同名称的新图像。

由于程序需要使用产品 ID + 图片扩展名来命名上传的图片,因此每次上传具有相同扩展名的新图片时,Sharp包都会使用之前缓存的图片,而不是使用新的图片。

只需在调用组件的地方写一行代码就足以解决问题:

sharp.cache(false);
于 2021-12-12T14:57:17.040 回答