因为要用到这样一个场景,需要下载系统的使用教程,所以在前端项目中就提供了一个能够下载系统教程的一个按钮,供使用者进行下载。
所以就试着写一下这个功能,以一个demo的形式进行演示,在学习的过程中也发现了中文路径的严重问题!!!!!!!!!!!!
首先描述一下接下来展示的代码的效果:
就是在界面上又对应的文本和下载对应文件的按钮,这里因为是1.0版本,就不对实现这个功能做成函数进行封装了,如果有时间的话还可以封装成一个函数,通过传参来下载指定的文件。
这里就是真正的符合应用场景的前端通过发送请求来下载服务器上的文件这样的场景,而不是通过ZZAI回答的那样使用window.location.href的方式,吐槽一波
直接上完整的代码,随后进行介绍
后端代码【node.js】:
const express = require('express');
const path = require('path');
const cors = require('cors'); // 引入 CORS 中间件
const fs=require('fs');
const app = express();
const PORT = 3001;
// 启用 CORS,允许所有域访问(仅用于测试,生产环境中应限制允许的域)
app.use(cors());// 设置静态文件的目录为 public
app.use(express.static(path.join(__dirname, 'public')));
console.log(12,path.join(__dirname, 'public/操作手册.pdf'))
// 定义一个路由来返回文件的 URL 而不是直接发送文件
app.get('/api/download-url/操作手册.pdf', (req, res) => {const filePath = path.join(__dirname, 'public/操作手册.pdf');res.json({ fileUrl: `http://localhost:${PORT}/download/操作手册.pdf` });
});
// 修改后的路由,用于返回 abc.txt 文件的内容
app.get('/check-file/abc.txt', (req, res) => {const filePath = path.join(__dirname, 'public/abc.txt');fs.readFile(filePath, 'utf8', (err, data) => {if (err) {if (err.code === 'ENOENT') {res.status(404).send('文件不存在');} else {res.status(500).send('读取文件时出错: ' + err.message);}} else {res.send(data); // 返回文件内容}});
});
app.get('/download/abc.txt', (req, res) => {const filePath = path.join(__dirname, 'public/abc.txt');fs.readFile(filePath, (err, data) => {if (err) {res.status(500).send('文件读取错误');} else {res.setHeader('Content-Disposition', 'attachment; filename="abc.txt"');res.set('Content-Type', 'text/plain');res.send(data);}});
});
app.get('/download/操作手册.pdf', (req, res) => {const filePath = path.join(__dirname, 'public/操作手册.pdf');fs.readFile(filePath, (err, data) => {if (err) {res.status(500).send('文件读取错误');} else {res.setHeader('Content-Disposition', 'attachment; filename="操作手册.pdf"');res.set('Content-Type', 'application/pdf');res.send(data);}});
});
app.get('/download/b.pdf', (req, res) => {const filePath = path.join(__dirname, 'public/b.pdf');fs.readFile(filePath, (err, data) => {if (err) {res.status(500).send('文件读取错误');} else {res.setHeader('Content-Disposition', 'attachment; filename="b.pdf"');res.set('Content-Type', 'application/pdf');res.send(data);}});
});
app.get('/download/a.pdf', (req, res) => {const filePath = path.join(__dirname, 'public/a.pdf');fs.readFile(filePath, (err, data) => {if (err) {res.status(500).send('文件读取错误');} else {res.setHeader('Content-Disposition', 'attachment; filename="a.pdf"');res.set('Content-Type', 'application/pdf');res.send(data);}});
});
app.get('/download/一.pdf', (req, res) => {const filePath = path.join(__dirname, 'public/一.pdf');fs.readFile(filePath, (err, data) => {if (err) {res.status(500).send('文件读取错误');} else {res.setHeader('Content-Disposition', 'attachment; filename="一.pdf"');res.set('Content-Type', 'application/pdf');res.send(data);}});
});
// 启动服务器
app.listen(PORT, () => {console.log(`服务器启动成功,访问地址为:http://localhost:${PORT}`);
});
后端是用node写的服务器,因为这些语法还是js,所以对于前端人员来说还是比较好理解的,使用的是express框架。
前端访问界面:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件下载示例</title>
</head>
<body><h1>点击按钮下载 abc.txt</h1><button id="downloadButton">下载 abc.txt</button><h1>点击按钮下载 a.pdf</h1><button id="downloadButton2">a.pdf</button><h1>点击按钮下载 操作手册.pdf</h1><button id="downloadButton3">下载操作手册.pdf</button><h1>点击按钮下载 操作手册a.pdf</h1><button id="downloadButton4">b.pdf</button><h1>点击按钮下载 操作手册一.pdf</h1><button id="downloadButton5">一.pdf</button><script>document.getElementById("downloadButton").addEventListener("click", async function() {try {const response = await fetch(`http://localhost:3001/download/abc.txt`);if (!response.ok) {throw new Error('Network response was not ok');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.style.display = 'none';a.href = url;a.download = 'abc.txt';document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('下载文件时出错:', error);}});document.getElementById("downloadButton2").addEventListener("click", async function() {try {const response = await fetch(`http://localhost:3001/download/a.pdf`);if (!response.ok) {throw new Error('Network response was not ok');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.style.display = 'none';a.href = url;a.download = 'a.pdf'; // 保持文件名和扩展名不变document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('下载a.pdf 文件时出错:', error);}});document.getElementById("downloadButton3").addEventListener("click", async function() {try {const response = await fetch(`http://localhost:3001/download/操作手册.pdf`);if (!response.ok) {throw new Error('Network response was not ok');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.style.display = 'none';a.href = url;a.download = '操作手册.pdf'; // 保持文件名和扩展名不变document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('下载操作手册.pdf 文件时出错:', error);}});document.getElementById("downloadButton4").addEventListener("click", async function() {try {const response = await fetch(`http://localhost:3001/download/b.pdf`);if (!response.ok) {throw new Error('Network response was not ok');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.style.display = 'none';a.href = url;a.download = 'b.pdf'; // 保持文件名和扩展名不变document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('b.pdf 文件时出错:', error);}});document.getElementById("downloadButton5").addEventListener("click", async function() {try {const response = await fetch(`http://localhost:3001/download/一.pdf`);if (!response.ok) {throw new Error('Network response was not ok');}const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.style.display = 'none';a.href = url;a.download = '一.pdf'; // 保持文件名和扩展名不变document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('一.pdf 文件时出错:', error);}});</script>
</body>
</html>
来看运行的效果:
当我们点击下载abc.txt按钮的时候,对应的文件就会下载
因为我下载了多个同名文件,自动重命名为abc(2).txt。
来看服务器上的文件结构:
我这里下载的就是服务器上的public文件夹中的文件,因为这样符合实际,因为我们要下载的资源肯定是要上传的远程服务器上,不可能在本地。这里我们可以再本地下载服务器上的文件。
因为一开始写的版本是针对文本文件的,所以设置的请求头和content-type会和下面的pdf文件类型的设置会不一样:
然后测试pdf文件的下载,也是可以正常下载的,因为一开始我的pdf是中文命名的,所以就会出现了这个错误【下载操作手册.pdf】:
于是就开始检查代码,发现并没有问题,一开始试着从网上找问题,发现可能是中文导致的问题,所以就试着把中文改成了英文,发现,一样的代码,改成了英文命名,就可以成功下载。
于是求助于WXYY,它告诉了我可能的原因,然后我就进行了多次尝试:
路径正确。浏览器这是Chrome,程序员公认最好的浏览器,没有之一,于是试着换Lenovo浏览器,不用说,还是一样的效果。
服务器日志那种东西更是很难看懂的,直接pass,毕竟还是没有专业到能通过看懂日志来解决问题的地步!
然后简化文件名:我也试了 ,将文件名改成一.pdf,这样就更简单了,发现还是不行
对于删除重复的路由处理函数,当我发现他这样说的时候,我就将重复代码给删了,还是一样的结果。
更新浏览器或node,这个浏览器就是最新的,可能就是小版本差异,这个影响直接忽略,对于node,突然想到因为做项目调成了老版本的node
那就开整,调成20,没必要是最新,这个版本完全足够,那就改一下版本试一试,顺便说一下,nvm是真好用,如果没了它,卸载node,再安装新版本,那就没那个必要了,这里我们直接切换node版本。
然后我们再启动node服务器,看一下,这下如果再不行,那就说明中文是真的不行!!!!!!!!!!!!!!!!!!
一毛一样,还是放弃,这个中文是真的不行,就像你看url栏中有中文吗?除了那种携带的参数在?后面传递过来的中文,在url中也会转成 url编码。
说在最后,介绍一下URL编码:
当你在浏览器中输入包含中文字符的URL时,浏览器会自动将这些中文字符进行URL编码(也称为百分号编码或百分比编码)。URL编码是一种将非ASCII字符转换为可以在URL中安全传输的格式的方法。
在你提供的例子中,“操作手册.pdf”被编码为“%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C.pdf”。这是因为在UTF-8编码中,“操”字的编码是E6 93 8D
,“作”字的编码是E4 BD 9C
,以此类推,每个中文字符都被转换成了三个百分号后跟两个十六进制数的形式。
URL编码(URL Encoding),也称为百分号编码(Percent-Encoding),是一种用于将非ASCII字符或特定字符转换为可在统一资源定位符(URL)中安全传输的格式的方法。这种编码方法使用百分号(%)后跟两位十六进制数来表示原始字符。URL编码是互联网上数据交换的一种标准方式,特别是在处理包含特殊字符的URL、表单数据或任何需要在互联网上传输的文本时。
为什么需要URL编码?
-
字符集限制:URL只能包含ASCII字符集。URL编码允许非ASCII字符(如中文字符、特殊符号等)被安全地传输。
-
保留字符:URL中包含一些具有特殊意义的字符,如
?
、=
、&
等。这些字符在URL中有特定的用途(如分隔参数、赋值等)。为了避免歧义,这些特殊字符在需要作为普通文本传输时也需要进行URL编码。
URL编码规则
-
空格字符编码为
+
号或者%20
。 -
非ASCII字符和特殊字符(如
@
、#
、$
、%
、&
、+
、,
、/
、:
、;
、=
、?
、[
、]
、"
等)转换为%
后跟两位十六进制数。 -
对于ASCII字符,通常不需要编码,但某些字符(如空格)根据上下文可能需要编码。
示例
假设我们有一个包含中文字符和特殊字符的字符串:“你好 & 世界”。在URL编码后,它变为:%E4%BD%A0%E5%A5%BD%20%26%20%E4%B8%96%E7%95%8C
。
-
“你”在UTF-8编码下是
E4 BD A0
,所以编码后为%E4%BD%A0
。 -
“好”在UTF-8编码下是
E5 A5 BD
,所以编码后为%E5%A5%BD
。 -
空格编码为
%20
。 -
“&”编码为
%26
。 -
“世”在UTF-8编码下是
E4 B8 96
,所以编码后为%E4%B8%96
。 -
“界”在UTF-8编码下是
E7 95 8C
,所以编码后为%E7%95%8C
。
在Web开发中的应用
-
URL参数:当在URL中传递参数时,参数名和参数值通常需要进行URL编码。
-
表单提交:在HTML表单中,当
enctype
属性设置为application/x-www-form-urlencoded
时,表单数据在发送到服务器之前会进行URL编码。 -
AJAX请求:在发送AJAX请求时,如果请求的数据包含特殊字符,也需要进行URL编码。
URL编码是Web开发中不可或缺的一部分,它确保了数据在不同系统之间的安全、可靠传输。