выполнение fs.unlink() в node для файла в окне настройки с именем volume восстанавливает объект, если создается файл с тем же именем

#node.js #docker #multer #sharp #docker-named-volume

#node.js #docker #мультер #острым #docker-named-volume

Вопрос:

У меня какое-то странное поведение с приложением, написанным на 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 resu<

  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}`
      );
    }
  }
};
 

Комментарии:

1. Кэширование в браузере?

2. почти! Я ответил на свой собственный вопрос, и оказалось, что это компонент изменения размера на стороне сервера, кэширующий предыдущее обработанное изображение.

Ответ №1:

Я, наконец, понял, в чем проблема.

То, что происходило, было проблемой КЭША, а не проблемой разрешений, как я сначала предположил.

Проблема была внутри resizeProductImage, который использовал функцию resizeImage, которая использовала пакет sharp node (https://www.npmjs.com/package/sharp ).

У этого компонента по умолчанию включен кэш, в результате чего не обрабатывается (в данном случае изменяется размер) новое изображение с тем же именем, которое было недавно обработано.

Поскольку программа требует назвать загруженное изображение с идентификатором продукта расширением изображения, каждый раз, когда загружалось новое изображение с тем же расширением, пакет sharp использовал предыдущее кэшированное изображение вместо работы с новым.

Только одной строки кода, помещенной там, где вызывается компонент, было достаточно для решения проблемы:

 sharp.cache(false);