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

How do I pass data to meteor method?

我正在研究一种从流星服务器上传到 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 内容没有被传递到 meteor 后端服务器。后端服务器上的控制台日志没有 return 任何内容。它甚至没有 return undefined。 我需要这方面的帮助。

你的数组是空的,因为你调用了异步函数来填充数组,所以当你发送到服务器时流星删除它。

你需要同步填充数组,或者将它们包装到 promise 中。

    ....
    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

感谢@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(),我只能从后端获取 return 值。后端本身 return 是一个承诺,如下所示。

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