使用NodeJS将文件上载到Amazon S3

时间:2015-01-19 06:12:07

标签: node.js amazon-s3 multipartform-data

我在尝试将文件上传到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 param设置为"hello"之类的字符串可以正常工作。根据{{​​3}},Body param必须使用(缓冲区,类型化数组,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' }

任何帮助将不胜感激!

8 个答案:

答案 0 :(得分:88)

所以看起来这里有一些问题。根据您的帖子,您似乎尝试使用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类似的接口,但抽象出一些细节例如多部分上传和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的文档,如果您正在寻找其他方法或遇到问题,请随时打开问题。

答案 1 :(得分:12)

我发现以下是一个有效的解决方案::

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

答案 2 :(得分:4)

或使用承诺:

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

答案 3 :(得分:1)

上传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();

答案 4 :(得分:0)

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!')

})

答案 5 :(得分:0)

将文件上传到AWS s3并发送URL作为响应来访问文件。

Multer是用于处理multipart / form-data的node.js中间件,主要用于上传文件。它是在busboy之上编写的,以实现最大效率。检查此npm模块here

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

upload.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 :(得分:0)

感谢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);

upload.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);
}

upload-image.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'
    });
  }   

}

upload-image.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 :(得分:0)

对我有用:)

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