NodeJS 在React和节点不工作的情况下上传块中的文件

63lcw9qa  于 2023-03-01  发布在  Node.js
关注(0)|答案(2)|浏览(110)

我正在使用React和Nodejs。我们有scenerio,我们必须在AWS上上传10GB的文件。我知道这不可能与单个请求,这就是为什么我把它分成块,但它仍然不工作,并抛出错误后,几个块上传。以下是代码
前端

import React, { useState, useEffect } from "react";
import { UploadDropzone } from "../../../components/icons";
import { Progress } from "antd";
import Dropzone from "react-dropzone";
import axios from "axios";
import { notification } from "antd";
import { isEmptyArray } from "formik";

const chunkSize = 1048576 * 25;

function UploadLocalVideo(props) {
  const { setUploadUrl, storageId, acceptFileType, isResourceBucket, callBackFun } = props;
  const [progress, setProgress] = useState(0);
  const [beginingOfTheChunk, setBeginingOfTheChunk] = useState(0);
  const [endOfTheChunk, setEndOfTheChunk] = useState(chunkSize);
  const [fileToBeUpload, setFileToBeUpload] = useState({});
  const [progressUpload, setProgressUpload] = useState(0);
  const [fileSize, setFileSize] = useState(0);
  const [chunkCount, setChunkCount] = useState(0);
  const [uploadId, setUploadId] = useState("");
  const [name, setName] = useState("");
  const [parts, setParts] = useState([]);
  const [counter, setCounter] = useState(1);
  const [percent, setPercent] = useState(0);
  const [fileType, setFileType] = useState("");
  const [uploading, setUploading] = useState(false);
  const onUpload = async (files) => {
    if (!isEmptyArray(files)) {
      setUploading(true);
    }
    let percent = 0;
    let name = Math.random().toString(36).substring(2, 10);
    resetChunkProperties();
    const _file = files[0];
    name = (name + _file?.name?.replace(/ +/g, "")).replace(/[{()}]/g, "");
    setName(name);
    setFileType(_file.type);
    setFileSize(_file.size);

    const _totalCount = _file.size % chunkSize == 0 ? _file.size / chunkSize : Math.floor(_file.size / chunkSize) + 1;
    setChunkCount(_totalCount);
    percent = 100 / _totalCount;
    setPercent(percent);
    setFileToBeUpload(_file);
    setProgress(1);
  };

  const resetChunkProperties = () => {
    setProgressUpload(0);
    setCounter(1);
    setBeginingOfTheChunk(0);
    setEndOfTheChunk(chunkSize);
    setUploadId("");
    setName("");
    setParts([]);
    setPercent(0);
    setProgress(0);
    setFileType("");
  };
  useEffect(() => {
    if (fileSize > 0) {
      fileUpload(counter);
    }
  }, [fileToBeUpload, progressUpload]);

  const fileUpload = () => {
    setCounter(counter + 1);
    if (counter <= chunkCount) {
      var chunk = fileToBeUpload.slice(beginingOfTheChunk, endOfTheChunk);
      uploadChunk(chunk);
    }
  };

  const uploadChunk = async (chunk) => {
    try {
      const formData = new FormData();
      formData.append("file", chunk);
      formData.append("name", name);
      formData.append("fileType", fileType);
      formData.append("chunkSize", chunk.size);
      formData.append("currentIndex", counter);
      formData.append("totalChunk", chunkCount);
      formData.append("uploadId", uploadId);
      formData.append("EtagArray", JSON.stringify(parts));
      formData.append("storageId", storageId);
      formData.append("isResourceBucket", isResourceBucket);
      await axios({
        method: "post",
        url: `${process.env.REACT_APP_NEW_API_HOSTNAME}/upload-chunk`,
        data: formData,
      }).then((response) => {
        if (response.data.uploadStatus == "uploading") {
          setBeginingOfTheChunk(endOfTheChunk);
          setEndOfTheChunk(endOfTheChunk + chunkSize);
          setUploadId(response.data.uploadId);
          setParts([...parts, response.data.etag]);
          setProgress(parseInt((progressUpload + 1) * percent));
          setProgressUpload(progressUpload + 1);
        } else if (response.data.uploadStatus == "complete") {
          setUploadUrl(response.data.url); // set url or response url
          callBackFun(fileToBeUpload);
          setProgress(100);
          setUploading(false);
        } else if (response.data.uploadStatus == "failed" || response.data.status == false) {
          notification["error"]({ message: response.data.message });
          setProgress(0);
          setUploading(false);
        } else if (response.data.success == false) {
          notification["error"]({ message: "Storage not found" });
          setProgress(0);
          setUploading(false);
        }
      });
    } catch (error) {
      console.log(error, "error");
    }
  };
  return (
    <div className="form-group">
      <Dropzone
        onDrop={(acceptedFiles) => {
          onUpload(acceptedFiles);
        }}
        accept={acceptFileType}
        disabled={uploading}
      >
        {({ getRootProps, getInputProps }) => (
          <div className="dropzone">
            <div className="dropzone-inner" {...getRootProps()}>
              <input {...getInputProps()} />
              <div className="dropzone-icon">
                <UploadDropzone />
              </div>
              <div className="dropzone-title">Upload a File</div>
              <div className="dropzone-subtitle">
                Click to <u>browse</u>, or drag & drop your file here
              </div>
              <Progress strokeLinecap="butt" type="line" percent={progress} /> {progress > 1 ? `${progress} %` : ""}
            </div>
          </div>
        )}
      </Dropzone>
    </div>
  );
}

export default UploadLocalVideo;

后端

const handler = async (request, reply) => {
    try {
        let uploadId = (_.get(request.payload, "uploadId", ""));
        let fileName = (_.get(request.payload, "name", ""));
        let multiParts = JSON.parse(_.get(request.payload, "EtagArray", []))
        let storageId = _.get(request, "payload.storageId", "")
        let dataBuffer = Buffer.from(request.payload.file)
        let currentChunkIndex = parseInt(request.payload.currentIndex);
        let totalChunk = parseInt(request.payload.totalChunk);
        let isResourceBucket = JSON.parse(_.get(request, "payload.isResourceBucket", false))
        let region = ""
        let credentials = {}
        let squery = {
            name: { $in: ["aws"] }
        }
        if (isResourceBucket && storageId == "") {
            squery._id = mongoose.Types.ObjectId("62e112750e3d4dada1b9a3c0")
        } else {
            squery._id = mongoose.Types.ObjectId(storageId)
        }

        if (currentChunkIndex <= totalChunk) {
            let storages = await Storage.findOne(squery)
            if (storages && storages.credentials) {
                credentials = await StreamService.decrypt_storage_credentials(storages.credentials, 'aws')
                region = (credentials.region).replace("s3.", "").replace(".amazonaws.com", "").replace("s3-", "")
            } else {
                return reply({
                    status: false,
                    message: 'Storage not found',
                    uploadStatus: "failed"
                })
            }
        }
        AWS.config.update({
            accessKeyId: credentials.access_key,
            secretAccessKey: credentials.secret_key,
            region: region
        })

        const s3 = new AWS.S3({
            params: {
                Bucket: credentials.bucket_name,
            },
            // endpoint,
            signatureVersion: 'v4',
            region: region,
            apiVersion: '2006-03-01'
        })

        let filename = `uploadFile/${fileName}`;

        if (currentChunkIndex == 1) {
            uploadId = await getUploadId(filename, s3, credentials.bucket_name)
            console.log("currentChunkIndex", " == ", currentChunkIndex, { uploadId })
        }

        if (currentChunkIndex < totalChunk) {
            let etag = await uploadParts(filename, credentials.bucket_name, dataBuffer, currentChunkIndex, uploadId, s3)
            return reply({
                status: true,
                uploadId,
                etag,
                uploadStatus: "uploading",
                message: "uploading"
            })
        } else if (currentChunkIndex == totalChunk) {
            let finalChunk = await uploadParts(filename, credentials.bucket_name, dataBuffer, currentChunkIndex, uploadId, s3)
            let etag = { ETag: finalChunk.ETag, PartNumber: currentChunkIndex };
            multiParts.push(etag)
            let location = await completeFileMultiPartUpload(filename, credentials.bucket_name, uploadId, multiParts, s3, credentials.cdn_suffix);
            location = location.replace("%2F","/")
            console.log({ location })
            if (location) {
                return reply({
                    status: true,
                    url: location,
                    uploadStatus: "complete",
                    message: "upload completed"
                })
            }
        }
    } catch (error) {
        logger.error(error)
        return reply({
            success: false,
            message: error.message
        });
    }
};

这些是日志


所以我有几个问题。1.当我们上传10GB的文件时,它是否会消耗节点内存?如果“是”,那么解决方案是什么?
谢谢

ocebsuys

ocebsuys1#

AWS Lambda不支持有效负载大小大于6mb的调用。
您可以在AWS Lambda documentation页面的"功能配置、部署和执行"下查看此限制和其他限制。另外,请注意AWS S3对多部分上传有自己的限制,如您在AWS S3 documentation页面所见。
从你的前端代码看,你的文件上传块似乎被设置为每个25mb;这远远超过了该极限。
回答你的第一个问题:是的,当您通过Lambda将文件数据上传到S3时,上传的文件数据被分配到函数存储器上-尽管这似乎不是问题所在。
一个可能的解决方案是将块大小设置为接近5mb(~1048576 * 5)--但不精确到6mb,因为http请求数据也计入大小限制。
最后,从最佳实践的Angular 来看,AWS Lambda不太适合文件上传,特别是大文件。亚马逊通过S3 pre-signed urls支持并鼓励将文件直接上传到AWS S3。lambda函数只负责为每个块生成预签名的URL,并将其响应到前端,这也会从本质上降低计算成本。
您可以从AWS本身查看这个amazing blog post,描述如何实现这一点。

ttp71kqs

ttp71kqs2#

节点的堆内存

如果在块到达时不将上载流的块流式传输到服务器上的设备存储,您将只能上载适合Node堆内存的文件。这里的“上载流”是Node和/或Express中的request对象,它是可读流。
有关如何增加堆内存的链接可以在Web上找到,但我在www.example.com站点上找不到明确的信息node.org。* 您还需要在服务器上提供超过10 Gb的可用内存。*

从前端上传表单数据

在研究这个主题时,我发现最常见的解决方法是将文件作为formData对象的一部分上传,在上传大文件时,仍然需要将文件内容流传输到服务器上的磁盘。

将文件数据作为请求正文发送

一个不太常见的解决方案是使用fetch(或XHTTPRequest对象)将文件作为请求主体上传。上传的数据仍需要流式传输到服务器上的本地存储。此方法不适用于14.1之前的Node版本,尽管将request流动态流式传输到磁盘,但该版本仍会耗尽堆空间(更新Node修复了此问题)。
将文件作为正文中的二进制数据直接上载还需要在前端向fetch提供一个相当复杂的init对象,包括

  • Content Disposition作为文件名,
  • Content-type表示文件类型,
  • Last-modiied表示修改日期,并在本地实现mine
  • UTF毫秒的IBM风格Last modified-milliseconds报头,
  • 自定义头"Payload-Length,以八位字节为单位,用于在发送请求主体之前显示文件大小,以实现错误控制和安全目的。

注意这个答案是针对文章中关于Node的1号和2号问题的-没有提到AWS或React,我声称没有经验。也有可能发布的错误是在内存耗尽之前发生的。

相关问题