一、前言

最近项目中遇到一个问题,我们需要在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;
}
}

踩坑记录:

  1. 首先,我需要定义一个boundary_key,它当前环境提供给FormData的随机数,chrome v87.0.4270.0提供给FormData的是"WebKitFormBoundary" + "xxxxxxxxxxxxxx" 随机数。我在项目里使用的是timestamp,这里只要提供一个随机数即可。
  2. appendFile方法的实现,要根据具体上传类型,文件类型,作content-type定义,比如我这里上传的是音频文件,所以设置的是"audio/mpeg"。
  3. 普通键值对和复杂键值对的区分,如果value是字符串,直接分解成字符再处理;如果是number,这里有个坑,那就是直接添加到FormData会失败,所以需要先把number值转为string,再像处理string值一样处理。
  4. 再看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);
});

踩坑记录:

  1. 因为我们上传的是二进制流数据,appendFile函数添加后缀名,在formdata数据里定义好Content-Disposition里的文件名,方便和后端开发人员识别是什么样的文件,是有必要的。
  2. request headers里注意multipart/form-data; boundary=${stamp}这里一定要把随机数写到boundary=后面,否则后端服务会报错'no multipart boundary was found'

五、如果觉得有帮助的话,还请给个赞,谢谢!

如何实现一个FormData的更多相关文章

  1. 前端传递数据到后台的两种方式;创建一个map或者创建一个FormData对象

    一.构建一个map getAllDeptAllUsers(){ const modleCode = {'auditMenuId': this.auditMenuId, 'enterpriseId': ...

  2. 三种上传文件不刷新页面的方法讨论:iframe/FormData/FileReader

    发请求有两种方式,一种是用ajax,另一种是用form提交,默认的form提交如果不做处理的话,会使页面重定向.以一个简单的demo做说明: html如下所示,请求的路径action为"up ...

  3. 通过Ajax使用FormData对象无刷新上传文件

    写在前面:本文说的这个方案有浏览器兼容性问题:所有主流浏览器的较新版本已经支持这个对象了,比如Chrome 7+.Firefox 4+.IE 10+.Opera 12+.Safari 5+,对兼容性比 ...

  4. 利用Jquery使用HTML5的FormData属性实现对文件的上传

    1.利用Jquery使用HTML5的FormData属性实现对文件的上传 在HTML5以前我们如果需要实现文件上传服务器等功能的时候,有时候我们不得不依赖于FLASH去实现,而在HTML5到来之后,我 ...

  5. FormData对象

    FF4中增加了一个很有意思的对象,FormData.通常我们提交(使用submit button)时,会把form中的所有表格元素的name与value组成一个queryString,提交到后台.这用 ...

  6. 使用FormData上传文件、图片

    关于FormData XMLHttpRequest Level 2添加了一个新的接口  ---- FormData 利用FormData对象,可以通过js用一些键值对来模拟一系列表单控件,可以使用XM ...

  7. ajax+XMLHttpRequest里的FormData实现图片异步上传

    发这篇博客的时候我是自己在研究这个XMLHttpRequest请求,在别人的博客上面知道XMLHttpRequest新加了一个FormData的东西,好像现在APP请求后台也有用这种方式的吧. 别的不 ...

  8. 使用JS的FormData对象

    利用FormData对象,你可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单". 创建一个FormData对象 你可以先创建一个空的F ...

  9. 使用PHP和HTML5 FormData实现无刷新文件上传教程

    无刷新文件上传是一个常见而又有点复杂的问题,常见的解决方案是构造 iframe 方式实现. 在 HTML5 中提供了一个 FormData 对象 API,通过 FormData 可以方便地构造一个表单 ...

随机推荐

  1. JVM学习(七)JMM内存模型

    一.什么是JMM 概念:Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能 ...

  2. Java源码赏析(三)初识 String 类

    由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...

  3. xss利用——BeEF#stage1

    全文概览 简介 BeEF( The Browser Exploitation Framework) 是由Wade Alcorn 在2006年开始创建的,至今还在维护.是由ruby语言开发的专门针对浏览 ...

  4. 用Docker swarm快速部署Nebula Graph集群

    用Docker swarm快速部署Nebula Graph集群 一.前言 本文介绍如何使用 Docker Swarm 来部署 Nebula Graph 集群. 二.nebula集群搭建 2.1 环境准 ...

  5. RTThread DFS文件系统使用: 基于使用SFUD驱动的SPI FLASH之上的ELM FATFS文件系统

    参考博文: 博文很长,但是实际要操作的步骤没几下. http://m.elecfans.com/article/730878.html  为了防止几年后文章链接找不到,我把文章复制过来了 /***** ...

  6. 迪杰斯特拉和spfa

    迪杰斯特拉 Dijkstra算法是典型的算法.Dijkstra算法是很有代表性的算法.Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均 ...

  7. Mindmaster破解版与正版

    1 免费版与正版 MindMaster思维导图软件,免费版没有过期时间可以一直使用,导出或者保存的文件没有水印.免费版和专业版的不同之处在于,专业版可以享受全功能,比如导出为可编辑的PDF.Offic ...

  8. 02 ArcPython的使用大纲

    一.什么情况下使用ArcPython? 1.现有工具实现不了,可以用python 2.流程化需要时,可以使用python 3.没有AE等二次开发环境 4.其他特殊场景 二.ArcPython在ArcG ...

  9. Cadence OrCAD如何查看整页原理图中的元件的属性

    转载: 1.选中DSN文件右键"Edit Object Properties ".在这里插入图片描述2.单击选择"Pivot"按钮. 3.找到Part refe ...

  10. matlab中get查询图形对象属性

    来源:https://ww2.mathworks.cn/help/matlab/ref/get.html?searchHighlight=get&s_tid=doc_srchtitle get ...