前端开发中总免不了关于文件的上传、下载需求。下面来总结一下常用的办法,欢迎讨论和吐槽。
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 JFormData
目标,不需求对数据做处理。 -
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。
思路
- 将文件按必定巨细(比方 1M)s Y C | O [ 8 ! 2截2 o 8 N ! T取成一小份,并将Z Q %切片带上 hash 值,用于作为标识。
- 将每个切片文件并发提交到服务端,服务端保存每个切片文件的信息。
- 切片上传完成后,服务端根据文件标识进行兼并,兼并完后删去切片文件。
这样因为每个切片是并发上传的,所以能够有效地下降上传时刻。下面说一下具体的完成步骤。(PS:这是我x I = ] , y o T司的完成办法,并不是唯一办法,且涉及到具体接口的代码就不贴在这里了)
生成 hash 值
不管上传文件信息还是上传切片文件,都有必要要生成文件和切片的 hash
。最简略粗暴的 hash
值能够用文件名字+下标来标识,但– 6 ? 4 j是这样文件名一旦修正就失去了作用,而事实上只要文件内容f M E } z R t M不变,l ` zhash
就不应该变化,所以正确的做法是根据文件内容生成# U ! _ j ? + hash
。x 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上传的基础上的
- 每个分片上传的时分` ] c w 7 # o,服务端记载上传好的文件o Q V 4 Q v u hash 值,? o b n ` j /上传成功后回来 hash 值给前端,前端记载 hash 值
- 从头上传时,将每个文件的 hash 值与记载的 hash 值做比对,假如相同的话则越过,持续下一个分段的上传。
- 悉数分片上传完成后,服务端根据文件标识进行兼并,兼并完后删去小文件。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);
}
}
}
引荐文章
重视的我的大众号不定期U * ; x 9 V R i %共享前端常识,与您一起进步!