使用 NodeJS 将文件上传到 Amazon S3

Posted

技术标签:

【中文标题】使用 NodeJS 将文件上传到 Amazon S3【英文标题】:Upload a file to Amazon S3 with NodeJS 【发布时间】:2015-03-17 02:49:28 【问题描述】:

我在尝试将文件上传到我的 S3 存储桶时遇到了问题。一切正常,除了我的文件参数似乎不合适。我正在使用 Amazon S3 sdk 从 nodejs 上传到 s3。

这些是我的路线设置:

var multiparty = require('connect-multiparty'),
    multipartyMiddleware = multiparty();
app.route('/api/items/upload').post(multipartyMiddleware, items.upload);

这是 items.upload() 函数:

exports.upload = function(req, res) 
    var file = req.files.file;
    var s3bucket = new AWS.S3(params: Bucket: 'mybucketname');
    s3bucket.createBucket(function() 
        var params = 
            Key: file.name,
            Body: file
        ;
        s3bucket.upload(params, function(err, data) 
            console.log("PRINT FILE:", file);
            if (err) 
                console.log('ERROR MSG: ', err);
             else 
                console.log('Successfully uploaded data');
            
        );
    );
;

Body 参数设置为"hello" 之类的字符串可以正常工作。根据doc,Body 参数必须采用 (Buffer, Typed Array, Blob, String, ReadableStream) 对象数据。 但是,上传文件对象失败并显示以下错误消息:

[Error: Unsupported body payload object]

这是文件对象:

 fieldName: 'file',
  originalFilename: 'second_fnp.png',
  path: '/var/folders/ps/l8lvygws0w93trqz7yj1t5sr0000gn/T/26374-7ttwvc.png',
  headers: 
    'content-disposition': 'form-data; name="file"; filename="second_fnp.png"',
     'content-type': 'image/png' ,
  ws: 
    _writableState: 
       highWaterMark: 16384,
        objectMode: false,
        needDrain: true,
        ending: true,
        ended: true,
        finished: true,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        buffer: [],
        errorEmitted: false ,
     writable: true,
     domain: null,
     _events:  error: [Object], close: [Object] ,
     _maxListeners: 10,
     path: '/var/folders/ps/l8lvygws0w93trqz7yj1t5sr0000gn/T/26374-7ttwvc.png',
     fd: null,
     flags: 'w',
     mode: 438,
     start: undefined,
     pos: undefined,
     bytesWritten: 261937,
     closed: true ,
  size: 261937,
  name: 'second_fnp.png',
  type: 'image/png' 

任何帮助将不胜感激!

【问题讨论】:

您的服务器只需要存储 aws 机密即可创建 s3-signed-urls(使用 aws-sdk)。客户端检索签名的 url,然后使用该 url 在客户端进行任何上传。查看本指南:devcenter.heroku.com/articles/s3-upload-node 【参考方案1】:

所以看起来这里有一些问题。根据您的帖子,您似乎正在尝试使用 connect-multiparty 中间件支持文件上传。该中间件所做的是将上传的文件写入本地文件系统,然后将req.files 设置为上传的文件。

您的路线配置看起来不错,问题似乎与您的items.upload() 函数有关。特别是这部分:

var params = 
  Key: file.name,
  Body: file
;

正如我在答案开头提到的connect-multiparty 将文件写入本地文件系统,因此您需要打开文件并读取它,然后上传它,然后在本地文件系统上将其删除。

也就是说,您可以将方法更新为以下内容:

var fs = require('fs');
exports.upload = function (req, res) 
    var file = req.files.file;
    fs.readFile(file.path, function (err, data) 
        if (err) throw err; // Something went wrong!
        var s3bucket = new AWS.S3(params: Bucket: 'mybucketname');
        s3bucket.createBucket(function () 
            var params = 
                Key: file.originalFilename, //file.name doesn't exist as a property
                Body: data
            ;
            s3bucket.upload(params, function (err, data) 
                // Whether there is an error or not, delete the temp file
                fs.unlink(file.path, function (err) 
                    if (err) 
                        console.error(err);
                    
                    console.log('Temp File Delete');
                );

                console.log("PRINT FILE:", file);
                if (err) 
                    console.log('ERROR MSG: ', err);
                    res.status(500).send(err);
                 else 
                    console.log('Successfully uploaded data');
                    res.status(200).end();
                
            );
        );
    );
;

它的作用是从本地文件系统读取上传的文件,然后将其上传到 S3,然后删除临时文件并发送响应。

这种方法存在一些问题。首先,它的效率并不高,因为对于大文件,您将在编写之前加载整个文件。其次,这个过程不支持大文件的分段上传(我认为在你必须进行分段上传之前,截止是 5 Mb)。

我建议你使用我一直在开发的一个名为 S3FS 的模块,它提供了与 Node.JS 中的原生 FS 类似的接口,但抽象出一些细节,例如 multi -part 上传和 S3 api(以及添​​加一些附加功能,如递归方法)。

如果您要引入 S3FS 库,您的代码将如下所示:

var fs = require('fs'),
    S3FS = require('s3fs'),
    s3fsImpl = new S3FS('mybucketname', 
        accessKeyId: XXXXXXXXXXX,
        secretAccessKey: XXXXXXXXXXXXXXXXX
    );

// Create our bucket if it doesn't exist
s3fsImpl.create();

exports.upload = function (req, res) 
    var file = req.files.file;
    var stream = fs.createReadStream(file.path);
    return s3fsImpl.writeFile(file.originalFilename, stream).then(function () 
        fs.unlink(file.path, function (err) 
            if (err) 
                console.error(err);
            
        );
        res.status(200).end();
    );
;

这会为提供的存储桶和 AWS 凭证实例化模块,然后在存储桶不存在时创建它。然后,当请求上传文件时,我们将打开文件流并使用它将文件写入 S3 到指定路径。这将在后台处理多部分上传片段(如果需要),并且具有通过流完成的好处,因此您不必等待在开始上传之前读取整个文件。

如果您愿意,可以将代码更改为来自Promises 的回调。或者使用pipe() 方法和事件监听器来确定结束/错误。

如果您正在寻找其他方法,请查看 s3fs 的文档,如果您正在寻找其他方法或遇到问题,请随时提出问题。

【讨论】:

我有一个存储桶和一个文件夹。如何为上传到此文件夹的对象指定 ACL。我已将文件夹权限更改为“公开”,但每次上传新对象时它都不是公开的。我在 S3FS 构造函数中尝试了 acl: "public-read" 但它不起作用。 如果您在编写对象时使用的是 S3FS,则可以指定 ACL。检查此链接:docs.aws.amazon.com/AWSjavascriptSDK/latest/AWS/… 否则,您需要在 S3FS 之外上传文件时指定策略。 req.file.originalname 对我有用,而不是 req.file.originalFilename。【参考方案2】:

我发现以下是一个可行的解决方案::

npm install aws-sdk

安装 aws-sdk 后,在需要的地方使用以下代码替换值。

var AWS = require('aws-sdk');
var fs =  require('fs');

var s3 = new AWS.S3();

// Bucket names must be unique across all S3 users

var myBucket = 'njera';

var myKey = 'jpeg';
//for text file
//fs.readFile('demo.txt', function (err, data) 
//for Video file
//fs.readFile('demo.avi', function (err, data) 
//for image file                
fs.readFile('demo.jpg', function (err, data) 
  if (err)  throw err; 



     params = Bucket: myBucket, Key: myKey, Body: data ;

     s3.putObject(params, function(err, data) 

         if (err) 

             console.log(err)

          else 

             console.log("Successfully uploaded data to myBucket/myKey");

         

      );

);

我在这里找到了关于这个主题的完整教程,以防你在寻找参考资料::


How to upload files (text/image/video) in amazon s3 using node.js

【讨论】:

在哪里设置 s3 密钥和秘密? 在实例化新的 AWS.S3() 之前,您需要添加如下内容:AWS.config.update( accessKeyId: 'someKey', secretAccessKey: 'secondKey' ); 如果我想上传本地系统中的一些文件?怎么做??【参考方案3】:

或使用承诺:

const AWS = require('aws-sdk');
AWS.config.update(
    accessKeyId: 'accessKeyId',
    secretAccessKey: 'secretAccessKey',
    region: 'region'
);

let params = 
    Bucket: "yourBucketName",
    Key: 'someUniqueKey',
    Body: 'someFile'
;
try 
    let uploadPromise = await new AWS.S3().putObject(params).promise();
    console.log("Successfully uploaded data to bucket");
 catch (e) 
    console.log("Error uploading data: ", e);

【讨论】:

【参考方案4】:
var express = require('express')

app = module.exports = express();
var secureServer = require('http').createServer(app);
secureServer.listen(3001);

var aws = require('aws-sdk')
var multer = require('multer')
var multerS3 = require('multer-s3')

    aws.config.update(
    secretAccessKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    accessKeyId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    region: 'us-east-1'
    );
    s3 = new aws.S3();

   var upload = multer(
   storage: multerS3(
    s3: s3,
    dirname: "uploads",
    bucket: "Your bucket name",
    key: function (req, file, cb) 
        console.log(file);
        cb(null, "uploads/profile_images/u_" + Date.now() + ".jpg"); //use  
     Date.now() for unique file keys
    
  )
   );

 app.post('/upload', upload.single('photos'), function(req, res, next) 

 console.log('Successfully uploaded ', req.file)

 res.send('Successfully uploaded ' + req.file.length + ' files!')

)

【讨论】:

虽然这段代码 sn-p 可以解决问题,including an explanation 确实有助于提高您的帖子质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!【参考方案5】:

将文件上传到 AWS s3 并发送 url 以响应访问文件。

Multer 是一个用于处理 multipart/form-data 的 node.js 中间件,主要用于上传文件。它被写在 busboy 的顶部以获得最大的效率。检查这个 npm 模块here。

当您发送请求时,请确保标头的 Content-Type 为 multipart/form-data。 我们在响应中发送文件位置,这将提供 url,但如果您想访问该 url,请将存储桶公开,否则您将无法访问它。

上传.router.js

const express = require('express');
const router = express.Router();
const AWS = require('aws-sdk');
const multer = require('multer');
const storage = multer.memoryStorage()
const upload = multer(storage: storage);

const s3Client = new AWS.S3(
    accessKeyId: 'your_access_key_id',
    secretAccessKey: 'your_secret_access_id',
    region :'ur region'
);

const uploadParams = 
         Bucket: 'ur_bucket_name', 
         Key: '', // pass key
         Body: null, // pass file body
;


router.post('/api/file/upload', upload.single("file"),(req,res) => 
    const params = uploadParams;

    uploadParams.Key = req.file.originalname;
    uploadParams.Body = req.file.buffer;

    s3Client.upload(params, (err, data) => 
        if (err) 
            res.status(500).json(error:"Error -> " + err);
        
        res.json(message: 'File uploaded successfully','filename': 
        req.file.originalname, 'location': data.Location);
    );
);

module.exports = router;

app.js

const express = require('express');
const app = express();

const router = require('./app/routers/upload.router.js');
app.use('/', router);

// Create a Server
  const server = app.listen(8080, () => 
  console.log("App listening at 8080"); 
)

【讨论】:

【参考方案6】:

感谢 David,因为他的解决方案帮助我提出了将多部分文件从 Heroku 托管站点上传到 S3 存储桶的解决方案。我使用强大的来处理传入的表单和 fs 来获取文件内容。希望对您有所帮助。

api.service.ts

public upload(files): Observable<any>   
    const formData: FormData = new FormData(); 
    files.forEach(file => 
      // create a new multipart-form for every file 
      formData.append('file', file, file.name);           
    );   
    return this.http.post(uploadUrl, formData).pipe(
      map(this.extractData),
      catchError(this.handleError)); 
  

server.js

app.post('/api/upload', upload);
app.use('/api/upload', router);

上传.js

const IncomingForm = require('formidable').IncomingForm;
const fs = require('fs');
const AWS = require('aws-sdk');

module.exports = function upload(req, res) 
    var form = new IncomingForm();

    const bucket = new AWS.S3(
      
        signatureVersion: 'v4',
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
        region: 'us-east-1'       
      
    ); 

    form.on('file', (field, file) => 

        const fileContent = fs.readFileSync(file.path);

        const s3Params = 
            Bucket: process.env.AWS_S3_BUCKET,
            Key: 'folder/' + file.name,
            Expires: 60,             
            Body: fileContent,
            ACL: 'public-read'
        ;

        bucket.upload(s3Params, function(err, data) 
            if (err) 
                throw err;
                        
            console.log('File uploaded to: ' + data.Location);
            fs.unlink(file.path, function (err) 
              if (err) 
                  console.error(err);
              
              console.log('Temp File Delete');
          );
        );
    );              

    // The second callback is called when the form is completely parsed. 
    // In this case, we want to send back a success status code.
    form.on('end', () =>         
      res.status(200).json('upload ok');
    );

    form.parse(req);

上传图片.component.ts

import  Component, OnInit, ViewChild, Output, EventEmitter, Input  from '@angular/core';
import  ApiService  from '../api.service';
import  MatSnackBar  from '@angular/material/snack-bar';

@Component(
  selector: 'app-upload-image',
  templateUrl: './upload-image.component.html',
  styleUrls: ['./upload-image.component.css']
)

export class UploadImageComponent implements OnInit 
  public files: Set<File> = new Set();
  @ViewChild('file',  static: false ) file;
  public uploadedFiles: Array<string> = new Array<string>();
  public uploadedFileNames: Array<string> = new Array<string>();
  @Output() filesOutput = new EventEmitter<Array<string>>();
  @Input() CurrentImage: string;
  @Input() IsPublic: boolean;
  @Output() valueUpdate = new EventEmitter();
  strUploadedFiles:string = '';
  filesUploaded: boolean = false;     

  constructor(private api: ApiService, public snackBar: MatSnackBar,)  

  ngOnInit()     
  

  updateValue(val)   
    this.valueUpdate.emit(val);  
    

  reset()
  
    this.files = new Set();
    this.uploadedFiles = new Array<string>();
    this.uploadedFileNames = new Array<string>();
    this.filesUploaded = false;
  

  upload()  

    this.api.upload(this.files).subscribe(res =>    
      this.filesOutput.emit(this.uploadedFiles); 
      if (res == 'upload ok')
      
        this.reset(); 
           
    , err => 
      console.log(err);
    );
  

  onFilesAdded() 
    var txt = '';
    const files:  [key: string]: File  = this.file.nativeElement.files;

    for (let key in files) 
      if (!isNaN(parseInt(key))) 

        var currentFile = files[key];
        var sFileExtension = currentFile.name.split('.')[currentFile.name.split('.').length - 1].toLowerCase();
        var iFileSize = currentFile.size;

        if (!(sFileExtension === "jpg" 
              || sFileExtension === "png") 
              || iFileSize > 671329) 
            txt = "File type : " + sFileExtension + "\n\n";
            txt += "Size: " + iFileSize + "\n\n";
            txt += "Please make sure your file is in jpg or png format and less than 655 KB.\n\n";
            alert(txt);
            return false;
        

        this.files.add(files[key]);
        this.uploadedFiles.push('https://gourmet-philatelist-assets.s3.amazonaws.com/folder/' + files[key].name);
        this.uploadedFileNames.push(files[key].name);
        if (this.IsPublic && this.uploadedFileNames.length == 1)
        
          this.filesUploaded = true;
          this.updateValue(files[key].name);
          break;
         
        else if (!this.IsPublic && this.uploadedFileNames.length == 3)
        
          this.strUploadedFiles += files[key].name;          
          this.updateValue(this.strUploadedFiles); 
          this.filesUploaded = true;
          break;
        
        else
        
          this.strUploadedFiles += files[key].name + ",";          
          this.updateValue(this.strUploadedFiles); 
              
      
        
  

  addFiles() 
    this.file.nativeElement.click();  
  

  openSnackBar(message: string, action: string) 
    this.snackBar.open(message, action, 
      duration: 2000,
      verticalPosition: 'top'
    );
     


上传图片.component.html

<input type="file" #file style="display: none" (change)="onFilesAdded()" multiple />
&nbsp;<button mat-raised-button color="primary" 
         [disabled]="filesUploaded" (click)="$event.preventDefault(); addFiles()">
  Add Files
</button>
&nbsp;<button class="btn btn-success" [disabled]="uploadedFileNames.length == 0" (click)="$event.preventDefault(); upload()">
  Upload
</button>

【讨论】:

这篇文章很棒,感谢您的提示!【参考方案7】:

上传 CSV/Excel

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

const s3 = new AWS.S3(
    accessKeyId: XXXXXXXXX,
    secretAccessKey: XXXXXXXXX
);

const absoluteFilePath = "C:\\Project\\test.xlsx";

const uploadFile = () => 
  fs.readFile(absoluteFilePath, (err, data) => 
     if (err) throw err;
     const params = 
         Bucket: 'testBucket', // pass your bucket name
         Key: 'folderName/key.xlsx', // file will be saved in <folderName> folder
         Body: data
     ;
      s3.upload(params, function (s3Err, data) 
                    if (s3Err) throw s3Err
                    console.log(`File uploaded successfully at $data.Location`);
                    debugger;
                );
  );
;

uploadFile();

【讨论】:

【参考方案8】:

为我工作:)

  const fileContent = fs.createReadStream(`$fileName`);
  return new Promise(function (resolve, reject) 
    fileContent.once('error', reject);
    s3.upload(
      
        Bucket: 'test-bucket',
        Key: `$fileName + '_' + Date.now().toString()`,
        ContentType: 'application/pdf',
        ACL: 'public-read',
        Body: fileContent
      ,
      function (err, result) 
        if (err) 
          reject(err);
          return;
        
        resolve(result.Location);
      
    );
  );```

【讨论】:

您能提供更多解释吗?此代码将如何帮助 OP?【参考方案9】:

使用 aws SDK v3

npm install @aws-sdk/client-s3

上传代码

import  S3Client, PutObjectCommand  from "@aws-sdk/client-s3";

/**
* advisable to save your AWS credentials and configurations in an environmet file. Not inside the code
* AWS lib will automatically load the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY if available in your environment
*/
const s3Client = new S3Client( region: process.env.AWS_S3_REGION );

/**
* upload a file
* @param file the file object to be uploaded
* @param fileKey the fileKey. could be separated with '/' to nest the file into a folder structure. eg. members/user1/profile.png
*/
export function uploadFile(file, fileKey)
    s3Client.send(new PutObjectCommand(
       Bucket: process.env.MY_AWS_S3_BUCKET,
       Key: fileKey,
       Body: file
    ));

如果你想下载

import  GetObjectCommand  from "@aws-sdk/client-s3";
/**
 * download a file from AWS and send to your rest client
 */
app.get('/download', function(req, res, next)
    var fileKey = req.query['fileKey'];

    var bucketParams = 
        Bucket: 'my-bucket-name',
        Key: fileKey,
    ;

    res.attachment(fileKey);
    var fileStream = await s3Client.send(new GetObjectCommand(bucketParams));
    // for TS you can add: if (fileStream.Body instanceof Readable)
    fileStream.Body.pipe(res)
);

【讨论】:

以上是关于使用 NodeJS 将文件上传到 Amazon S3的主要内容,如果未能解决你的问题,请参考以下文章

使用 Websockets 和 Nodejs 上传文件

使用 Alamofire 将文件上传到 amazon s3

使用 HTTP PUT 将文件上传到 Amazon S3

如何使用 POSTMAN 应用程序将文件上传到 Amazon S3

将 NodeJS 服务器端口重定向到子域或文件夹(amazon ec2)

将文件从 Android 缓慢上传到 Amazon S3