前端 之 FormData对象进阶

news2024/11/18 12:20:40

一、进阶知识

在前一篇博客中,我讲解了FormData对象的基础概念、相关方法和基本用法,本篇博客我将讲解一些FormDate对象相关的进阶知识,主要包含FormData与其他对象结合使用的各类场景,以及一些使用技巧。

1、FormData对象、JSON字符串、key=value字符串 三种参数形式对比

① 使用FormData对象传递参数

​ 该参数形式对应请求头Content-type类型中的 multipart/form-data,以键值对的形式存储参数数据,参数中允许包含FileBlob类型的数据。

// 发送FormData对象参数
function ajaxFormData () {
   // 创建空的 FormData 对象
   const formData = new FormData()
   // 创建Blob对象
   var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组
   var blob = new Blob(aFileParts, { type: 'text/html' });
   // 添加一个Blob键值对数据  并设置文件名称
   formData.append('content', blob)
   // 添加一个字符串键值对数据
   formData.append('name', '张三')
   // 添加一个字符串键值对数据
   formData.append('age', '18')
   // 创建 XMLHttpRequest 实例对象
   const xhr = new XMLHttpRequest();
   // 设置发送POST请求的URL地址
   const url = 'http://example.com/api/user';
   // 配置请求对象
   xhr.open('POST', url);
   // 无需设置请求头信息 浏览器会自动设置 Content-type 为 multipart/form-data
   // 设置请求完成后的回调
   xhr.onreadystatechange = function () {
     if (xhr.readyState === 4 && xhr.status === 200) {
       console.log(xhr.responseText);
     }
   };
   // 发送请求并将参数加入进去
   xhr.send(formData);
}
浏览器查看请求头和请求参数:

在这里插入图片描述

② 使用JSON字符串传递参数

​ 该参数形式对应请求头Content-type类型中的 application/json,参数中的FileBlob类型的数据会被转换成{},数据会丢失。当然我们也可以通过将FileBlob对象转成base64格式的方式来传递数据,但是不够优雅,而且在转换格式的时候,如果文件过大,会占用大量内存,影响浏览器性能,因此并不推荐采用这种形式来传递FileBlob类型数据。

function ajaxJSON () {
   // 创建Blob对象
   var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组
   var blob = new Blob(aFileParts, { type: 'text/html' });
   // 创建一个 FileReader 对象
   var reader = new FileReader();
   // 当以 DataURL 格式读取成功后,执行回调函数
   reader.onload = (event) => {
     // 将blob对象转换为bas64字符串
     var blobBase64 = event.target.result
     // 创建要发送的参数对象
     var params = {
       name: '张三',
       age: 18,
       content: blob,
       contentBase64: blobBase64
     }
     // 将参数对象转换为JSON字符串
     var JSONParams = JSON.stringify(params)
     // 创建 XMLHttpRequest 实例对象
     const xhr = new XMLHttpRequest();
     // 设置发送POST请求的URL地址
     const url = 'http://example.com/api/user';
     // 配置请求对象
     xhr.open('POST', url);
     // 设置请求头信息
     xhr.setRequestHeader('Content-type', 'application/json')
     // 设置请求完成后的回调
     xhr.onreadystatechange = function () {
       if (xhr.readyState === 4 && xhr.status === 200) {
         console.log(xhr.responseText);
       }
     };
     // 发送请求并将参数加入进去
     xhr.send(JSONParams);
   };
   // 以 DataURL 的形式读取 Blob 数据
   reader.readAsDataURL(blob);
}
浏览器查看请求头和请求参数:

在这里插入图片描述

③ 使用key=value字符串传递参数

​ 该参数形式对应请求头Content-type类型中的 application/x-www-form-urlencoded,传递过程中只能传递字符串类型的参数,参数组成key=value格式字符串,多个参数之间通过&进行连接。参数中的FileBlob类型的数据会被转换成[object File][object Blob]字符串,数据会丢失。同理,我们也可以通过将FileBlob对象转成base64格式的方式来传递数据,但缺点也相同,在转换格式的时候,如果文件过大,会占用大量内存,影响浏览器性能,因此并不推荐采用这种形式来传递FileBlob类型数据。

function ajaxString () {
   // 创建Blob对象
   var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含 DOMString 的数组
   var blob = new Blob(aFileParts, { type: 'text/html' });
   // 创建一个 FileReader 对象
   var reader = new FileReader();
   // 当以 DataURL 格式读取成功后,执行回调函数
   reader.onload = (event) => {
     // 将blob对象转换为bas64字符串
     var blobBase64 = event.target.result
     // 创建要发送的参数字符串
     var params = 'name=张三&age=18&content=' + blob + '&contentBase64=' + blobBase64
     // 创建 XMLHttpRequest 实例对象
     const xhr = new XMLHttpRequest();
     // 设置发送POST请求的URL地址
     const url = 'http://example.com/api/user';
     // 配置请求对象
     xhr.open('POST', url);
     // 设置请求头信息
     xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
     // 设置请求完成后的回调
     xhr.onreadystatechange = function () {
       if (xhr.readyState === 4 && xhr.status === 200) {
         console.log(xhr.responseText);
       }
     };
     // 发送请求并将参数加入进去
     xhr.send(params);
   }
   // 以 DataURL 的形式读取 Blob 数据
   reader.readAsDataURL(blob);
}
浏览器查看请求头和请求参数:

在这里插入图片描述

2、对FormData对象中的数据进行过滤

​ 我们使用FormData对象向服务端传输数据时,通常是为了进行文件上传,以及一些相关数据的上传。为了数据安全,我们需要对要加入到FormData中的数据进行校验过滤,比如对文件名、文件类型、文件内容等等进行过滤,只有过滤后的数据才能加入到FormData中,并发送到服务端。

① 文件名过滤

​ 文件名过滤可以防止用户上传的文件名中包含违规内容和字符等,具体实现可以结合正则表达式和字符串操作两者来实现。

​ 例如:上传文件的文件名不能包含&#%三个特殊字符,且文件名不能包含sb2b两个违规词。

// 声明FormData
const formData = new FormData();

// 省略...

// 获取文件对象
var file = e.target.files[0]
// 获取文件名
var fileName = file.name
// 校验文件名中是否包含sb或2b 两个违规词
const pattern = /^(?!.*([sS]b|2[Bb])).*$/i;
// 进行文件名过滤 不能含有违规词 且不能含有特殊字符
if (pattern.test(fileName) && fileName.indexOf('&') === -1 && fileName.indexOf('#') === -1 && fileName.indexOf('%') === -1) {
  formData.append('file',file)
} else {
  alert('文件名不符合规范,请修改后再上传~');
}
② 文件类型过滤

​ 文件类型过滤可以防止用户上传不支持的文件类型,虽然前端可以通过<input>标签的accept属性来限制用户选择的文件类型,但是这并不严谨,用户可以通过操作文件选择框的选项来解除限制,所以在文件上传之前对文件类型进行过滤是有必要的。

​ 例如:上传文件的类型限制为图片类型,且只能为.jpg.png.gif三种类型的文件。

// 创建空的 FormData 对象
const formData = new FormData()

// 省略...

// 获取文件对象
var file = e.target.files[0]
// 获取文件名
var fileName = file.name
// 获取文件类型
var fileType = file.type
// 校验文件名是否以规定格式 jpg、png、gif 结尾
const pattern = /\.jpg$|\.png$|\.gif$/i;
// 进行文件类型过滤
if (pattern.test(fileName) && fileType.indexOf('image') === 0) {
	formData.append('file', file)
} else {
	alert('文件类型不符,请修改后再上传~');
}
③ 文件内容过滤

​ 文件内容过滤可以防止用户上传包含恶意代码和违规内容的文件,可以使用JS来过滤部分文件的内容,也可以借助一些完善第三方的库来检查文件内容,如:js-xssFilter.js等等。

​ 例如:对用户上传的.txt文件,进行简单的敏感词汇校验过滤。

// 创建空的 FormData 对象
const formData = new FormData()
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {
     // 获取文件对象
     var file = e.target.files[0]
     // 声明一个 FileReader 对象
     const reader = new FileReader();
     // 当以文本形式读取成功后,执行回调函数
     reader.onload = (event) => {
       const content = event.target.result;
       console.log('原文件内容---', content);
       // 过滤敏感词汇
       const filteredContent = content.replace(/sb|智障|2B/gi, '**');
       // 显示过滤后的内容
       console.log('过滤后的文件内容---', filteredContent);
       // 将过滤后的内容写入FormData
       formData.append('fileText', filteredContent)
     };
     // 以文本形式读取文件内容
     reader.readAsText(file);
 }
④ 白名单过滤

​ 白名单过滤是指根据过滤条件批量设置允许名单,只有符合白名单的数据才能通过过滤。

​ 例如:设置文件类型白名单,只允许jpgpnggif类型的图片文件加入到FormData中。

// 创建空的 FormData 对象
var formData = new FormData()
// 声明一个白名单数组
const whitelist = ['image/jpg', 'image/png', 'image/gif'];
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {
	// 获取文件对象
	var file = e.target.files[0]
	// 获取文件类型
	var fileType = file.type
	// 判断文件类型是否在白名单中
	if (whitelist.indexOf(fileType) > -1) {
		formData.append('file', file)
	} else {
		alert('文件类型不符,请修改后再上传~');
	}
}
⑤ 黑名单过滤

​ 黑名单过滤是指根据过滤条件批量设置禁止名单,凡是符合黑名单的数据都禁止通过。

​ 例如:设置文件类型黑名单,禁止.exe.bat类型的文件加入到FormData中。

// 创建空的 FormData 对象
var formData = new FormData()
// 声明一个黑名单数组
const blackList = ['application/x-msdownload', 'application/x-msdos-program',];
// 调起文件选择框
document.getElementById('file').click()
// 监听文件选择框的change事件
document.getElementById('file').onchange = function (e) {
	// 获取文件对象
	var file = e.target.files[0]
	// 获取文件类型
	var fileType = file.type
	// 判断文件类型是否在黑名单中
	if (blackList.indexOf(fileType) === -1) {
		formData.append('file', file)
	} else {
		alert('文件类型不允许上传~');
	}
}

3、FormData对象结合同步token预防CSRF攻击

​ CSRF(Cross-site Request Forgery,跨站请求伪造)攻击是一种常见的网络攻击,攻击者通过伪造用户的身份,利用用户在某些站点上的登录状态,来构造并发送篡改数据的请求。防范CSRF攻击方式有很多,在涉及表单提交的页面中,我们常用的是FormData对象结合同步token(又称CSRF token)的防范策略,来防范攻击者恶意伪造表单数据提交,具体操作步骤如下:

​ ① 当用户请求访问表单页面时,服务端生成一个随机且唯一的token,服务端存储一份,并将该token存储在cookie之中,发送给前端。

​ ② 前端从cookie中获取token,然后将token添加到要提交的FormData对象中。

​ ③ 前端触发表单提交接口,发送FormData对象,服务端收到请求后,对比FormData对象中的token与服务端存储的token是否一致,如果一致,则认为是合法请求,否则,认为是CSRF攻击,拒绝请求。

​ 该防范策略的核心在于攻击者虽然在调用提交接口时能携带相关的cookie信息(接口携带的cookie取决于接口的域名),但是无法通过js获取相关cookie的值(js只能获取当前页面域名下的cookie),因而也就拿不到有效的token,无法构造有效的表单数据,请求就会被服务端所拒绝。

​ 该防范策略的优点在于安全性高、操作简单、支持性好,缺点在于需要增加额外的计算量和存储开销。

​ 除此之外,我们还可以给存储token的那个cookie设置SameSite=Strict或lax,进一步防范CSRF攻击。

4、FormData对象结合input实现选择文件夹,批量上传文件

​ 之前我们批量上传文件时,都是让用户一个个的去选择文件,操作繁多;或者就是让用户将文件放到文件夹下,统一打包成压缩包,作为一个文件上传,但是文件的压缩格式有很多,服务端基本不可能全部支持,因此也有一定的局限性。所以我想到了另一种方案就是:让用户直接去选择文件夹,然后前端获取文件夹中的所有文件,逐一加入到FormData对象中,最后统一上传到服务端。我们还可以结合黑白名单过滤的方式,对文件夹中的文件进行过滤,只保留允许上传的文件,发送到服务端。

​ 想要通过<input>实现文件夹上传需要借助该元素的webkitdirectory属性,设置该属性后,将限制用户只能选择文件夹,而无法选择文件。但是该属性并非标准属性,所以请慎用!!!

浏览器兼容性:

在这里插入图片描述

示例代码:
<input type="file" id="folder" name="folder" webkitdirectory />
<div id="showBox">
    文件夹内文件展示区域
</div>
// 创建空的 FormData 对象
var formData = new FormData()
// 声明一个白名单数组 表示可以上传的文件后缀名
const whiteList = ['ppt', 'pptx', 'txt', 'xlsx'];
// 调起文件选择框
document.getElementById('folder').click()
// 监听文件选择框的change事件
document.getElementById('folder').onchange = function (e) {
	// 获取文件列表类数组对象
	let files = e.target.files
	// 输出文件列表类数组对象
	console.log(files);
  // 将类数组对象转换为数组 且对文件后缀名进行过滤
	files = Array.from(files).filter(item => {
		// 过滤掉文件夹对象
     if (item.type !== "" && item.name !== '.DS_Store') {
        // 获取文件后缀名
        const suffix = item.name.split('.').pop()
        // 过滤掉不足在白名单中的文件
        if (whiteList.indexOf(suffix) > -1) {
            return true
        }
     }
	})
	// 输出过滤后的文件对象列表
	console.log('选择文件夹中的所有文件过滤后的结果---', files);
	// 用于显示的html字符串
	let html = ''
	// 遍历文件对象列表
	files.forEach(item => {
		// 将文件对象的信息拼接到html字符串中
		html = html + `<p>文件名:${item.name} <br />文件路径:${item.webkitRelativePath}</p>`
		// 将文件对象添加到FormData中
		formData.append('file', item)
	})
	// 将html字符串渲染到页面中
	document.getElementById('showBox').innerHTML = html
	// 后续上传文件的逻辑
  ...

选择文件夹上传后,首先浏览器会弹窗获取用户授权(Safari浏览器在本地环境时无需授权,线上环境未验证)

在这里插入图片描述

​ 用户授权之后,我们可以监听<input>标签的onchange事件,然后通过event.target.files获取所选文件夹本身及其的所有子文件和子文件夹组成的文件类数组,在进行相关处理时,建议使用Array.from()转换真正的数组类型。

原始目录层级:

在这里插入图片描述

获取的文件类数组以及过滤后的文件结果:

在这里插入图片描述

页面渲染结果:

在这里插入图片描述

​ 从上面的示例中可以看出获取文件列表中,包含一种name.DS_Store并且type""的特殊文件,这类特殊文件文件表示的就是文件夹,我们可以通过该文件的webkitdirectory属性来获取文件夹的真实名称。

​ 而且此时获取的各文件之间无法体现原始目录层级关系,但是我们可以通过每个file文件的webkitRelativePath属性来得知每个文件的层级关系,各级路径之间通过 / 连接,我们可以通过/ 分割webkitRelativePath属性值,从而还原文件夹的原始层级关系。

注意: 文件名和文件夹名最好不要包含/\等特殊字符,因为获取的File中的namewebkitRelativePath属性,会将他们转义,很有可能会影响层级的拆分和判断。例如:/File中的namewebkitRelativePath中都会被转义为:\File中的name中会被转义为\\,在webkitRelativePath中会被转义为/?(奇奇怪怪的规则(╯°□°)╯︵┻━┻)。

5、FormData对象结合dataTransfer实现拖拽文件夹,批量上传文件

可以实现,但其中涉及知识点太多,暂时还没完全搞懂,想了解的建议查阅最后一篇相关资料。

二、相关资料

前端FileReader对象

CSRF攻击及常用防范手段

Cookie相关

webkitdirectory

文件夹上传相关

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在一个不小但很美的公司里工作

在这个公司里学到了什么 电商交易前业务&#xff1a;商品&#xff0c;库存&#xff0c;物流&#xff0c;会员 &#xff1b; 电商广告部分业务&#xff1a; 网红&#xff0c;联盟&#xff1b; 并且对这些业务里的核心流程&#xff0c;核心技术 有过总结。核心技术问题采用对应…

HTTP.sys远程代码执行

本文转载与&#xff1a;https://blog.csdn.net/weixin_47723270/article/details/129472716 01 漏洞描述 HTTP.sys是Microsoft Windows处理HTTP请求的内核驱动程序&#xff0c;为了优化IIS服务器性能&#xff0c;从IIS6.0引入&#xff0c;IIS服务进程依赖HTTP.sys。HTTP.sys远程…

keycloak异常关闭报错username ‘admin‘ already added时卡死无法重启的问题处理

问题现象 使用docker部署keycloak服务&#xff0c;使用docker-compose进行配置管理&#xff0c;配置如下&#xff1a; keycloak:image: jboss/keycloak:16.1.0 container_name: keycloakcommand:[-b,0.0.0.0,-Dkeycloak.migration.actionimport,-Dkeycloak.migration.provider…

UE5中如何新建C++类?

UE5 插件开发指南 前言0.如何在UE编辑器内创建C++类?1.如何在UE编辑器外创建C++类?前言 这个问题应该细分成两个问题: (1)如何在编辑器内创建C++类? (2)如何在编辑器外创建C++类? 问题(1)主要针对那些可以在编辑器内继承并创建的类,然而有些内是无法在编辑内继承的,必须在…

overflow属性的常用值详解

什么是overflow 在CSS中&#xff0c;overflow是“溢出”的意思&#xff0c;该属性规定当内容溢出元素框时发生的事情&#xff0c;设置内容是否会被修剪&#xff0c;溢出部分是否会被隐藏&#xff1b;例如当属性值设置为“visible”则内容不会被修剪&#xff0c;为“hidden”则内…

软件测试用例包括_白盒测试用例

测试用例分层 每个测试用例都有1个或多个测试步骤&#xff08;List[step]&#xff09;&#xff0c;每个测试步骤对应一个API请求或其他用例的引用。 从上图分析&#xff0c;我们可以看到testsuite中包含了3个测试用例&#xff0c;testcase1中有4个请求和一个步骤teststep12&am…

LeetCode:25. K 个一组翻转链表

25. K 个一组翻转链表 1&#xff09;题目2&#xff09;思路3&#xff09;代码4&#xff09;结果 1&#xff09;题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。…

研发工程师玩转Kubernetes——通过文件创建Pod

在《研发工程师玩转Kubernetes——部署应用》中&#xff0c;我们使用kubectl run命令启动了一个可以在kubernetes集群内部访问的nginx——它不可以通过物理机访问。而我们使用文件创建时&#xff0c;则可以通过设置相关参数&#xff0c;让nginx可以通过物理机地址访问。 创建P…

【社工】NodeJS 应用仓库钓鱼

前言 城堡总是从内部攻破的。再强大的系统&#xff0c;也得通过人来控制。如果将入侵直接从人这个环节发起&#xff0c;那么再坚固的防线&#xff0c;也都成为摆设。 下面分享一个例子&#xff0c;利用应用仓库&#xff0c;渗透到开发人员的系统中。 应用仓库 应用仓库对于开…

手写决策树算法——基于决策树的XX联盟游戏胜负预测

机器学习 基于决策树的 XX联盟游戏胜负预测 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/ar…

用Powerpoint (PPT)制作并导出矢量图、高分辨率图

论文写作时经常需要导入矢量图&#xff0c;正规军都是用AI或者Inkscape&#xff0c;但是PPT更加适合小白用户&#xff0c;或者一些简单的构图需求&#xff0c;而且不得不承认PPT的某些功能是真的香&#xff0c;例如&#xff1a;简单的对齐、文字插入和格式修改等等。但是PPT是不…

Ansys仿真TDR

1、TDR测量的原理&#xff1a; 2、时域反射计TDR的电路仿真 打开Ansys的TDR电路例程&#xff0c;单端电路和TDR源的设置如下图所示&#xff0c;被测的DUT为4段传输线和一个电容&#xff0c;一个电感。 TDR测的结果如下&#xff1a; 信号到负载电容的时间为1.5ns&#xff0c;然…

Cesium源码分享--气泡窗

Cesium气泡窗插件 在线api文档说明 在线体验地址 更多案例地址 免费gis数据 ps&#xff1a;如果可以的话&#xff0c;希望大家能给我个star&#xff0c;好让我有更新下去的动力&#xff1b; 实现原理&#xff1a; Cesium和我们平时常见的leaflet、ol以及arcgis api是不一样…

组合数学公式

1、8个相同的球放进4个相同的盒子里&#xff0c;每盒至少一个&#xff0c;有几种方法 &#xff1f; 公式&#xff1a;球相同&#xff0c;盒相同&#xff0c;拆分公式。 P4(8)P1(4)P2(4)P3(4)P4(4) 1211 5 2、8个相同的球放进4个不同的盒子里&#xff0c;每盒至少一个&#xff…

GPT学习笔记-聚类(clustering)

1. 什么是clustering 聚类是一种非常有用的无监督学习技术&#xff0c;它的主要目的是发现数据的内在结构和模式。在许多实际应用中&#xff0c;我们可能没有明确的目标变量或预测目标&#xff0c;但我们仍希望了解数据的组织方式&#xff0c;或者找出数据中的特定模式或组。这…

如何构造HTTP请求

直接通过浏览器地址栏 具体的流程,直接通过浏览器地址栏,输入一个url >构造出一个GET请求 html中,一些特殊标签,也会触发GET请求 1) link 2) script 3) img 4) a以上的几个标签都会触发Get请求了. form表单,可以触发GET和POST请求 form 的重要参数: action: 构造的 HTT…

【Redis面试点总结】

1、缓存 1.1、穿透 查询一个空数据&#xff0c;mysql也查不到也不会写入缓存可能导致多次请求数据库 方案一&#xff1a;缓存设空即可&#xff08;可能发生数据不一致就是这条数据有了但此时缓存是空&#xff0c;消耗内存&#xff09; 方案二&#xff1a;布隆过滤器&#x…

hive数据库hql基础操作02

1.内部表和外部表 默认情况下创建的表就是内部表&#xff0c;Hive拥有该表的结构和文件。换句话说&#xff0c;Hive完全管理表&#xff08;元数据和数据&#xff09;的生命周期&#xff0c;类似于RDBMS中的表。当你删除内部表时&#xff0c;它会删除数据以及表的元数据。可以使…

【python】价值25~30K的国外企业招聘面试考题

目录标题 前言案例介绍网站分析代码展示尾语 &#x1f49d; 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 今天的这个案例&#xff0c;是一位同学的面试题&#xff0c;人在国外&#xff0c;月薪25~30K 本来以为是难度很大的反pa、逆向或者算法之类的&#xff0c; 谁知道…

深度学习笔记1——CNN识别黑白手写数字

文章目录 摘要手写数字数据集&#xff08;MNIST&#xff09;卷积神经网络&#xff08;Convolution Neural Network, CNN&#xff09;模型架构搭建Softmax函数和CrossEntropy损失函数Adam 优化器 构造数据迭代器训练、验证、测试模型训练结果可视化 摘要 本文将介绍CNN的开山之…