开发了这么久了,关于文件下载一直半懂不懂得,咦,为什么这个链接自动下载了,为什么这个链接会默认在浏览器预览展示,下载需要鉴权怎么办等等 花了半天时间整理了一下,方便日后复习巩固
后端部分
新建一个node服务,写上一些简单代码(直接GPT快速写的,ai时代,写这种基础功能真的很方便)
const express = require("express");
const path = require("path");
const fs = require("fs");
const app = express();
const PORT = 3000;
// 文件下载接口
app.get("/download", (req, res) => {
const filename = "报告.pdf";
const filePath = path.join(__dirname, "downloads", filename);
if (fs.existsSync(filePath)) {
const filestream = fs.createReadStream(filePath);
filestream.pipe(res);
} else {
res.status(404).send("File not found.");
}
});
// 启动服务器
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
好了,运行node, 直接打开 http://localhost:3000/download
结果是在网页内部显示pdf,这样嘛,如果换成xls,会怎么样
哦豁,居然是下载,但是呢,这个下载又有问题,1:没有文件名 2:格式也不识别 3:最主要的是,同一个接口,可能会出现有时候会进行下载,有时候又直接是在网页打开,这显然是不可接受的。好了,接下来一一解决下
//添加这两行代码
res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
res.setHeader(
"Content-Disposition",
`attachment; filename*=UTF-8''abc.xls`
);
加上这代码之后,嗯,刷新链接,所有链接都是直接下载了,这样符合我的期望 然后,解释一下Content-Disposition,
这是官方解答,大概意思就是一个标准协议,然后告知浏览器内容怎么展示,把里面“attachment”换成“inline”,刷新就发现,pdf的会默认在浏览器中展示,总结归纳以下我得见解,浏览器会自动识别文件内容,如果是inline属性,并且支持在线预览的,就会直接预览显示,反之,则下载,好叭,这个我个人推荐一直用attachment
噢,对了,上面abc.xls是写的固定的,因为刚测试下载发现中文命名会报错,先用英文演示一下,然后我们解决一下中文问题
中文报错
很简单,在node里面用encodeURIComponent转义一下即可
下面是关于
node的完整代码
const express = require("express");
const path = require("path");
const fs = require("fs");
const cors = require("cors");
const app = express();
const PORT = 3000;
// 使用cors中间件
app.use(
cors({
origin: "*", // 允许所有来源,注意生产环境中需要设置成特定的域名
methods: ["GET", "POST"],
allowedHeaders: ["Authorization", "Content-Type"],
exposedHeaders: ["Content-Disposition"],
})
);
// 普通文件下载
app.get("/download", (req, res) => {
const filename = "报告.pdf";
const filePath = path.join(__dirname, "downloads", filename);
if (fs.existsSync(filePath)) {
const encodedFileName = encodeURIComponent(path.basename(filePath), "utf-8");
res.setHeader(
"Content-Disposition",
`attachment; filename*=UTF-8''${encodedFileName}`
);
getFile(res, filePath);
} else {
res.status(404).send("File not found.");
}
});
// 鉴权文件下载
app.get("/downloadAndToken", (req, res) => {
const authHeader = req.headers["authorization"];
if (!authHeader || authHeader !== "123") {
return res.status(403).send("Authorization failed");
}
const filename = "报告.pdf";
const filePath = path.join(__dirname, "downloads", filename);
if (fs.existsSync(filePath)) {
const encodedFileName = encodeURIComponent(path.basename(filePath), "utf-8");
res.setHeader(
"Content-Disposition",
`attachment; filename*=UTF-8''${encodedFileName}`
);
getFile(res, filePath);
} else {
res.status(404).send("File not found.");
}
});
const getFile = (res, filePath) => {
const filestream = fs.createReadStream(filePath);
filestream.pipe(res);
}
// 启动服务器
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
理清楚了后端的逻辑,现在需要弄前端逻辑了
前端部分
方式一:
直接下载
a标签下载
<a href="http://localhost:3000/download" download>下载</a>
js下载
let a = document.createElement('a');
a.style = 'display: none';
a.href = 'http://localhost:3000/download';
document.body.appendChild(a);
document.body.removeChild(a);
方式二:
鉴权下载
const response = await axios.get('http://localhost:3000/downloadAndToken', {
responseType: 'blob',
headers: {
'Authorization': `123`
},
});
const objectUrl = URL.createObjectURL(response.data)
const a = document.createElement('a')
a.setAttribute('href', objectUrl)
a.setAttribute('download', '报告.pdf')
a.click()
URL.revokeObjectURL(objectUrl)
有一点说一下,关于下载,之前通过接口请求下载,文件名一直是前端自定义的,殊不知其实header里面content-disposition有带过来,好的,展示对应代码
const disposition = response.headers['content-disposition'];
const filename = disposition.match(/filename\*=UTF-8''(.+)/)[1];
return decodeURIComponent(filename);
以下是前端三种方式下载的
全部代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a download target="_blank">a标签下载</a>
<button onclick="getInterface()">接口下载</button>
<button onclick="getInterfaceAndToken()">鉴权接口下载</button>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const BASE_URL = 'http://localhost:3000'
const INTERFACE_URL = BASE_URL + '/download'
const INTERFACE_TOKEN_URL = BASE_URL + '/downloadAndToken'
document.querySelector('a').href = INTERFACE_URL
const getFileName = (response) => {
const disposition = response.headers['content-disposition'];
const filename = disposition.match(/filename\*=UTF-8''(.+)/)[1];
return decodeURIComponent(filename);
}
const downloadFile = (response) => {
const objectUrl = URL.createObjectURL(response.data)
const a = document.createElement('a')
a.setAttribute('href', objectUrl)
a.setAttribute('download', getFileName(response))
a.click()
URL.revokeObjectURL(objectUrl)
}
const getInterface = async () => {
const response = await axios.get(INTERFACE_URL, {
responseType: 'blob',
});
downloadFile(response)
}
const getInterfaceAndToken = async () => {
const response = await axios.get(INTERFACE_TOKEN_URL, {
responseType: 'blob',
headers: {
'Authorization': `123`
},
});
downloadFile(response)
}
</script>
</body>
</html>
补充说明,最好加上responseType为blob,不加或者其他类型,能生成,但是就比较麻烦点了
最后总结以下,虽然干了这么久前端,几乎没怎么写过文档,发现自己写文章的能力是真的不行, 还是得多练习练习文字组织能力,加油。