前端学习笔记之文件下载(1.0)
因为要用到这样一个场景,需要下载系统的使用教程,所以在前端项目中就提供了一个能够下载系统教程的一个按钮,供使用者进行下载。
所以就试着写一下这个功能,以一个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开发中不可或缺的一部分,它确保了数据在不同系统之间的安全、可靠传输。
相关文章:

前端学习笔记之文件下载(1.0)
因为要用到这样一个场景,需要下载系统的使用教程,所以在前端项目中就提供了一个能够下载系统教程的一个按钮,供使用者进行下载。 所以就试着写一下这个功能,以一个demo的形式进行演示,在学习的过程中也发现了中文路径…...

从技术视角看AI在Facebook全球化中的作用
在全球化日益加深的今天,人工智能(AI)作为一种变革性技术,正在深刻影响全球互联网巨头的发展方向。Facebook作为全球最大的社交媒体平台之一,正通过AI技术突破语言、文化和技术的障碍,推动全球化战略的实现…...

Web 表单开发全解析:从基础到高级掌握 HTML 表单设计
文章目录 前言一、什么是 Web 表单?二、表单元素详解总结前言 在现代 Web 开发中,表单 是用户与后端服务交互的重要桥梁。无论是用户登录、注册、搜索,还是提交反馈,表单都无处不在。在本文中,我们将从基础入手,全面解析表单的核心知识点,并通过示例带你轻松掌握表单开…...

Milvus 2.5:全文检索上线,标量过滤提速,易用性再突破!
01. 概览 我们很高兴为大家带来 Milvus 2.5 最新版本的介绍。 在 Milvus 2.5 里,最重要的一个更新是我们带来了“全新”的全文检索能力,之所以说“全新”主要是基于以下两点: 第一,对于全文检索基于的 BM25 算法,我们采…...

【webrtc】 mediasoup中m77的IntervalBudget及其在AlrDetector的应用
IntervalBudget 用于带宽控制和流量整形 mediasoup中m77 代码的IntervalBudget ,版本比较老IntervalBudget 在特定时间间隔内的比特预算管理,从而实现带宽控制和流量整形。 一。 pacedsender 执行周期: 下一次执行的时间的动态可变的 int64_t PacedSender::TimeUntilNextPr…...

AI数据分析工具(二)
豆包-免费 优点 强大的数据处理能力: 豆包能够与Excel无缝集成,支持多种数据类型的导入,包括文本、数字、日期等,使得数据整理和分析变得更加便捷。豆包提供了丰富的数据处理功能,如数据去重、填充缺失值、转换格式等…...

小米路由mini刷PDCN教程补充
花了10天帮助一个网友解决小米路由刷PDCN做打印服务器失败的过程,经历颇多。特别把中间的一些坑写出来,希望大家不要遇到。 首先网上好多教程写的都不错,很适合小白。推荐如下: 刷breed和PDCN方法: 小米路由器mini刷…...

[巅峰极客 2021]签到
[巅峰极客 2021]签到 给了我们好多表情,真的是一脸懵逼 注意给我们的关键词 GAME 现在还不知道是什么意思我们去试着解开一下 用这个emoji表情解密器,这里我找了好久才找到一个 emoji-aes 这里的Key值就是GAME 运行后出现flag NSSCTF{10ve_4nd_Peace…...

详解SpringCloud集成Camunda7.19实现工作流审批(二)
本章将分享的是camunda流程设计器--Camunda Modeler的基本使用(对应camunda版本是7.19),包括bpmn流程图画法,各种控件使用以及一些日常业务场景的流程图的实现 参考资料: Camunda BPMN 基础组件-CSDN博客 Camunda: Exe…...
Matlab学习笔记
Magic Traits 文件读取 fid fopen(fn,rt);out fscanf(fid,spec,inf);fclose(fid);2. 读取数据 fid fopen(fn,rt); out textscan(fid,spec);运算篇 fprintf(" xxx %d",a),当a为数组时,会输出数组数目行,每行是一个元素相关文…...
Hexo博客在多个设备同步
title: ‘Hexo博客在多个设备同步’ date: 2024-11-28 19:08:08 categories: Hexo教程 cover: /img/cover4.jpg description: ‘实现Hexo博客在不同的设备上都可以使用和上传’ 博客链接1 :Hexo搭建博客的多终端同步问题 博客链接2:Hexo博客多台电脑设备同步管理 …...

淘宝Vision Pro:革新购物体验的沉浸式未来
引言 简要介绍淘宝Vision Pro版的背景,包括它在美区AppStore的发布及WWDC上的展示。阐述本文的目的:为读者提供一个全面的功能概览与设计背后的思考。设计原则 列出并简要解释5条设计原则(熟悉、直观、真实、实用、易用)。说明这些原则如何指导整个产品设计过程。核心功能详…...

公链开发中的技术实现路径:构建高效、安全的去中心化网络
区块链技术作为数字经济的重要组成部分,公链(Public Chain)是其核心架构之一。公链作为去中心化的数字账本,不仅承载着去中心化应用(DApp)的运行,还确保了交易的透明、安全性。随着区块链技术的…...

mac上的建议xftp 工具
mac上的建议xftp 工具 最近使用mac比较频繁了,但是第一次重度使用mac里面有很多的工具都是新的,有的window版本的工具无法使用。 xftp 的平替 Cyberduck 从它的官网上下载是免费的,但是如果使用 Apple store 要花费198呢。这不就剩下一大笔…...
Android 使用Charles抓包显示Unknown
最近开发的一个功能需要抓包验证一下网络请求的结果。 但是在配置完Charles的证书代理等设置后,抓包时显示Unknown。 在网上查了半天资料和文章,最终解决了问题。 以下是在调试抓包环境中遇到的一些问题和解决方法。 1、手机证书的安装 Charles在Mac…...
C++设计模式:桥接模式(Bridge)
什么是桥接模式? 桥接模式(Bridge Pattern)是一个用来解耦的设计模式,它将抽象层和实现层分离开,让它们可以独立变化。用最简单的话来说,就是让你能够改变抽象的功能和具体的实现,而不需要修改…...
spark3.x之后时间格式数据偶发报错org.apache.spark.SparkUpgradeException
3.x之后如果你去处理2.x生成的时间字符串数据,很容易遇到一个问题 Error operating ExecuteStatement: org.apache.spark.SparkUpgradeException: You may get a different result due to the upgrading of Spark 3.0: Fail to parse 20200725__cb90fcc3_8006_46…...

spring boot框架漏洞复现
spring - java开源框架有五种 Spring MVC、SpringBoot、SpringFramework、SpringSecurity、SpringCloud spring boot版本 版本1: 直接就在根下 / 版本2:根下的必须目录 /actuator/ 端口:9093 spring boot搭建 1:直接下载源码打包 2:运行编译好的jar包:actuator-testb…...

下载安装Android Studio
(一)Android Studio下载地址 https://developer.android.google.cn/studio 滑动到 点击下载文档 打开新网页 切换到english 
三、计算机视觉_08YOLO目标检测
0、前言 YOLO作为目前CV领域的扛把子,分类、检测等任务样样精通,本文将基于两个小案例,用YOLO做检测任务,看看效果如何 1、对图片内容做检测 假设我有一张名为picture.jpeg的图片,其内容如下 我将图片和代码放到了同…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...

DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...

Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...
[特殊字符] Spring Boot底层原理深度解析与高级面试题精析
一、Spring Boot底层原理详解 Spring Boot的核心设计哲学是约定优于配置和自动装配,通过简化传统Spring应用的初始化和配置流程,显著提升开发效率。其底层原理可拆解为以下核心机制: 自动装配(Auto-Configuration) 核…...
【系统架构设计师-2025上半年真题】综合知识-参考答案及部分详解(回忆版)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7题】【第8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20~21题】【第…...

Web APIS Day01
1.声明变量const优先 那为什么一开始前面就不能用const呢,接下来看几个例子: 下面这张为什么可以用const呢?因为复杂数据的引用地址没变,数组还是数组,只是添加了个元素,本质没变,所以可以用con…...