# 前端下载普通文件与二进制流文件

前端下载文件通常会遇到这两种情况

  1. 文件上传到资源服务器,后端只保存了文件地址,前端拿到后端返回的文件地址直接下载。
  2. 文件就存在后端服务器上(通常是临时根据前端参数动态生成,用完就删),后端读取文件后向前端返回文件的二进制流。

下面以下载 excel 文件为例,分别模拟这是这两种情况。

# 通过文件地址直接下载

新建一个项目,在项目中新建一个空的文件夹 service 模拟一个服务,在文件夹内新建一个 test.xlsx,然后在根目录上新建一个 index.html 模拟前端。

安装 serve 用来启动静态资源服务器。

yarn global add serve

进入 service 目录,启动服务

cd service
serve -s

此时在页面中放置一个 a 标签,并写上 download 属性,在浏览器中打开点击下载。

img

这种相当于一个 get 请求,浏览器直接访问该静态资源地址,download 属性告诉这个浏览器这个 a 标签不是打开页面预览而是直接下载。

这与通常在实际项目中通过 ajax 请求接口无关,只需要按照请求,因为后端返回的只是文件的地址,那到底之后绑定在 a 标签上或者通过 window.open() 都可以进行下载。

# 二进制流文件下载

这种情况一般就是在开发中通过 ajax 请求接口的方式,比如 post 请求,前端传递若干参数,后端返回一个二进制流。

关闭之前启动的静态服务,新建 service.js 文件,我们用 node 来写一个简单的服务。

const http = require("http");
const fs = require("fs");

const server = http.createServer((req, res) => {
  // 下载接口
  if (req.url === "/download") {
    res.writeHead(200, {
      "Content-type": "application/vnd.ms-excel", // 返回 excel
      // 跨域设置
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "content-type",
    });

    // 异步读取文件内容
    fs.readFile("test.xlsx", (err, data) => {
      // 返回二进制文件流
      res.end(data);
    });
  }
});

server.listen(3000, () => {
  console.log("Node.js server is running at port 3000!");
});

启动该服务:

node service.js

返回项目根目录,我们引入 axios 来做 ajax 请求。

yarn add axios

我们修改 index.html,移除 a 标签,替换为一个按钮,并为按钮增加点击事件:

<body>
  <button id="btn">下载</button>
</body>
<script src="node_modules/axios/dist/axios.min.js"></script>
<script>
	const btn = document.getElementById("btn");

  btn.onclick = function () {
  	axios({
    	method: "post",
      url: "http://localhost:3000/download",
      data: {
      	test: "test data",
      },
    }).then((res) => {
      console.log(res.data);
    });
  };
</script>

此时点击下载,可以看到打印出来是一堆乱码。

其实原理和上面直接通过文件地址访问是一样的,现在我们的目标便是将这些二进制数据转换成一个文件 url。

我们修改一下 axios的配置:

<body>
  <button id="btn">下载</button>
</body>
<script src="node_modules/axios/dist/axios.min.js"></script>
<script>
	const btn = document.getElementById("btn");

  btn.onclick = function () {
  	axios({
    	method: "post",
      url: "http://localhost:3000/download",
      data: {
      	test: "test data",
      },
      responseType: 'blob'
    }).then((res) => {
			const url = window.URL.createObjectURL(res.data);
      const a = document.createElement("a");
      a.href = url;
      a.download = "test.xlsx";
      document.body.appendChild(a);
      a.click();
      a.remove();
    });
  };
</script>

点击下载,成功转换成文件。

ps:这种转换有一个问题在于格式不一定会正确,因为是文件系统自动转换的,如果想要做到精确转换,则 responseType 使用 arraybuffer 格式,然后手动生成 blob。