使用 React 将文件上传到 AWS S3 时使用进度处理程序

Using progress handler when uploading files to AWS S3 with React

我最近才处理 AWS SDK,因此如果我的方法完全是胡说八道,请原谅。

我想将一个简单的媒体文件上传到我的 S3。我正在关注 this tutorial and so far I am able to upload files without a problem. For userbility a progress bar would be a nice extra and therefore I was researching how to achieve this. I quickly found that the but we should use @aws-sdk/lib-storage。使用这个库,我仍然可以将文件上传到 S3,但我无法让进度跟踪器工作! 我认为这与我没有完全理解如何在 React 组件中处理 async 有关。

所以这是我的缩小组件示例(我在这里使用 Chakra UI)

const TestAWS: React.FC = () => {
  const inputRef = useRef<HTMLInputElement | null>(null);
  const [progr, setProgr] = useState<number>();

  const region = "eu-west-1";
  const bucketname = "upload-test";

  const handleClick = async () => {
    inputRef.current?.click();
  };

  const handleChange = (e: any) => {

    console.log('Start file upload');

    const file = e.target.files[0];
    const target = {
      Bucket: bucketname,
      Key: `jobs/${file.name}`,
      Body: file,
    };

    const s3 = new S3Client({
      region: region,
      credentials: fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region: region }),
        identityPoolId: "---MY ID---",
      }),
    });

    const upload = new Upload({
      client: s3,
      params: target,
    });

    const t = upload.on("httpUploadProgress", progress => {
      console.log("Progress", progress);

      if (progress.loaded && progress.total) {
        console.log("loaded/total", progress.loaded, progress.total);
        setProgr(Math.round((progress.loaded / progress.total) * 100)); // I was expecting this line to be sufficient for updating my component
      }
    });
    await upload.done().then(r => console.log(r));
  };

console.log('Progress', progr);

return (
    <InputGroup onClick={handleClick}>
      <input ref={inputRef} type={"file"} multiple={false} hidden accept='video/*' onChange={e => handleChange(e)} />
      <Flex layerStyle='uploadField'>
        <Center w='100%'>
          <VStack>
            <PlusIcon />
            <Text>Choose Video File</Text>
          </VStack>
        </Center>
      </Flex>
      {progr && <Progress value={progr} />}
    </InputGroup>
  );
};

export default TestAWS;

所以基本上我看到事件被触发 (开始文件上传)。然后需要一段时间,我在控制台中看到了 Promise 结果和 Progress, 100。这对我来说意味着状态变量得到更新(至少一次)但是组件没有重新渲染?

我在这里做错了什么?任何帮助表示赞赏!

好的,我已经找到解决办法了。 state 变量的回调工作正常并且做了它应该做的事情。但是 Upload 对象的配置已关闭。在深入研究源代码后,我发现事件侦听器只有在 上传者上传了更多数据 时才会被触发。因为 Uploader 将上传分块,所以您有两个单独的配置参数,它们允许您将上传分成单独的块。所以

const upload = new Upload({
  client: s3,
  params: target,
  queueSize: 4,          // 4 is minimum
  partSize: 5*1024*1024  // 5MB is minimum
});

当我们上传的文件 大于 5MB 时,基本上会完成这项工作! 只有这样事件才会再次触发并更新状态变量。

因为这个上传器是为处理大文件上传而设计的,这完全是有道理的,我们可以根据我们要上传的文件简单地调整queueSizepartSize。像

let queueSize = 10;
const file = event.target.files[0];

let partSize = file.size / (10 * 1024 * 1024);    // 1/10th of the file size in MB

const upload = new Upload({
  client: s3,
  params: target,
  queueSize: partSize > 5 queueSize : undefined,
  partSize: partSize > 5 ? partsize : undefined
});

显然,这可以做得更复杂,但我不想花太多时间在这上面,因为它不是原始问题的一部分。

结论

如果您的文件足够大 (>5MB),您将看到进度更新,具体取决于您选择拆分的块数(5MB 或更多)文件。

因为这只影响原始示例中的 handleChange 方法,我 post 为了完整性

const handleChange = async ( event ) => {
  const file = event.target.files[0]

  const target = {
    Bucket: 'some-S3-bucket',
    Key: `jobs/${file.name}`,
    Body: file,
  };

  const s3 = new S3Client({
    region: 'your-region',
    credentials: fromCognitoIdentityPool({
      client: new CognitoIdentityClient({ region: 'your-region' }),
      identityPoolId: "your-id",
    }),
  });

  // this will default to queueSize=4 and partSize=5MB
  const upload = new Upload({
    client: s3,
    params: target
  });

  upload.on("httpUploadProgress", progress => {
    console.log('Current Progress', progress);
    setProgr(progress);
  });

  await upload.done().then(r => console.log(r));
} 

也许这对遇到同样问题的人有帮助。

今天遇到完全相同的问题(使用 Vue)后,我看到了您的回答!

确实你是对的:AWS SDK JS v3 事件只在每个 part 触发,这一点都不清楚,我也浪费时间调试它。就像 4MB 的文件一样,它只会以 100% 的速度触发。

如您所说,您可以试验部分大小 ,但 最小值为 5MB 等连接速度较慢时,我发现上传可能会像您一样卡住等待 5MB 获取 any 数据。唔。所以我所做的是查看正在上传的文件的大小。如果它低于阈值(比如 25MB,或任何适用的大小),那么一次上传所有内容可能是安全的,因为您实际上并不需要分段上传。所以我 做了一个预签名的 URL (https://aws.amazon.com/blogs/developer/generate-presigned-url-modular-aws-sdk-javascript/) 可以用来使用 axios PUT (因为 fetch 还不支持进度事件).

这样您就可以对大文件使用 upload(您实际上需要分块上传并且 5MB 占文件大小的百分比很小),并使用预先签名的 URL小文件,因此更新频率更高。

两者可以使用相同的进度事件处理程序。

this.$axios
  .request({
     method: "PUT",
     url: SIGNED-URL-HERE,
     data: file,
     timeout: 3600 * 1000,
     onUploadProgress: this.uploadProgress,
  })
  .then((data) => {
     console.log("Success", data);
  })
  .catch((error) => {
     console.log("Error", error.code, error.message);
  });

不理想,但有帮助。