【问题标题】:Upload a file to Amazon S3 with NodeJS使用 NodeJS 将文件上传到 Amazon S3
【发布时间】: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" 之类的字符串可以正常工作。根据docBody 参数必须采用 (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

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


【解决方案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)
        });
        

        【讨论】:

          猜你喜欢
          • 2019-12-13
          • 1970-01-01
          • 2021-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-12-10
          相关资源
          最近更新 更多