当文件作为 'multipart/form-data' 发送时,Express Route returns 状态代码 500 具有通用 'Error'

Express Route returns status code 500 with generic 'Error' when file is sent as 'multipart/form-data'

详情:

我有一个快速路由,设置为接受文件 multipart/formdata 并将它们上传到 S3 存储桶。我正在使用 multer 来过滤图像类型,并通过创建 upload 目录将它们临时存储在服务器上。上传成功后不久,文件将被删除。根据多重配置,文件数组被命名为 images,并且最多接受 3 个图像。

该代码在我的本地机器上运行完美。我通过 POSTMAN 测试,可以上传 1-3 个文件并得到正确的响应。如果没有附加文件,也会触发正确的响应,状态代码均为 200。

问题:

在 Amazon ECS 上使用 Docker 部署了完全相同的代码库,但不知何故一直失败,状态代码为 500 和在代码库中找不到的通用 'Error' 消息。使用日志我确定 multer 不是原因,因为它通过了过滤器。它似乎在 multer 中间件和路由本身之间的某处失败,但有一个例外。

异常:使用 POSTMAN,如果 multipart/formdata POST 请求没有文件 I.E 为空 images数组,正确触发路由并返回消息“您没有附加任何图像”作为响应。

我一直无法弄清楚这个问题,如果能就此问题提供一些指导,我将不胜感激!

代码片段:

文件控制器:

files.post(
  "/multiple",
  upload.array("images", 3),
  async (req: ExpressRequest, res: ExpressResponse) => {
    try {
      const files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] =
        req.files;
      console.log("FILES", files);
      // execute only if there are files
      if (files.length > 0) {
        const dataPromises = (files as Array<Express.Multer.File>).map(
          async (file: Express.Multer.File) => {
            // check if file.mimetype here is 'image/heic', and convert into jpeg accordingly
            const fileNameWithoutExt = file.filename.split(".")[0];
            try {
              if (file.mimetype == "image/heic") {
                await convertFile(file, fileNameWithoutExt, 0.2);
                const response = await uploadFilePath(
                  S3_IMAGE_BUCKET,
                  `./uploads/${fileNameWithoutExt}.jpeg`,
                  `${fileNameWithoutExt}.jpeg`
                );
                console.log("HEIC File Upload Response", response);
                fs.unlinkSync(`./uploads/${fileNameWithoutExt}.jpeg`);
                fs.unlinkSync(file.path);
                return {
                  fileName: `${fileNameWithoutExt}.jpeg`,
                  metaData: response.$metadata,
                };
              } else {
                const response = await uploadFile(S3_IMAGE_BUCKET, file);
                console.log("JPEG File Upload Response", response);
                fs.unlinkSync(file.path);
                return {
                  fileName: file.filename,
                  metaData: response.$metadata,
                };
              }
            } catch (err) {
              console.error("Error for file conversion/upload", err, err.stack);
              res.status(500).send({
                message: "Upload failed due to conversion or something.",
                error: err,
                stack: err.stack,
              });
            }
          }
        );
        const fileData = await Promise.all(dataPromises);
        const fileNames = fileData.map((data: any) => data.fileName);
        const statusCodes = fileData.map((data: any) => data.metaData.httpStatusCode);

        if (statusCodes.find((statusCode) => statusCode === 200)) {
          res.status(200).send({
            filePath: `/image/`,
            fileNames,
          });
        } else {
          res.status(403).send({
            message: "Upload failed. Please check credentials or file has been selected.",
          });
        }
      } else {
        res.status(200).send({
          message: "You did not attach any images",
        });
      }
    } catch (err) {
      res.status(500).send({
        message: "Upload failed. Please check credentials or file has been selected.",
      });
    }
  }
);

多种配置:

const storage = multer.diskStorage({
  // potential error, path to store files, callback
  destination: (req, file, cb) => {
    // cb acceptes two arguments: 1. err 2. destination folder wrt to server.js
    cb(null, "uploads/");
  },
  filename: (req, file, cb) => {
    console.log("MULTER STORAGE STARTED")
    const date = new Date().toISOString().substring(0, 10);
    // const name = `${req.body.first_name}_${req.body.last_name}`;
    // cb defines the name of the file when stored
    const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-";
    const nanoid = customAlphabet(alphabet, 20);
    cb(null, `${date}_${nanoid()}_${file.originalname}`);
    console.log("FILE NAME CREATED, MULTER STORAGE STOPPED")
  },
});

/* Accept jpeg or png files only */
// NOTE: file type rejection works, but there is no error message displayed if file is rejected. logic in route continues to be executed
const fileFilter = (
  req: Request,
  file: Express.Multer.File,
  cb: (error: Error | null, accepted: boolean) => void
) => {
  console.log("======== FILE FILTER ========", file);
  if (
    file.mimetype === "image/jpeg" ||
    file.mimetype === "image/png" ||
    file.mimetype === "image/heic"
  ) {
    cb(null, true);
    console.log("FILTER PASSED")
  } else {
    console.log("FILTER FAILED");
    cb(null, false);
  }
};

/* Only accepts filesize up to 5MB */
// the first parameter is super important that determines where the data is stored on the server
const upload = multer({
  dest: "uploads/", // default simple config to upload file as binary data
  storage, // enable if storage of actual file is required.
  // limits: { fileSize: 1024 * 1024 * 5 },
  fileFilter,
});

屏幕截图:

表单数据中没有图像的响应

响应表单数据中的图像

你能确定你的 Docker 容器中存在上传目录吗?如果它不存在,Multer 将不会创建它。它可能在您的 storage 函数和实际将文件写入磁盘之间静默失败。

const storage = multer.diskStorage({
  // potential error, path to store files, callback
  destination: (req, file, cb) => {
    // cb acceptes two arguments: 1. err 2. destination folder wrt to server.js
    cb(null, "uploads/");
  },

应该是这样的:

import { access, constants } from "fs";
import { join } from "path";
...
const storage = multer.diskStorage({
  // potential error, path to store files, callback
  destination: (req, file, cb) => {
    // cb acceptes two arguments: 1. err 2. destination folder wrt to server.js
    const currentDirectory: string = process.cwd();
    const uploadDirectory: string = join(currentDirectory, 'uploads/');
    // can we write to this path?
    access(uploadDirectory, constants.W_OK, (err) => {
      if (err) {
        console.error(err);
        cb(err, null);
      }
      cb(null, uploadDirectory);
    })
  },