【问题标题】:How to upload multiple files in nodejs to AWS S3 and save file url into database?如何将nodejs中的多个文件上传到AWS S3并将文件url保存到数据库中?
【发布时间】:2021-04-15 21:12:42
【问题描述】:

您好,我需要在 s3 上一次上传多张图片。 目前我正在使用express-fileupload 在 AWS 上上传单个图像,我想使用相同的方法将多个文件上传到 s3 并使用 mongodb 上的 url 更新图像数组。

我的架构属性:

const ServiceSchema = new mongoose.Schema(
{
    photo: [
        {
            type: String,
            default: 'no-photo.jpg',
        },
    ],
});
module.exports = mongoose.model('Service', ServiceSchema);

我的控制器:

// @desc        Upload photo for service
// @route       PUT /api/v1/services/:id/photo
// @access      Private
exports.servicePhotoUpload = asyncHandler(async (req, res, next) => {
const service = await Service.findById(req.params.id);

if (!service) {
    return next(new ErrorResponse(`Service not found with id of ${req.params.id}`, 404));
}

// Make sure user adding service is business owner
if (service.user.toString() !== req.user.id && req.user.role !== 'admin') {
    return next(
        new ErrorResponse(
            `User ${req.user.id} is not authorized to update this service to business ${service._id}`,
            401
        )
    );
}

// File Upload validation
if (!req.files) {
    return next(new ErrorResponse(`Please upload a file.`, 400));
}

const file = req.files.file;

// Make sure it is a valid image file
if (!file.mimetype.startsWith('image')) {
    return next(new ErrorResponse(`Please upload a valid image file.`, 400));
}

//Check File Size
if (file.size > process.env.MAX_FILE_UPLOAD) {
    return next(
        new ErrorResponse(
            `Please upload an image less then ${process.env.MAX_FILE_UPLOAD / 1024}KB in size.`,
            400
        )
    );
}

// Create custom filename
file.name = `service-uploads/servicePhoto_${service._id}${path.parse(file.name).ext}`;

uploadToS3({
    fileData: req.files.file.data,
    fileName: file.name,
})
    .then(async (result) => {
        console.log('Success Result: ', result);

        await Service.findByIdAndUpdate(service._id, { photo: result.Location });

        return res
            .status(200)
            .json({ success: true, message: 'Service photo added successfully', url:    result.Location });
    })
    .catch((err) => {
        console.log(err);
        return next(new ErrorResponse('Failed to upload file to S3', 500));
    });
  });

我的实用程序文件将文件上传到 S3:

const AWS = require('aws-sdk');

const uploadToS3 = (options) => {
// Set the AWS Configuration
AWS.config.update({
    accessKeyId: process.env.AWS_S3_ACCESS_KEY,
    secretAccessKey: process.env.AWS_S3_SECRET_KEY,
    region: 'us-east-2',
});

// Create S3 service object
const s3 = new AWS.S3({ apiVersion: '2006-03-01' });

// Setting up S3 upload parameters
const params = {
    Bucket: 'toolbox-uploads',
    Key: options.fileName, // File name you want to save as in S3
    Body: options.fileData, //
};

// Return S3 uploading function as a promise so return url can be handled properly
return s3.upload(params).promise();
};

module.exports = uploadToS3;

我的路由器:

const express = require('express');
const {
 servicePhotoUpload
} = require('../controllers/service');

const Service = require('../models/Service');

router.route('/:id/photo').put(protect, authorize('publisher', 'business', 'admin'),  servicePhotoUpload);
 module.exports = router;

以上代码 100% 工作。

我有点困惑,因为有不同的方法,谷歌和堆栈溢出都没有对我有用,而且它们都没有获取返回 url 并保存到数据库中。

我想制作单独的实用程序文件以将多个文件上传到 3,就像我为单个文件所做的那样,以便在任何地方使用它们。该文件应返回上传的网址,以便我可以更新我的数据库。 我已经尝试过 multer-s3,但没有适合我的解决方案。

【问题讨论】:

  • link 看看这是我几天前帮助解决的非常相似的情况
  • @Erykj97 谢谢,但是如何将上传的文件 url 保存到数据库中?
  • 我在那里为您发布了一个答案,以便其格式整齐@Arslan Ameer

标签: node.js mongodb express amazon-s3 mongoose


【解决方案1】:

这种方法对您来说可能会有所不同,但这就是我能够解决相同问题的方法。

首先你需要

  • 穆尔特
  • multer-s3
  • aws-sdk

我创建了一个 FileUpload 类来处理单次和多次上传(我还需要能够上传 pdf 和视频文件),这是我的构造函数,请注意,我还从 aws 指定了有问题的 s3-bucket。

this.s3 = new AWS.S3({
        accessKeyId: process.env.S3_ACCESS_KEY_ID,
        secretAccessKey: process.env.S3_SECRET_KEY,
        Bucket: 'name_of_s3_bucket',
    });

我在类中创建了一个名为 upload 的方法。代码如下

 upload(path, type) {
    let ext = 'jpeg';
    const multerFilter = (req, file, cb) => {
        if (type === 'image') {
            if (file.mimetype.startsWith(this.type)) {
                cb(null, true);
            } else {
                cb(
                    new AppError(
                        'Not an Image! Please upload only images',
                        400
                    ),
                    false
                );
            }
        } else if (type === 'pdf') {
            ext = 'pdf';
            const isPdf = file.mimetype.split('/')[1];
            if (isPdf.startsWith(this.type)) {
                cb(null, true);
            } else {
                cb(
                    new AppError('Not a pdf! Please upload only pdf', 400),
                    false
                );
            }
        }
    };

    const upload = multer({
        storage: multers3({
            acl: 'public-read',
            s3: this.s3,
            bucket: 'name_of_s3_bucket',
            metadata: function (req, file, cb) {
                cb(null, { fieldName: file.fieldname });
            },
            key: function (req, file, cb) {
                let filename = `user-${
                    req.user.id
                }/${path}/${uuid.v4()}-${Date.now()}.${ext}`;
                // eslint-disable-next-line camelcase
                const paths_with_sub_folders = [
                    'auditions',
                    'biography',
                    'movies',
                ];
                if (paths_with_sub_folders.includes(path)) {
                    filename = `user-${req.user.id}/${path}/${
                        req.params.id
                    }/${uuid.v4()}-${Date.now()}.${ext}`;
                }
                cb(null, filename);
            },
        }),
        fileFilter: multerFilter,
        limits: {
            fileSize: 5000000,
        },
    });

    return upload;
}

为了使用上述内容,我将类导入任何需要上传功能的控制器并调用以下内容。

旁注:忽略路径代码(这只是为文件生成唯一文件名的一种方式)

const upload = new FileUpload('image').upload('profile-images', 'image');
exports.uploadUserPhoto = upload.array('photos', 10);

然后我在调用以下之前使用了 uploadUserPhoto 作为中间件

exports.addToDB = catchAsync(async (req, res, next) => {
if (!req.files) return next();
req.body.photos = [];
Promise.all(
    req.files.map(async (file, i) => {
        req.body.photos.push(file.key);
    })
);

next();

});

概括地说,这是流程,首先,将您的照片上传到 s3 并获取 req.files,然后查看该 req.files 对象,将它们传递到您的 req 对象的数组字段中,最后保存它们在您的数据库中。

注意:您必须保证 req.file 循环,因为任务是异步的

我的最终路由器看起来像这样

router
.route('/:id')
.put(uploadUserPhoto, addToDB, updateProfile)

【讨论】:

  • 感谢您的回复。只是混乱。 const upload = new FileUpload('image').upload('profile-images', 'image'); exports.uploadUserPhoto = upload.array('photos', 10); 函数调用中的“个人资料图像”和图像以及“照片”,10 是什么?有 10 个文件吗?
  • 你是对的,'profile-images'是文件的路径,这是我命名上传文件以便于跟踪的约定,'image'是文件类型。 10 是文件数。如果你看一下上传方法,你可以看到它是如何使用的。
  • 您真正需要的是上传多个文件,我在代码中也有,应该可以帮助您解决问题。
【解决方案2】:

Item.js

您的模型可以有一个名为 images 的字段,它是数组类型。

const mongoose = require("mongoose");

const ItemSchema = mongoose.Schema({
  images: {
    type: [],
  },
});

module.exports = mongoose.model("Items", ItemSchema);

您映射对象数组并仅提取您要存储的数据,在此示例中,它是键,它是为每个上传的图像赋予的唯一名称。

route.js

router.post("/", verify, upload.array("image"), async (req, res) => {
  
  const { files } = req;
  const images = [];
  files.map((file) => {
    images.push(file.key);
  });

  try {
    new Item({
      images,
    }).save();
    res.status(200).send({message: "saved images to db"})
  }catch(err){
    res.status(400).send({message: err})
  }
  
});

如果这符合您的要求,请告诉我

【讨论】:

    猜你喜欢
    • 2017-09-25
    • 2020-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-06
    • 2022-11-03
    相关资源
    最近更新 更多