如何将数据传递到流星方法?

时间:2019-02-19 11:40:58

标签: reactjs amazon-web-services meteor

我正在研究一种从流星服务器上传到AWS S3存储桶并响应前端的方法。

我已经定义了以下文件

server/methods.js

import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';

const AWS = require('aws-sdk')
const s3_bucket = "bucket-name"

import { mediaFiles } from '../imports/api/files.collection';

const s3  = new AWS.S3({
    accessKeyId: '<key>',
    secretAccessKey: '<secret>',
    endpoint: 's3.eu-west-2.amazonaws.com',
    region: 'eu-west-2',
    signatureVersion: 'v4'
});

Meteor.methods({
    'aws.getUploadId' (filename, filetype) {
        let params = {
            Bucket: s3_bucket,
            Key: filename,
            ContentType: filetype
        }
        return new Promise((resolve, reject) => {
            s3.createMultipartUpload(params, (err, data) => {
                if (err) reject(err)
                if (data) resolve(data.UploadId)
            })
        })
    },

    'aws.uploadPart' (filename, blob, upload_id, index) {
        let params = {
            Bucket: s3_bucket,
            Key: filename,
            PartNumber: index,
            UploadId: upload_id,
        }

        return new Promise((resolve, reject) => {
            s3.uploadPart(params, (err, data) => {
                if (err) reject(err)
                if (data) resolve(data)
            })
        })
    },

    'aws.completeUpload' (filename, upload_id, upload_parts) {
        console.log("aws.completeUpload called")
        console.log(`filename: ${filename}\nID: ${upload_id}\nUpload_parts****${upload_parts}****`)
        let params = {
            Bucket: s3_bucket,
            Key: filename,
            UploadId: upload_id,
            MultipartUpload: {Parts: upload_parts}
        }

        return new Promise((resolve, reject) => {
            s3.completeMultipartUpload(params, (err, data) => {
                if (err) reject(err)
                if (data) resolve(data)
            })
        })
    },
});

upload.js # client side

import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { Page, Icon, ProgressBar, Input, Select } from 'react-onsenui';
import _ from 'underscore';

import Navbar from './Navbar';

class Upload extends Component {

    state = { 
        uploadId : '',
        media_file : null,
        filename : '' 
    }

    setUploadFileParameters = (e) => {
        e.preventDefault();
        e.stopPropagation();
        console.log('setUploadFileParameters called')
        const media_file = e.target.files[0]
        const filename = media_file.name
        const filetype = media_file.type

        Meteor.call('aws.getUploadId', filename, filetype, (err, res) => {
            if (err) console.log("Error getting id: ", err)
            if (res) {
                this.setState({ media_file: media_file, filename: filename, uploadId: res })
            }
        })
    }

    uploadIt = (e) => {
        e.preventDefault();
        const t = e.target
        const upload_id = this.state.uploadId
        const media_file = t.media_file.files[0]
        console.log(`mediafile: ${media_file}`)

        try {
            const FILE_CHUNK_SIZE = 10000000 // 10MB
            const fileSize = media_file.size
            const filename = media_file.name
            const NUM_CHUNKS = Math.round(fileSize / FILE_CHUNK_SIZE) + 1
            let start, end, blob
            let upload_parts = []

            for (let index = 1; index < NUM_CHUNKS + 1; index++) {
                start = (index - 1)*FILE_CHUNK_SIZE
                end = (index)*FILE_CHUNK_SIZE
                blob = (index < NUM_CHUNKS) ? media_file.slice(start, end) : media_file.slice(start)

                // Puts each file part into the storage server
                Meteor.call('aws.uploadPart', filename, blob, upload_id, index, (err, res) => {
                    if (err) console.log("uploading part error ", err)
                    if (res) {
                        // console.log("RES: ", typeof res, res)
                        upload_parts.push({Etag: res.ETag, PartNumber: index})    
                    }
                })
            }

            // Generate the parts list to complete the upload
            // Calls the CompleteMultipartUpload endpoint in the backend server
            console.log("upload_parts: ", upload_parts)

            Meteor.call('aws.completeUpload', filename, upload_id, upload_parts, (err, res) => {
                console.log("Complete upload called *****")
                if (err) console.log("Complete upload err: ", err)
                if (res) console.log("Complete upload res: ", res)
            })
        }
        catch(err) {
            console.log(err)
        }
    }

    render() {
        const { showMenu } = this.props
        console.log("State: ", JSON.stringify(this.state))

            return (
                <Page renderToolbar={Navbar('Upload', showMenu)}>
                    <div className="form-container">

                    {Meteor.user() &&
                        <form onSubmit={(e) => this.uploadIt(e)}>

                            <p>File</p>

                            <Input
                                type="file"
                                id="fileinput"
                                ref="fileinput"
                                name="media_file"
                                onChange={e => this.setUploadFileParameters(e)}
                            />
                            <br/>

                            <button
                                type="submit"
                                value="Upload"
                                className="btn upload-work-button" 
                            >
                                Upload
                            </button>
                        </form>
                    }
                    </div>
                </Page>
            )
        }
    }
export default Upload;

我遇到的问题是upload_parts的内容没有传递到流星后端服务器。后端服务器上的控制台日志不返回任何内容。它甚至不返回undefined。 我需要帮助。

2 个答案:

答案 0 :(得分:2)

您的数组为空,因为您调用了异步函数来填充该数组,所以流星在将其发送到服务器时将其删除。

您需要同步填充数组,或将它们包装为promises。

    ....
    const uploadParts = (filename, blob, upload_id, index) => {
       return new Promise((resolve, reject) => resolve(
       Meteor.call('aws.uploadPart', filename, blob, upload_id, index, (err, res) => {
           upload_parts.push({Etag: res.ETag, partNumber: res.index})
       }))
    }
    let promises = []
    for (let index = 1; index < NUM_CHUNKS + 1; index++) {
        start = (index - 1)*FILE_CHUNK_SIZE
        end = (index)*FILE_CHUNK_SIZE
        blob = (index < NUM_CHUNKS) ? media_file.slice(start, end) : 
        media_file.slice(start)
        promises.push(uploadParts(filename, blob, upload_id, index))
    }
    Promise.all(promises).then(() => {
       Meteor.call('aws.completeUpload', filename, upload_id, upload_parts, (err, res) => {
          console.log("Complete upload called *****")
          if (err) console.log("Complete upload err: ", err)
          if (res) console.log("Complete upload res: ", res)
       })
    })

在此处查看文档('asyncCallback'):https://docs.meteor.com/api/methods.html#Meteor-call

答案 1 :(得分:0)

感谢@Nathan Schwarz的回复。您真的帮了我这个忙。这就是我终于解决难题的方法。

const uploadParts = (filename, blob, upload_id, index) => {
    return new Promise(
        (resolve, reject) => 
        Meteor.call('aws.uploadPart', filename, blob, upload_id, index, (err, res) => {
            resolve(res)
        })
    )
}

for (let index = 1; index < NUM_CHUNKS; index++) {
    start = (index - 1)*FILE_CHUNK_SIZE
    end = (index)*FILE_CHUNK_SIZE
    blob = (index < NUM_CHUNKS) ? media_file.slice(start, end) : media_file.slice(start)

    const b = new Blob([blob], {type:filetype})
    const c = {size: blob.size, type:filetype}
    console.log("Media ", media_file.size, media_file)
    console.log("Blob: ", blob.size, blob)
    console.log("B: ", b.size, b)

    promises.push(uploadParts(filename, c, upload_id, index))
}

Promise.all(promises).then(res => {
    res.forEach((r, index) => 
        upload_parts.push({ETag: r.ETag, PartNumber: index+1})
    )
    console.log("upload_parts: ", upload_parts)
    Meteor.call('aws.completeUpload', filename, upload_id, upload_parts, (err, res) => {
        if (err) console.log("Complete upload err: ", err)
        if (res) console.log("Complete upload res: ", res)
    })
})

使用此代码upload_partsMeteor.call('aws.completeUpload',...)之前完成

请注意我如何履行uploadParts中的承诺。它是从后端服务器的工作方式得出的。如果将回调传递给Meteor.call(),则只能从后端获取返回值。后端本身返回一个Promise,如下所示。

'aws.uploadPart' (filename, blob, upload_id, index) {
    console.log("aws.uploadPart method")

    console.log("file blob: ", blob)
    console.log("filename: ", filename)

    let params = {
        Bucket: s3_bucket,
        Key: filename,
        PartNumber: index,
        UploadId: upload_id,
        Body: blob,
    }

    return new Promise((resolve, reject) => {
        s3.uploadPart(params, (err, data) => {
            if (err) reject(err)
            if (data) {
                console.log("Upload part return ", data)
                resolve(data)
            }
        })
    })