前端学习笔记之文件下载(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的图片,其内容如下 我将图片和代码放到了同…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...