服务端 fs.stat()
方法获取文件字节流长度通过 fs.createReadStream
获取文件可读流,并返回给前端。前端在 onprogress
中获取文件下载进度,并通过动态创建 a 标签进行文件下载。
服务端代码
const fs = require('fs');
const moment = require('moment');
const getFileTest = function (request, response) {
// 通过某种方式获取到文件路径
const filePath = './public/test.xls';
fs.stat(filePath, function (error, stats) {
// fs.stat 获取文件字节长度
if (error) {
response.status(400).send({
status: 400,
message: '文件大小获取失败,' + error.message,
time: moment().format('YYYY-MM-DD HH:mm:ss')
});
} else {
let rs = fs.createReadStream(filePath);
rs.addListener('error', (err) => {
response.status(400).send({
status: 400,
message: err.message,
time: moment().format('YYYY-MM-DD HH:mm:ss')
});
})
rs.addListener('open', () => {
response.writeHead(200, {
// 文件字节长度,前端下载进度条需要用到
'Content-Length': stats.size,
'Content-Type': 'application/octet-stream',
// 设置文件名的响应头
'File-Name': 'test.xls',
// 开放响应头访问权限给前端
'Access-Control-Expose-Headers': 'File-Name',
});
// 将可读流传给响应对象response
rs.pipe(response);
})
}
})
}
前端代码
getFile() {
const xhr = new XMLHttpRequest();
xhr.timeout = 1000 * 1000;
xhr.responseType = "blob";
xhr.open("get", "http://127.0.0.1:8888/download");
xhr.onprogress = (e) => {
if (e.total > 0) {
// 获取进度,用于页面回显
// 如果 e.total 始终为 0,则可能是服务端未设置 Content-Length 响应头
this.progress = parseInt((e.loaded / e.total) * 100, 10);
}
};
xhr.onload = (e) => {
const fileName = xhr.getResponseHeader("File-Name");
this.downloadFile(xhr.request, fileName);
};
xhr.send();
},
downloadFile(fileBlob, fileName) {
const blob = new Blob([fileBlob], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
if ("download" in document.createElement("a")) {
const link = document.createElement("a");
link.download = fileName;
link.style.display = "none";
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
} else {
navigator.msSaveBlob(blob, fileName);
}
},