需求

最近一个WEB项目有一个功能,在一段表格数据中进行导出excl功能,由于采用的前后段分离式开发,思路是通过ajax发送POST请求,携带需要生成excl的数据的id集合给后端,后端收集查询,然后生成文件进行下载。
不幸的是,使用ajax,ajax的返回值类型是json,text,html,xml类型,或者可以说ajax的发送,接受都只能是string字符串,不能流类型,所以无法实现文件下载,强用会出现response冲突。如果非要使用ajax的话,只能通过返回值得到生成的文件相关url。然后在回调函数里通过创建一个iframe,并设置其src值为文件url,或者一个对文件生成流的处理url,这样操作来实现文件下载且页面无刷新。
但是后端说不想生成临时文件,只想输出流数据。于是,我的需求如下:

  • 可以发送POST数据。
  • 可以接受文件下载 。

原理

  • 服务端提供一个接受POST请求的接口,输出流文件。
  • 客户端使用form + iframe 进行数据构造和请求。

服务端代码

首先我们使用Express来创建一个Node.js的服务,新建项目,安装以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// index.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({
extended: true // 必须为true
}));
app.use("/", express.static("../"));
app.post("/download", (req, res) => {
// 必须 让浏览器强制下载文件
res.setHeader("Content-Type", "application/force-download");
// res.setHeader("Content-Type", "application/octet-stream"); // 可选
// res.setHeader("Content-Type", "application/download"); // 可选
res.setHeader("Content-Disposition", "attachment; filename=" + "Report.txt");
res.send(req.body);
});
app.listen(3000, () => {
console.log("listen to 3000");
});

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const download = (url, data) => {
// 首先创建一个name为“__iframe_downloader__”的隐藏的iframe标签,防止页面跳出
const iframeName = "__iframe_downloader__";
iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.name = iframeName;
document.body.appendChild(iframe);
// 创建一个隐藏的form标签
const form = document.createElement("form");
form.style.display = "none";
form.target = iframeName;
form.method = "post";
form.acceptCharset = "utf-8";
form.action = url;
// 通过form发送json数据请参考 https://darobin.github.io/formic/specs/json/
const traverse = (obj, key) => {
key = key || "";
const result = [];
for (let prop in obj) {
const value = obj[prop];
typeof value === "object" ? result.push.apply(result, traverse(value, key + "[" + prop + "]")) : result.push({ name: key ? key + "[" + prop + "]" : prop, value: value });
}
return result;
};
const inputs = document.createDocumentFragment();
const fields = traverse(data);
fields.forEach(field => {
const input = document.createElement("input");
input.type = "hidden";
input.name = field.name;
input.value = field.value;
inputs.appendChild(input);
});
form.appendChild(inputs);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
};
// 调用 解决传输数据的问题
download("http://localhost:3000", {
a: "12",
b: ["1", "2"]
});

本人简单包装了下,你可以访问项目主页查看更多详情。