如何实现一个FormData
一、前言
最近项目中遇到一个问题,我们需要在cocos项目里去上传音频文件,而cocos原生环境和平时我们开发所在的浏览器环境和Node环境有很多差异,而cocos环境只提供了基础类,没有提供FormData这种封装类。
所以问题来了?如何实现一个FormData,以及怎么去使用它?
二、浏览器中的FormData
这里我列一个最简单的例子,我们来看看FormData到底是什么。
function App() {
const [name, setName] = useState('')
const [age, setAge] = useState(0)
const [file, setFile] = useState<File | null>()
const submit = () => {
console.log(name, age);
console.log(file);
var fd = new FormData()
fd.append('name', name)
fd.append('age', age.toString())
fd.append('file', file as Blob)
$.ajax({
type: "POST",
url: "www.happy.com",
data: fd,
processData: false,//重要
contentType: 'multipart/form-data',//重要
success: function (data: any) {
}
})
}
return (
<div className="App">
<form action="form_action.asp" method="get">
<p>name: <input type="text" name="name" value={name} onChange={e => setName(e.currentTarget.value)} /></p>
<p>age: <input type="number" name="age" value={age} onChange={e => setAge(Number(e.currentTarget.value))} /></p>
<p>file:<input type="file" name="file" onChange={e => setFile(e.target.files && e.target.files[0])}/>
</p>
<input type="button" name="b1" value="submit" onClick={() => submit()} />
</form>
</div>
);
}
FormData:FormData 接口提供了一种表示表单数据的键值对 key-value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。(摘自MDN)
大多数文章里,只给了这样的一种描述或者说是概念,它是一个接口类,用来做上传用,我们来看它在数据形式上体现的是什么。
下面是chrome devtool request payload里的样子。
------WebKitFormBoundaryuhGsgTdqAAltAXy7 // 分隔/边界符
Content-Disposition: form-data; name="name" // 内联形式
hackftz // value
------WebKitFormBoundaryuhGsgTdqAAltAXy7
Content-Disposition: form-data; name="age"
22
------WebKitFormBoundaryuhGsgTdqAAltAXy7
Content-Disposition: form-data; name="file"; filename="Minstrel - eyecatch!.mp3"
Content-Type: audio/mpeg
------WebKitFormBoundaryuhGsgTdqAAltAXy7-- // 这里是end_boundary,结尾分隔/边界符,必需!
三、我在实现FormData时遇到了哪些坑?
先贴代码,然后说说我遇到了哪些坑。
export default class MyFormData {
// 将随机数传入构造函数
constructor(stamp) {
this._boundary_key = stamp; // 随机数,分隔符和结尾分隔符必需。
this._boundary = '--' + this._boundary_key;
this._end_boundary = this._boundary + '--';
this._result = [];
}
// 上传普通键值对——字符串、数字
append(key, value) {
this._result.push(this._boundary + '\r\n');
this._result.push('Content-Disposition: form-data; name="' + key + '"' + '\r\n\r\n');
this._result.push(value);
this._result.push('\r\n');
}
// 上传复杂数据——文件
appendFile(name, data, ext){
let type = "audio/mpeg";
let filename = "upload."+ext;
this._result.push(`${this._boundary}\r\n`);
this._result.push(`Content-Disposition: form-data; name="${name}"; filename="${filename}"\r\n`); // 上传文件定义
this._result.push(`Content-Type: ${type}\r\n`);
this._result.push("\r\n");
this._result.push(data);
this._result.push("\r\n");
}
// 获取二进制数据 get
get arrayBuffer() {
this._result.push('\r\n' + this._end_boundary); // 结尾分隔符
let charArr = [];
// 处理charCode
for (let i = 0; i < this._result.length; i++) { // 取出文本的charCode(10进制
let item = this._result[i];
if( typeof(item) === 'string'){
for (let s = 0; s < item.length; s++){
charArr.push(item.charCodeAt(s));
}
} else if(typeof(item) === 'number') {
let numstr = item.toString()
for (let s = 0; s < numstr.length; s++){
charArr.push(numstr.charCodeAt(s));
}
} else{
for (let j = 0; j < item.length; j++){
charArr.push(item[j]);
}
}
}
let array = new Uint8Array(charArr);
return array.buffer;
}
}
踩坑记录:
- 首先,我需要定义一个boundary_key,它当前环境提供给FormData的随机数,chrome v87.0.4270.0提供给FormData的是"WebKitFormBoundary" + "xxxxxxxxxxxxxx" 随机数。我在项目里使用的是timestamp,这里只要提供一个随机数即可。
- appendFile方法的实现,要根据具体上传类型,文件类型,作content-type定义,比如我这里上传的是音频文件,所以设置的是"audio/mpeg"。
- 普通键值对和复杂键值对的区分,如果value是字符串,直接分解成字符再处理;如果是number,这里有个坑,那就是直接添加到FormData会失败,所以需要先把number值转为string,再像处理string值一样处理。
- 再看arrayBuffer实现方法,我们可以得知FormData最终要给api data的值是一个由具体blob值,分解为单个字符,存储到一个字符数组中,再创建一个参数为字符数组的新的Uint8Array数组,最终可以将这样一个arrayBuffer数据(通用的、固定长度的原始二进制数据缓冲区。)提供给服务器去解析。
以上是封装FormData中我遇到的问题,再来看怎么去使用这样一个我们自定义的FormData。
四、MyFormData的使用
话不多说,先贴代码,再谈问题:
const stamp = Date.now() // 生成随机数,这里使用了时间戳
const fd = new MyFormData(stamp)
for (const key in data) {
if (data.hasOwnProperty(key)) {
fd.append(key, data[key])
}
}
fd.appendFile('file', blob, data.fileExtName); // 添加要上传的文件,这里记得第三个参数要传入文件后缀名。
const config = {
headers: {
'Content-Type': `multipart/form-data; boundary=${stamp}` // 分隔符
},
};
axios({
url,
data: fd.arrayBuffer,
method: 'POST',
headers
})
.then(response => {
if (response.status === 200) {
const { data } = response;
console.log("fun -> JSON.stringify(data)", JSON.stringify(data))
}
})
.catch(err => {
console.log(err);
});
踩坑记录:
- 因为我们上传的是二进制流数据,appendFile函数添加后缀名,在formdata数据里定义好Content-Disposition里的文件名,方便和后端开发人员识别是什么样的文件,是有必要的。
- request headers里注意
multipart/form-data; boundary=${stamp}这里一定要把随机数写到boundary=后面,否则后端服务会报错'no multipart boundary was found'
五、如果觉得有帮助的话,还请给个赞,谢谢!
如何实现一个FormData的更多相关文章
- 前端传递数据到后台的两种方式;创建一个map或者创建一个FormData对象
一.构建一个map getAllDeptAllUsers(){ const modleCode = {'auditMenuId': this.auditMenuId, 'enterpriseId': ...
- 三种上传文件不刷新页面的方法讨论:iframe/FormData/FileReader
发请求有两种方式,一种是用ajax,另一种是用form提交,默认的form提交如果不做处理的话,会使页面重定向.以一个简单的demo做说明: html如下所示,请求的路径action为"up ...
- 通过Ajax使用FormData对象无刷新上传文件
写在前面:本文说的这个方案有浏览器兼容性问题:所有主流浏览器的较新版本已经支持这个对象了,比如Chrome 7+.Firefox 4+.IE 10+.Opera 12+.Safari 5+,对兼容性比 ...
- 利用Jquery使用HTML5的FormData属性实现对文件的上传
1.利用Jquery使用HTML5的FormData属性实现对文件的上传 在HTML5以前我们如果需要实现文件上传服务器等功能的时候,有时候我们不得不依赖于FLASH去实现,而在HTML5到来之后,我 ...
- FormData对象
FF4中增加了一个很有意思的对象,FormData.通常我们提交(使用submit button)时,会把form中的所有表格元素的name与value组成一个queryString,提交到后台.这用 ...
- 使用FormData上传文件、图片
关于FormData XMLHttpRequest Level 2添加了一个新的接口 ---- FormData 利用FormData对象,可以通过js用一些键值对来模拟一系列表单控件,可以使用XM ...
- ajax+XMLHttpRequest里的FormData实现图片异步上传
发这篇博客的时候我是自己在研究这个XMLHttpRequest请求,在别人的博客上面知道XMLHttpRequest新加了一个FormData的东西,好像现在APP请求后台也有用这种方式的吧. 别的不 ...
- 使用JS的FormData对象
利用FormData对象,你可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单". 创建一个FormData对象 你可以先创建一个空的F ...
- 使用PHP和HTML5 FormData实现无刷新文件上传教程
无刷新文件上传是一个常见而又有点复杂的问题,常见的解决方案是构造 iframe 方式实现. 在 HTML5 中提供了一个 FormData 对象 API,通过 FormData 可以方便地构造一个表单 ...
随机推荐
- JVM学习(七)JMM内存模型
一.什么是JMM 概念:Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能 ...
- Java源码赏析(三)初识 String 类
由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...
- xss利用——BeEF#stage1
全文概览 简介 BeEF( The Browser Exploitation Framework) 是由Wade Alcorn 在2006年开始创建的,至今还在维护.是由ruby语言开发的专门针对浏览 ...
- 用Docker swarm快速部署Nebula Graph集群
用Docker swarm快速部署Nebula Graph集群 一.前言 本文介绍如何使用 Docker Swarm 来部署 Nebula Graph 集群. 二.nebula集群搭建 2.1 环境准 ...
- RTThread DFS文件系统使用: 基于使用SFUD驱动的SPI FLASH之上的ELM FATFS文件系统
参考博文: 博文很长,但是实际要操作的步骤没几下. http://m.elecfans.com/article/730878.html 为了防止几年后文章链接找不到,我把文章复制过来了 /***** ...
- 迪杰斯特拉和spfa
迪杰斯特拉 Dijkstra算法是典型的算法.Dijkstra算法是很有代表性的算法.Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均 ...
- Mindmaster破解版与正版
1 免费版与正版 MindMaster思维导图软件,免费版没有过期时间可以一直使用,导出或者保存的文件没有水印.免费版和专业版的不同之处在于,专业版可以享受全功能,比如导出为可编辑的PDF.Offic ...
- 02 ArcPython的使用大纲
一.什么情况下使用ArcPython? 1.现有工具实现不了,可以用python 2.流程化需要时,可以使用python 3.没有AE等二次开发环境 4.其他特殊场景 二.ArcPython在ArcG ...
- Cadence OrCAD如何查看整页原理图中的元件的属性
转载: 1.选中DSN文件右键"Edit Object Properties ".在这里插入图片描述2.单击选择"Pivot"按钮. 3.找到Part refe ...
- matlab中get查询图形对象属性
来源:https://ww2.mathworks.cn/help/matlab/ref/get.html?searchHighlight=get&s_tid=doc_srchtitle get ...