文件上传 🌕

公司选用七牛作为图床,使用前端直传七牛,不过公司服务器,所以许多本来在服务器上处理的事情要在前端进行解决了。

场景与需求

七牛前端上传SDK提供的上传方法是回调方式的,但上传是个异步过程,在实际的场景中,使用Prosmise处理异步会更为便捷。
七牛官方文档中提供的回调式上传如下:

1
2
3
4
5
var observable = qiniu.upload(file, key, token, putExtra, config)
var subscription = observable.subscribe(observer) // 上传开始
// or
var subscription = observable.subscribe(next, error, complete) // 这样传参形式也可以
subscription.unsubscribe() // 上传取消

一个有三个回调方法:nexterrorcomplete。分别会在上传进度更新、上传失败、上传成功时调用。所以,如果在图片上传成功或者失败,还要继续执行一些语句的话,都只能写在这几个回调方法里,如果处理的事情很多,那么,回调地狱欢迎你!

Promise

Promise就是为了解决回调地狱的问题的,后来的ECMAScript 2017提供了async/await语法糖,可以用同步的方式代替Promise链的写法,使代码更清晰明了、通俗易懂。所以,我们决定使用Promise封装一下上传方法。

封装

Promisify

根据Promise的特性,只有调用了reject或者resolve方法,Promise的状态才会标记结束。换言之,只要这两个方法不被调用,那么Promise会一直等待着方法被调用。

于是我们使用闭包的方式封装了三个回调方法nexterrorcomplete,在它们被调用的时候,才去执行Promisereject或者resolve方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
promisify (observable, next) {
return new Promise((resolve, reject) => {
const complete = (resolve) => {
return res => {
resolve(res.key)
}
}
const error = (reject) => {
return err => {
reject(err)
}
}
const progress = (next) => {
return data => {
next(Number(Number.prototype.toFixed.call(data.total.percent, 2)))
}
}
const subscription = observable.subscribe(progress(next), error(reject), complete(resolve))
this.cancel = () => {
subscription.unsubscribe()
}
})
}

生成唯一的文件名称

前端无法在浏览器端根据图片的内容生成content-hash,所以生成不会重复的文件名只能另辟蹊径。

1
2
3
4
5
generateFileName (type) {
const suffix = type.replace('image/', '').toLowerCase()
const fileName = Date.now().toString(35) + Math.random().toString(35).slice(2)
return `${process.env.VUE_APP_ENV}/${process.env.VUE_APP_BASE_NAME}/${fileName}.${suffix}`
}

我采用的是使用时间戳 + 随机数的方式。

token获取和图片压缩

这个没什么技术的难度,主要是根据实际场景的封装。具体可查看文件上传规范

封装成类

最后,要把所有的方法都写到一个类里面,这样使用上传的时候,只需要实例化一下类就行。具体实现可查看upload.js

错误处理

继承了一个UploadError类,这样在错误捕获时,在控制台可直接找到错误类型。

总结

基础工具的封装一定要注意几点:便于使用,稳定,包含所有使用场景。因为后续的开发可能都要依赖于此,设计之初必须要考虑周到。

要做到这些也并非什么难事,注意一下三点即可。

  1. 要仔细阅读相关SDK的文档,不要有遗漏,在设计时,可以去看一下源代码,理解别人的设计思维;
  2. 要在完成之后多次去测试,最好所有可能的场景都要跑通,切不要跑成功一次就觉得万事大吉。
  3. 如果后续有改动,一定要慎之又慎,要顾及到现有的接口和使用场景,并调研清楚要做的改动的真实需求,以及会不会给现有使用造成影响。