最近做一个项目,考虑到充分运用云存储的优势,于是用到了七牛来存储 MEDIA 文件。那上传文件当然就考虑直接利用七牛的接口将文件直接存储到七牛上,而不是从我们的服务器中转一次(当然这也是可以的,不过我总觉得如果真这么玩的话岂不是太傻了点)。对于 Web 端来说,我曾经一度都是偷懒,通过简单的表单提交来完成文件等内容的上传。不过这里因为涉及到了第三方存储,那这方法可就不大奏效了。于是又想到了利用 JavaScript 来做文件上传。当然也就需要折腾一番。虽然不复杂,但好记性也不如烂笔头,还是在这里简单记录一下,方便未来参考并与大家分享。
JS 文件上传如今还是有很多成熟的插件供我们使用。在经历一番筛选之后,从易用性和兼容性等方面考虑,我选择了 jQuery File Upload,可以通过以下渠道获得:
网站:http://blueimp.github.io/jQuery-File-Upload/
GitHub:https://github.com/blueimp/jQuery-File-Upload
一切准备就绪,下面就着手完成今天的目标。
需求
在 Django Admin 后台涉及 Media 文件上传的地方,利用 JS 将文件上传至七牛云存储,并且为了操作更友好,实现在上传过程中实时显示上传百分比。
流程分析
在向七牛上传文件之前,最重要的是先获取一个上传凭证,然后利用该上传凭证进行文件上传操作。而在上传之后,根据上传策略的设定,还有一步可选的 callback,可以以此通知业务服务器当前用户的操作情况并对前端进行响应。基于此,整个上传流程归结如下:
1. 用户选定要上传的文件;
2. JS 向业务服务器请求上传凭证;
3. 利用上传凭证向七牛上传文件;
4. 七牛服务器向业务服务器发起回调;
5. 应用服务器返回回调结果;
6. JS 收到回调结果(七牛会将回调结果原封不动的返回给客户端),完成上传。
实现分析
基于之前的流程分析,在业务服务器我们需要提供一个“获取上传凭证”以及“接收七牛回调”的接口。不过本文的主题以 JS 控制文件上传为主,因此不在此对后端的实现细节进行介绍,而是直接关注前端部分,即步骤 2 和 3。
在 MODEL 中,我定义了一个叫“资源”的表,基本的字段包括资源类型(如图片、音乐等)、对应的上传凭证、资源名(即七牛上对应的 KEY 值)以及一个“虚拟的文件”。如上图所示,我将其称之为“虚拟的文件”是因为该字段在 Form 中表现的像一个实体文件并具有 type 为 file 的 input,但实际上该“文件”并不存在于我们本地的服务器上,而是存储在七牛,本地只有一个对应资源的记录而已。再看上图,文件的确体现为一个文件上传的控件,因此我们必须改变原有的行为,用 JS 来控制该控件并实现将文件传到七牛处。因此实现的第一步是引入需要用到的 JS 文件。如果熟悉 Django Form 就会知道这可以利用定义 inner Media class 中的 js 来引入自定义的 JS 文件。
class ResourceUploading(forms.ModelForm):
"""
资源文件上传 Form
"""
...
class Meta:
...
class Media:
js = [
'common/jquery.min.js',
'file-uploader/js/vendor/jquery.ui.widget.js',
'file-uploader/js/jquery.iframe-transport.js',
'file-uploader/js/jquery.fileupload.js',
'admin/qiniu/upload.js'
]
其中引入的最后一个文件“admin/qiniu/upload.js”中是包含我自己定义的各种所需函数。其中最主要的部分如下:
$(function () {
$('#id_file').fileupload({
url: 'http://upload.qiniu.com',
dataType: 'json',
done: function (e, data) {
...
},
autoUpload: false,
add: function (e, data) {
...
在此获取上传凭证,然后触发 data.submit() 上传文件
...
},
fail: function (e, data) {
...
},
progress: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
...
}
});
});
这里用到了几个选项来定制完成所需的功能:
url:所要请求的 URL;
dataType:声明服务器返回的数据类型;
done:请求成功后的回调方法,我在此处理服务器返回,并正对上传成功和失败采取不同的策略;
autoUpload:是否启动自动上传,如果这里设置 true,则用户在选择文件之后会立即触发上传动作;
add:官方是这么解释这个 option 的:
The add callback can be understood as the callback for the file upload request queue. It is invoked as soon as files are added to the fileupload widget - via file input selection, drag & drop or add API call.
也就是说当用户选择了要上传的文件,则会触发该回调。
因此这里是重中之重,当用户选择上传文件后,我首先需要向业务服务器申请上传凭证,然后才触发 data.submit() 来将文件上传到七牛云存储;
fail:这里是请求失败后的回调,可以处理一些异常情况;
progress:上传进度的回调,这是实现实时显示上传百分比功能的重点,我们可以在此计算上传的百分比,并展示给用户以做友好的提示。
效果演示
只要搞清原理,实现起来其实很简单,来看看最终的效果(至于 UI 什么的就可以无视啦,我们现在遵循的是简介实用!)。
1. 勾选“图片”类型并选择一张图片:
2. 在选中之后,JS 会首先获取上传凭证并获取随机分配的资源名,然后进行文件上传操作:
3. 点击“保存”即可完成本次操作: