#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);