我使用axios的基本http请求,如GET和POST,它工作得很好。现在我需要能够下载Excel文件。这在axios中可行吗?如果是,谁有一些示例代码?如果不是,我还可以在React应用程序中使用什么来做同样的事情?


当前回答

诀窍是在render()中创建一个不可见的锚标记,并添加一个React ref,允许在我们有axios响应时触发单击:

class Example extends Component {
    state = {
        ref: React.createRef()
    }

    exportCSV = () => {
        axios.get(
            '/app/export'
        ).then(response => {
            let blob = new Blob([response.data], {type: 'application/octet-stream'})
            let ref = this.state.ref
            ref.current.href = URL.createObjectURL(blob)
            ref.current.download = 'data.csv'
            ref.current.click()
        })
    }

    render(){
        return(
            <div>
                <a style={{display: 'none'}} href='empty' ref={this.state.ref}>ref</a>
                <button onClick={this.exportCSV}>Export CSV</button>
            </div>
        )
    }
}

以下是文档:https://reactjs.org/docs/refs-and-the-dom.html。你可以在这里找到类似的想法:https://thewebtier.com/snippets/download-files-with-axios/。

其他回答

实现接收文档的Axios处理程序,数据格式为octect-stream, 数据可能看起来很奇怪PK一些JbxfFGvddvbdfbVVH34365436fdkln作为它的八字节流格式,你可能最终会创建这个数据可能被损坏的文件,{responseType: 'blob'}将数据转换为可读格式,

axios.get("URL", {responseType: 'blob'})
     .then((r) => {
         let fileName =  r.headers['content-disposition'].split('filename=')[1];
         let blob = new Blob([r.data]);
         window.saveAs(blob, fileName);             
      }).catch(err => {
        console.log(err);
      });

你可能尝试过这样的解决方案, 窗口。saveAs(blob, 'file.zip')将尝试将文件保存为zip,但将不会工作,

const downloadFile = (fileData) => {
    axios.get(baseUrl+"/file/download/"+fileData.id)
        .then((response) => {
            console.log(response.data);
            const blob = new Blob([response.data], {type: response.headers['content-type'], encoding:'UTF-8'});
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = 'file.zip';
            link.click();
        })
        .catch((err) => console.log(err))
}
const downloadFile = (fileData) => {
axios.get(baseUrl+"/file/download/"+fileData.id)
    .then((response) => {
        console.log(response);
        //const binaryString = window.atob(response.data)
        //const bytes = new Uint8Array(response.data)
        //const arrBuff = bytes.map((byte, i) => response.data.charCodeAt(i));
        //var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(response.data)));
        const blob = new Blob([response.data], {type:"application/octet-stream"});
        window.saveAs(blob, 'file.zip')
        // const link = document.createElement('a');
        // link.href = window.URL.createObjectURL(blob);
        // link.download = 'file.zip';
        // link.click();
    })
    .catch((err) => console.log(err))
}
function base64ToArrayBuffer(base64) {
    var binaryString = window.atob(base64);
    var binaryLen = binaryString.length;
    var bytes = new Uint8Array(binaryLen);
    for (var i = 0; i < binaryLen; i++) {
        var ascii = binaryString.charCodeAt(i);
        bytes[i] = ascii;
    };

    return bytes;
}

另一个简单的解决方法是,

window.open("URL")

将继续打开不必要的新选项卡,用户可能不得不让允许弹出的工作这段代码,如果用户想同时下载多个文件,所以先去解决方案或如果不尝试其他解决方案也怎么办

诀窍是在render()中创建一个不可见的锚标记,并添加一个React ref,允许在我们有axios响应时触发单击:

class Example extends Component {
    state = {
        ref: React.createRef()
    }

    exportCSV = () => {
        axios.get(
            '/app/export'
        ).then(response => {
            let blob = new Blob([response.data], {type: 'application/octet-stream'})
            let ref = this.state.ref
            ref.current.href = URL.createObjectURL(blob)
            ref.current.download = 'data.csv'
            ref.current.click()
        })
    }

    render(){
        return(
            <div>
                <a style={{display: 'none'}} href='empty' ref={this.state.ref}>ref</a>
                <button onClick={this.exportCSV}>Export CSV</button>
            </div>
        )
    }
}

以下是文档:https://reactjs.org/docs/refs-and-the-dom.html。你可以在这里找到类似的想法:https://thewebtier.com/snippets/download-files-with-axios/。

你需要返回File({file_to_download}, "application/vnd.ms-excel")从后端到前端,在你的js文件中,你需要更新下面写的代码:

function exportToExcel() {
        
        axios.post({path to call your controller}, null,
            {
                headers:
                {
                    'Content-Disposition': "attachment; filename=XYZ.xlsx",
                    'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                },
                responseType: 'arraybuffer',
            }
        ).then((r) => {
            const path= window.URL.createObjectURL(new Blob([r.data]));
            const link = document.createElement('a');
            link.href = path;
            link.setAttribute('download', 'XYZ.xlsx');
            document.body.appendChild(link);
            link.click();
        }).catch((error) => console.log(error));
    }

下载文件(使用Axios和Security)

当您希望使用Axios和一些安全手段下载文件时,这实际上更加复杂。为了防止其他人花太多时间来弄清楚这个问题,让我来给你介绍一下。

你需要做三件事:

配置您的服务器以允许浏览器看到所需的HTTP报头 实现服务器端服务,并使其为下载的文件发布正确的文件类型。 实现Axios处理程序以在浏览器中触发FileDownload对话框

这些步骤大部分都是可行的——但是由于浏览器与CORS的关系而变得相当复杂。一步一个脚印:

1. 配置您的HTTP服务器

当使用传输安全性时,在浏览器中执行的JavaScript(根据设计)只能访问HTTP服务器实际发送的6个HTTP头。如果我们想让服务器为下载建议一个文件名,我们必须通知浏览器,它是“OK”的JavaScript被授予访问其他头,建议的文件名将被传输。

为了便于讨论,让我们假设服务器在一个名为X-Suggested-Filename的HTTP报头中传输建议的文件名。HTTP服务器告诉浏览器,可以将接收到的自定义报头公开给JavaScript/Axios,报头如下:

Access-Control-Expose-Headers: X-Suggested-Filename

配置HTTP服务器来设置此标头的确切方式因产品而异。

有关这些标准标头的完整解释和详细描述,请参见https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers。

2. 实现服务器端服务

您的服务器端服务实现现在必须执行两件事:

创建(二进制)文档并将正确的ContentType分配给响应 为客户端分配包含建议文件名的自定义头文件(X-Suggested-Filename)

这取决于您选择的技术堆栈,以不同的方式完成。我将草拟一个使用JavaEE 7标准的示例,它应该发出一个Excel报告:

    @GET
    @Path("/report/excel")
    @Produces("application/vnd.ms-excel")
    public Response getAllergyAndPreferencesReport() {

        // Create the document which should be downloaded
        final byte[] theDocumentData = .... 

        // Define a suggested filename
        final String filename = ... 
     
        // Create the JAXRS response
        // Don't forget to include the filename in 2 HTTP headers: 
        //
        // a) The standard 'Content-Disposition' one, and
        // b) The custom 'X-Suggested-Filename'  
        //
        final Response.ResponseBuilder builder = Response.ok(
                theDocumentData, "application/vnd.ms-excel")
                .header("X-Suggested-Filename", fileName);
        builder.header("Content-Disposition", "attachment; filename=" + fileName);

        // All Done.
        return builder.build();
    }

该服务现在发出二进制文档(在本例中是Excel报告),设置正确的内容类型,并发送一个自定义HTTP报头,其中包含保存文档时使用的建议文件名。

3.为Received文档实现一个Axios处理程序

这里有一些陷阱,所以让我们确保所有细节都正确配置:

服务响应@GET(即HTTP GET),因此Axios调用必须是' Axios . GET(…)'。 文档以字节流的形式传输,因此必须告诉Axios将响应视为HTML5 Blob。(例如responseType: 'blob')。 在本例中,文件保护程序JavaScript库用于弹出浏览器对话框。然而,你可以选择另一个。

Axios的框架实现将如下所示:

     // Fetch the dynamically generated excel document from the server.
     axios.get(resource, {responseType: 'blob'}).then((response) => {

        // Log somewhat to show that the browser actually exposes the custom HTTP header
        const fileNameHeader = "x-suggested-filename";
        const suggestedFileName = response.headers[fileNameHeader];
        const effectiveFileName = (suggestedFileName === undefined
                    ? "allergierOchPreferenser.xls"
                    : suggestedFileName);
        console.log(`Received header [${fileNameHeader}]: ${suggestedFileName}, effective fileName: ${effectiveFileName}`);

        // Let the user save the file.
        FileSaver.saveAs(response.data, effectiveFileName);

        }).catch((response) => {
            console.error("Could not Download the Excel report from the backend.", response);
        });

有几个关键点大部分答案都被忽略了。

我将在这里进行更深入的解释。

TLDR;

如果您正在创建一个标记链接并通过浏览器请求启动下载,那么

总是调用window.URL.revokeObjectURL(url);。否则就会有 不必要的内存尖峰。 不需要使用document.body. appendchild (link);将创建的链接附加到文档主体,从而避免以后不必要地删除子链接。


有关组件代码和更深入的分析,请进一步阅读

首先要弄清楚试图从中下载数据的API端点是公共的还是私有的。你能控制服务器吗?


如果服务器响应

Content-Disposition: attachment; filename=dummy.pdf
Content-Type: application/pdf

浏览器总是会尝试下载名称为'dummy.pdf'的文件


如果服务器响应

Content-Disposition: inline; filename=dummy.pdf
Content-Type: application/pdf

浏览器将首先尝试打开一个本地文件阅读器,如果名称为'dummy.pdf',否则它将开始下载文件。


如果服务器没有响应以上两个头

如果没有设置下载属性,浏览器(至少chrome)将尝试打开该文件。如果设置,它将下载文件。在url不是blob的情况下,文件名将是最后一个路径参数的值。


除此之外,请记住使用transfer - encoding:从服务器传输大量数据。这将确保客户端知道在缺少Content-Length报头的情况下何时停止读取当前请求

私人档案

import { useState, useEffect } from "react";
import axios from "axios";

export default function DownloadPrivateFile(props) {
  const [download, setDownload] = useState(false);

  useEffect(() => {
    async function downloadApi() {
      try {
        // It doesn't matter whether this api responds with the Content-Disposition header or not
        const response = await axios.get(
          "http://localhost:9000/api/v1/service/email/attachment/1mbdoc.docx",
          {
            responseType: "blob", // this is important!
            headers: { Authorization: "sometoken" },
          }
        );
        const url = window.URL.createObjectURL(new Blob([response.data])); // you can mention a type if you wish
        const link = document.createElement("a");
        link.href = url;
        link.setAttribute("download", "dummy.docx"); //this is the name with which the file will be downloaded
        link.click();
        // no need to append link as child to body.
        setTimeout(() => window.URL.revokeObjectURL(url), 0); // this is important too, otherwise we will be unnecessarily spiking memory!
        setDownload(false);
      } catch (e) {} //error handling }
    }

    if (download) {
      downloadApi();
    }
  }, [download]);

  return <button onClick={() => setDownload(true)}>Download Private</button>;
}


公共档案

import { useState, useEffect } from "react";
export default function DownloadPublicFile(props) {
  const [download, setDownload] = useState(false);

  useEffect(() => {
    if (download) {
      const link = document.createElement("a");
      link.href =
        "http://localhost:9000/api/v1/service/email/attachment/dummy.pdf";
      link.setAttribute("download", "dummy.pdf");
      link.click();
      setDownload(false);
    }
  }, [download]);

  return <button onClick={() => setDownload(true)}>Download Public</button>;
}

很高兴知道:

Always control file downloads from server. Axios in the browser uses XHR under the hood, in which streaming of responses is not supported. Use onDownloadProgress method from Axios to implement progress bar. Chunked responses from server do not ( cannot ) indicate Content-Length. Hence you need some way of knowing the response size if you are using them while building a progress bar. <a> tag links can only make GET HTTP requests without any ability to send headers or cookies to the server (ideal for downloading from public endpoints) Brower request is slightly different from XHR request made in code.

参考:AJAX请求和常规浏览器请求之间的区别