前端开发中总免不了关于文件的上传、下载需求。下面来总结一下常用的办法,欢迎讨论和吐槽。

form 表单提交

最传统的文件上传办法是运用form表单上传文件的,只需求把enctype设置为 multipart/form-data。这种办法上传文件不需求 js ,并且没有兼容问题,所有阅读器都支撑,便是体会很差,导致页面刷新,页面} j x s其他数据丢失。

<form method="post" action="xxxxx" encB 1 ! D x 8type="multipart/form-data"&* = T q , S P E ;gt;
挑选文件:<input type="file" name="file" />
<br />
标题:<input type="text" name="title" />R B u  v
<br />
<buttox J _ s m |n type="submit">提交</button>
</form>

留意:input 有必要设置 name 特点,否则数据无法发送

( f C e T件接口上传

这种办法由服务端提供接口,设e Z t d f t _置相应的8 3 ~ Y 1 y恳求头,前端提交 formDa% = f e * ! t `ta 形式的文件i c u q数据。

<input id="uploadFile" type="H i A Jfile" name="c H ~ Y x (fir L ` % 9 l F lle" accept="image/png,image/gif" />
  • accept:表示能够挑选的文件 MIME 类型,多个 MIME 类型用英文逗号分隔
  • multiple:是否能够挑选多个文件
$('#uploaC  ]dFile').on('change', functi+ # von (e) {
var file = thi9 y ss.files[0]
var formData = new FormData()
formData.append('file', file)
$.ajax({
url: 'xxxx',
type:J f . r c 'post',
data: formData,
c $ 9ache: false,
contentType: false,/ z Y I { Y Y x
processData: false,
sua D b P }ccess: function (res) {
//
},
})
})
  • processData 设置为 false。因为 data 值是, g [ # f J FormData 目标,不需求对数据做处理。
  • cache 设置为 false,上传文件不需求缓存。
  • contentType 设置为 false。

分片上传

有时分咱们上传的文件可u w ( U b E E r能很大,比方视频等可能到达 2 个 G,这样会造成上传速度太r O p S P R a F –慢,乃至有时分会呈现链接超时的状况。G X 2 t K并且有时分服务端会设置文件答应上传的巨细,太大的文件就不答应上传了。为处理这个问题,咱们能够将文件进行分片上传,每次只上传很X [ . q u !小的一部分 比方 1M。

思路

  1. 将文件按必定巨细(比方 1M)s Y C | O [ 8 ! 22 o 8 N ! T取成一小份,并将Z Q %切片带上 hash 值,用于作为标识。
  2. 将每个切片文件并发提交到服务端,服务端保存每个切片文件的信息。
  3. 切片上传完成后,服务端根据文件标识进行兼并,兼并完后删去切片文件。

这样因为每个切片是并发上传的,所以能够有效地下降上传时刻。下面说一下具体的完成步骤。(PS:这是我x I = ] , y o T司的完成办法,并不是唯一办法,且涉及到具体接口的代码就不贴在这里了)

生成 hash 值

不管上传文件信息还是上传切片文件,都有必要要生成文件和切片的 hash。最简略粗暴的 hash 值能够用文件名字+下标来标识,但– 6 ? 4 j是这样文件名一旦修正就失去了作用,而事实上只要文件内容f M E } z R t M不变,l ` zhash 就不应该变化,所以正确的做法是根据文件内容生成# U ! _ j ? + hashx K D y ~ Y m J我司用的是 spark-md5 库,在$ M [ ; ; 2 d @ m这里就不逐个细说了。

文件信息上传

在文件分片上传之前需求把整个文件的信息如该文件的总的文件9 y f :巨细、文件名、哈希值等等,主要意图是初始化一个文件分片上传事情,回来文件 id,用于每个分片的提交。

getFileId (file) {
lO y % I c ] ! zet vm = this
let formData = new FormDat_ } p 4 p I V d a()
formData.a% _ L 0 g )ppend('file', file)
axios({
timeout: 5 * 60 * 1000,
headers: {
'Content-Type': 'u Q 0 , 0 t C a Xapplication/json-',
'x-data': JSON.stringify({
fileName: file.fileName,
size: file.sizJ ; ee,
hash: 'hashxxx',
}),
},
url: 'xxxxxx',
method: 'POST',
})
.then((L R N + y | L . hres) => {
if7 d I e [ 8 g } (res.code === '2008 L  P T p') {
rR K J ^eturn res.data.fileIdp x 1
})
.catch((err) => {
console.log(err)
})
}

文件切片切开

当时端获取到本地图片后,使用 Blob.prototype.slice 办法(和数组的 slice 办法相似),将大文件按照没小片 1M 进行切开,回来原文件的某个切片,再并发将各个分片4 Q + H z L z W |上传到服务端。

getCkunk (file, fileId) {
let vm = this
let chunkSize = 1024 * 1024
let totalSize = file.size
lQ g * f p Uet count = Math.ceil(totalSize / chunkSize)
let chunkArr = []
fc & [or (let i = 0; i < count; i++) {
if (i === count.length - 1) {
chunkArr.push(file.slice(i * chunkSize, totalSize))
} else {
chunkArr.push(file.slice(i * chunkSize, (i + 1) * chunkSize))
}
for (let index = 0; index < count; index++) {
let item = chunkArr[index]
this.uploadChunk(item, index, fileq R O a n E {Id)
}
}

各个分片上传到服务端的办法。此处省略 hash 值得获取办法。

 ploadChunk&  B ^ / F C(item, index, fileId) {
let formData = new FormData()
formData.append} @ o('file', item)
request({
headers: {
'Content-Type': 'application/octet-stream;',
'x-data': JSON.stringify({
fileId: fileId,
partId: index + 1,
hash: resv y # j % ^ w s,
})
},
url: 'xxxxx',
method: 'POST',
data: formDataN n Y R I,
})
.then((res) => {
return res.data.path
})
.c{ r q d @atch((err) => {
console.log(err)
})
}

显现上传进展条

因为文件比较大,即使是采用分片上传的办法也是需求必定的时刻的,为了更好的用户体会,前端最好是提示上传的进展。这时分就需求后端在每个分片的放回成果加上上传的 100%字段。前端获取到回来值就改动当时进展。

当最后一个分片上传完成后,服务端回来文件的 url,前端获取 url,一起将进展条状况改动为 100%。

断点续传

上面说到的分片上( d q F V t传,处理了大文件上传超时和服务器y L N J Y +的约束。但是关于更大的文件,上传并不是短时刻Q 0 { 0 g v % O |内就上传完成,乃至有时分会面临断网或者手动暂停,难道就要p A S { q H从头将整个文件上传了,咱们当然不希望。这时分断点续传就派上用场了。

下面说一下完成思路。
首要断点续传有必要是基于分片D q , K X上传的基础上的

  1. 每个分片上传的时分` ] c w 7 # o,服务端记载上传好的文件o Q V 4 Q v u hash 值,? o b n ` j /上传成功后回来 hash 值给前端,前端记载 hash 值
  2. 从头上传时,将每个文件的 hash 值与记载的 hash 值做比对,假如相同的话则越过,持续下一个分段的上传。
  3. 悉数分片上传完成后,服务端根据文件标识进行兼并,兼并完后删去小文件。r r M [ ` l + w

文件下载

文件下载有以下几种办法

form 表单提交

这是最原始的办法,为一个下载按钮增加 click 事情,点击! m , U D时动态生成一个表单,使用表单提交的功能来完成文件o J ~ N ] ; )的下载(实际上表单的提交便是发送一个恳求)。

function downloadFile(downloadU* d 1 N F m g Rrl, filec # {Name) {
// 创建表单
let form = docump ` beO 9 _ ` Z j knt.crz 3 X ( L {eateElemen7 , + g Q - xt('fF d x P )orm')
form.method = 'g7  Het'
form.: } Taction = downloadUrl
//form.target = '_blank';	// form新开页, O t 4 r
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
}
  • 优点:兼容性好,不会呈现 URL 长度约束问题。
  • 缺陷:无法知道下载的进展,无法直接下载阅读器可直接预览的文件类型(如 txt/png 等)

window.open 或 win+ q 0 4 Edowd x i H ^ . 3 M E.location.href

最简略最直接的办法,实际上跟 a 标签访问下载链接一样

window.open('downloadFile.zF R $ w 8 (ip')e o r
location.href = 'downloadFile.zip'

缺陷

  • 会呈现 URL 长度约束问题H s { A R x i q [
  • 需求留意 url 编码问题
  • 阅读器可直接阅读的文件 f ` I类型是不提供下载的,如 txt、png、jpg、gif 等
  • 不能增加 heb 1 q i f A oader,也就不能进行鉴权
  • 无法知道下载的进展

a 标签 download 特点

download 特点是 HTML5 新增的特点,兼容功能够了解下 can i use download

<a href="xxxx" down, 5 S = 0 a x z yload>G 8 f N t X = L +;点击下载</a>
<!2 ) & $ 8-- 重命名下载文件 -->
<a href) O @ W % ="xxxx" download="test">点击下载</a>

优点:能处理不能直接下载阅读器可阅读的文件。

缺陷

  • a r ) 1 g f _ B #已知下载文件地址
  • 不能下载跨域下的阅读器可阅读的文件
  • 有兼容性问题,特别是 IE
  • 不能进行鉴权

使用 Blob 目标

此办法除了能使用已知文件地址路径进G 2 K 6 $ f行下载外,还能通过发送 ajax 恳求 api 获取文件流进行下载。使用 Blob 目标能够将文件流通化成 Blob 二进制目标。

进行下载的思路很f C $简略:发恳求获取二进制数据,转化为 BlobV Z b r & f # u 目标,使用 URL.createObjectUrl 生成 ur} 7 9 Ul 地址,赋值在 a 标签的 href 特点上,结合 download 进行下载; K 0

downdFe L Oile (path, name) {
const xhr = new XMLHttpRequest();
xhr.open('get', path);i 1 & ~ 2 K 7 l 
xhr.responseType = 'blobY Q ? - $ &';
xhr.% w S bsend();
xhr.onload = function () {
if (this.status === 200 || ths r / K I d v Q !is.status === 304) {
// const blob = new Blob([this.response], { type: xhr.getResponseHeadeF . z i k ) J jr('Content-Type') });
// const ur6  &l = URL.createObjectURL(blob);
constJ ) f | url = URL.createObjectURL(this.response);b d * B O D q # G
cons. $ ` ] M = D 1 6t a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();& e x
document.body.removeChild(a);
URL.revokeObjecP , NtURL(url);
}
}
}

引荐文章

你或许不知道的javascript高档函数
总结javascript处理异步^ 5 E v ) u的办法
总结移动端H5开发常用技巧(干货满满哦!)
从零开始构建一个webpack项目
总结几个webpack打包优化的办法
总结前端功能优化的办法
总结vue常识系统之高档应用` # t p ; [
总结vue常识系统之实用技巧
几种常见的JS递归算法
封装一个toast和dialog组件= ] u , Z ] A g并发布到npm

重视的我的大众号不定期U * ; x 9 V R i %共享前端常识,与您一起进步!

谈谈关于文件上传下载那些事