如何在ReactJS前端和FastAPI后端中使用Axios下载文件?

5
我试图创建一个docx文件,并将其发送给前端客户端应用程序,以便它可以下载到用户的本地机器上。 我正在使用FastAPI进行后端开发。 我还使用python-docx库来创建Document

以下代码用于创建docx文件并将其保存到服务器。

@app.post("/create_file")
async def create_file(data: Item):
    document = Document()
    document.add_heading("file generated", level=1)
    document.add_paragraph("test")
    document.save('generated_file.docx')
    return {"status":"Done!"}

以下代码用于将创建的docx文件作为FileResponse发送给客户端。
@app.get("/generated_file")
async def download_generated_file():
    file_path = "generated_file.docx"
    return FileResponse(file_path, media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document', filename=file_path)

客户端(我正在使用ReactJS):
createFile = async () => {
   const data = {
      start: this.state.start,
      end: this.state.end,
      text: this.state.text,
   };
   await axios.post("http://localhost:8000/create_file", data).then(() => {
      console.log("processing completed!");
   });
};

downloadFile = async () => {
   await axios.get("http://localhost:8000/generated_file").then((res) => {
      const url = URL.createObjectURL(new Blob([res.data]));
      const link = document.createElement("a");
      link.href = url;
      link.setAttribute("download", "generated.txt");
      link.click();
   });
};

当调用downloadFile函数时,会下载generated.docx文件。然而,docx 文件总是损坏的,无法打开。 我尝试使用txt文件,它可以正常工作。我需要使用docx文件,那我该怎么办?
2个回答

5

在 Axios 的 GET 请求中,你必须确保将 responseType 参数设置为 blob。一旦从 API 获取到 response,你需要将 Blob 对象 (即 response.data) 传递给 URL.createObjectURL() 函数。下面是一个完整的示例,演示如何在前端使用 Axios 或 Fetch API 创建和下载文件 (Document)。本答案利用了 thisthis 答案中的方法和代码摘录,以及 这里这里 的答案。请参考上述答案以获取更多关于以下方法的详细信息。为了演示目的,下面的示例使用了 Jinja2Templates,但是你可以以类似的方式在你的 ReactJS 应用程序中使用下面的脚本。

app.py

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import FileResponse
from docx import Document

app = FastAPI()
templates = Jinja2Templates(directory="templates")


@app.get('/')
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})
    
@app.post("/create")
def create_file():
    document = Document()
    document.add_heading("file generated", level=1)
    document.add_paragraph("test")
    document.save('generated_file.docx')
    return {"status":"Done!"}
    
@app.get("/download")
def download_generated_file():
    file_path = "generated_file.docx"
    return FileResponse(file_path, media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document', filename=file_path)

使用 Axios

tempaltes/index.htnl

<!DOCTYPE html>
<html>
   <head>
      <title>Create and Download a Document</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
   </head>
   <body>
      <input type="button" value="Create Document" onclick="createFile()">
      <div id="response"></div><br>
      <input type="button" value="Download Document " onclick="downloadFile()">
      <script>
         function createFile() {
            axios.post('/create', {
                  headers: {
                     'Accept': 'application/json',
                     'Content-Type': 'application/json'
                  }
               })
               .then(response => {
                  document.getElementById("response").innerHTML = JSON.stringify(response.data);
               })
               .catch(error => {
                  console.error(error);
               });
         }
         
         function downloadFile() {
            axios.get('/download', {
                  responseType: 'blob'
               })
               .then(response => {
                  const disposition = response.headers['content-disposition'];
                  filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
                  if (filename.toLowerCase().startsWith("utf-8''"))
                     filename = decodeURIComponent(filename.replace("utf-8''", ''));
                  else
                     filename = filename.replace(/['"]/g, '');
                  return response.data;
               })
               .then(blob => {
                  var url = window.URL.createObjectURL(blob);
                  var a = document.createElement('a');
                  a.href = url;
                  a.download = filename;
                  document.body.appendChild(a); // append the element to the dom
                  a.click();
                  a.remove(); // afterwards, remove the element  
               })
               .catch(error => {
                  console.error(error);
               });
         }
      </script>
   </body>
</html>

使用Fetch API

tempaltes/index.htnl

<!DOCTYPE html>
<html>
   <head>
      <title>Create and Download a Document</title>
   </head>
   <body>
      <input type="button" value="Create Document" onclick="createFile()">
      <div id="response"></div><br>
      <input type="button" value="Download Document" onclick="downloadFile()">
      <script>
         function createFile() {
            fetch('/create', {
                  method: 'POST',
                  headers: {
                     'Accept': 'application/json',
                     'Content-Type': 'application/json'
                  }
               })
               .then(response => response.text())
               .then(data => {
                  document.getElementById("response").innerHTML = data;
               })
               .catch(error => {
                  console.error(error);
               });
         }
         
         function downloadFile() {
            fetch('/download')
               .then(response => {
                  const disposition = response.headers.get('Content-Disposition');
                  filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
                  if (filename.toLowerCase().startsWith("utf-8''"))
                     filename = decodeURIComponent(filename.replace("utf-8''", ''));
                  else
                     filename = filename.replace(/['"]/g, '');
                  return response.blob();
               })
               .then(blob => {
                  var url = window.URL.createObjectURL(blob);
                  var a = document.createElement('a');
                  a.href = url;
                  a.download = filename;
                  document.body.appendChild(a); // append the element to the dom
                  a.click();
                  a.remove(); // afterwards, remove the element
               })
               .catch(error => {
                  console.error(error);
               });
         }
      </script>
   </body>
</html>

这意味着整个文件首先被加载到内存中,然后保存到磁盘上,对吗?有没有什么方法可以避免这种情况? - leonbloy
1
@leonbloy 上面的示例是基于 OP 的需求,其中文件必须由两个不同的端点创建和提供。当然,您可以通过直接返回 Response 来避免这种情况。请参阅此处此处,以及答案。 - Chris

-1

使用Axios库在JavaScript中(包括React.js/Vue.js/Angular等)下载文件,您可以将responseType设置为blob并在Axios响应中处理文件下载。

以下是如何实现此操作的示例:

import axios from 'axios';

// Function to download the file
async function downloadFile() {
  try {
    const response = await axios.post('your-api-endpoint', {
      // Request payload data if needed
    }, {
      responseType: 'blob', // Set the response type to 'blob'
    });

    // Extract the filename from the Content-Disposition header
    const contentDisposition = response.headers['content-disposition'];
    const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
    const matches = filenameRegex.exec(contentDisposition);
    const filename = matches !== null && matches[1] ? matches[1].replace(/['"]/g, '') : 'download';

    // Create a URL object from the response data
    const url = window.URL.createObjectURL(new Blob([response.data]));

    // Create a temporary link element
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', filename); // Set the filename obtained from the response

    // Append the link to the DOM and trigger the download
    document.body.appendChild(link);
    link.click();

    // Clean up the URL object and remove the link from the DOM
    window.URL.revokeObjectURL(url);
    document.body.removeChild(link);
  } catch (error) {
    console.error('Error downloading file:', error);
  }
}

// Call the downloadFile function to initiate the file download
downloadFile();

在这个示例代码中:
  • 确保将your-api-endpoint替换为实际的API端点URL,该端点返回文件。
  • 在Axios请求配置中,将responseType设置为blob,以指示您期望在响应中收到二进制文件。
  • 收到响应后,使用window.URL.createObjectURL(new Blob([response.data]))从响应数据创建URL对象。
  • 创建一个临时链接元素,并将href属性设置为创建的URL。
  • contentDisposition变量从响应中提取Content-Disposition头的值。
  • filenameRegex正则表达式用于匹配和提取标题值中的文件名。
  • 提取的文件名然后用作链接元素的download属性的值:link.setAttribute('download', filename)
  • 如果无法从响应标头获取文件名,则默认为download
  • 将链接附加到DOM并通过调用link.click()触发下载。
  • 启动下载后,使用window.URL.revokeObjectURL(url)document.body.removeChild(link)清除URL对象并从DOM中删除链接元素。
  • 如果在过程中出现错误,则会在catch块中捕获。
注意:此示例假定API端点正确返回响应正文中的文件数据。根据您的特定用例调整API端点和有效载荷。

1
请用您自己的话描述一下您创建这个答案的过程。这个答案完全是您自己原创的吗? - sideshowbarker
这不够详细还是你不知道如何使用?@sideshowbarker - Trang Ha Viet
1
我想问一下,您是否可以在这里发表评论,以某些形式的简短陈述开头,例如“这个答案的创建方式是…”或“这个答案中代码及其注释的创建方式是…”。也就是说,请用自己的话简要描述您用于研究问题和找到解决方案的高层次过程概述。 - sideshowbarker
1
@sideshowbarker我认为通过比较这个回答的第一段文字以及他们在上面的评论与回答中其余的文字(包括代码部分的评论),很明显这个回答不是他们自己的原创作品,而是一个AI工具的结果。Trang,请看一下临时政策:ChatGPT被禁止 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接