使用 Cloudfront、nodejs 和 react 从 s3 下载视频

我从未意识到单击按钮下载文件会如此令人头疼,但事实就是如此。经过反复尝试,我做了我们作为开发人员最擅长的事情 — 寻找解决方案。

情况如下:

我使用 S3 和 CloudFront 传输视频文件。视频在后端进行了加密和访问限制,以确保没有人可以直接从我的 S3 存储桶中获取它们。用户可以观看和下载视频。

尝试 1:基本下载逻辑

最初,我尝试了典型的方法:

const handleDownload = () => {
    const link = document.createElement("a");
    link.href = videoUrl;
    link.download = `${title || "video"}-thook.mp4`; // Provide a default filename if `title` is unavailable.
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link); // Cleanup after the download is triggered
  };

这招管用…… 有点。它没有下载,而是重定向到另一个页面并开始播放视频。

罪魁祸首?我在 S3 上传过程中设置了“ContentType”。

const param = {  
  Bucket: process.env.AWS_S3_BUCKET_NAME,  
  Key: `${directory}/${uuid()}-${file.originalname}`,  
  Body: file.buffer,  
  // ContentType: file.mimetype ❌ (Removed this)  
};  

await s3.upload(param).promise();

删除“ContentType”设置后,文件终于开始在桌面上正确下载。

尝试 2:移动版 Safari 和其他浏览器问题

下一个问题是什么?移动设备上的 Safari 和其他一些浏览器仍然坚持打开视频而不是下载它。

为了解决这个问题,我决定使用 Node.js 从后端传输视频并设置适当的标头来强制下载:

import axios from "axios";
const downloadWithSignedURL = async (req: Request, res: Response) => {
  const signedUrl = req.query.signedUrl as string;
  const filename = (req.query.filename as string) || "video.mp4";

  if (!signedUrl) {
    return res.status(400).send("Signed URL is required.");
  }

  try {
    // Fetch the video stream using axios
    const response = await axios.get(signedUrl, {
      responseType: "stream",
    });

    if (response.status === 200) {
      // Set headers to force download
      res.setHeader("Content-Disposition", `attachment; filename="${filename}-THOOK.mp4"`);
      res.setHeader("Content-Type", "video/mp4");

      // Pipe the video stream to the client
      response.data.pipe(res);
    } else {
      res.status(response.status || 500).send("Failed to fetch video.");
    }
  } catch (error) {
    console.error("Error handling download:", error);
    res.status(500).send("Failed to handle download.");
  }
};
//route.ts
...
router.get("/downloadVideo", DownloadController.downloadWithSignedURL);

在前端,我调整了逻辑以与此后端路由进行交互:

//in react 

const handleDownload = () => {  
  const downloadUrl = `${API_ROUTE}/downloadVideo?signedUrl=${encodeURIComponent(url)}&filename=${encodeURIComponent(name || "video.mp4")}`;  
  window.open(downloadUrl, "_blank"); 
  };

最终解决方案:无缝下载,无需新标签

为了提高用户体验,我修改了逻辑,无需打开新选项卡即可直接触发下载:

const handleDownload = () => {  
  if (!url) {  
    alert("Please provide the signed URL.");  
    return;  
  }  
  try {  
    const downloadUrl = `${API_ROUTE}/downloadVideo?signedUrl=${encodeURIComponent(url)}&filename=${encodeURIComponent(name || "video.mp4")}`;  

    const link = document.createElement("a");  
    link.href = downloadUrl;  
    link.setAttribute("download", name || "video.mp4");  
    document.body.appendChild(link);  
    link.click();  
    link.remove();  
  } catch (error) {  
    console.error("Download error:", error);  
  }  
};

瞧!点击按钮后,下载立即开始,不会中断。

这就是我创建下载功能的方法:

附注

以下是该过程中的一些关键见解:

  • S3 ContentType 问题:设置 ContentType 会导致文件始终采用指定格式,而不是下载。删除它可修复桌面下载问题。
  • 浏览器兼容性:Safari 和一些浏览器需要带有强制下载标头的后端流以保持一致性。
  • 更好的用户体验:动态创建元素允许无需打开新标签即可下载,从而创造更流畅的用户体验。
  • 感谢阅读🙇‍♂️。