大文件分片上传,断点续传整理
问题:
前段时间做视频上传业务,通过网页上传视频到服务器。
视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:
1、文件过大,超出服务端的请求大小限制; 2、请求时间过长,请求超时; 3、传输中断,必须重新上传导致前功尽弃;
探索过程:
1、原先咨询过组里的大佬给我推荐了百度的webupload,但后来引入之后发现它是基于jquery封装的。由于本身项目是基于vue开发的所以与jquery相关的开源框架就尽量不考虑了。 2、后来查阅了资料后自己手动实现了文件切片上传到服务器基本需求已经实现,但由于效率及稳定性问题后来决定还是直传文件到七牛云。一开始我使用了表单上传的方式实现了,后来种种原因又要求我做成分片上传。 3、引入七牛的jssdk。
解决方案:
1、修改服务端上传的限制配置;Nginx 以及 PHP 的上传文件限制 不宜过大,一般5M 左右为好; 2、大文件分片,一片一片的传到服务端,再由服务端合并。这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4MB以内,服务端限制在4M即可。 3、使用七牛JavaScript SDK分片上传
首先,刚接触一门新的技术我们还是先去官方文档走一圈了解下基本用法~
文档地址:JavaScript SDK
Qiniu-JavaScript-SDK 为客户端 SDK,没有包含 token
生成实现,为了安全,token
建议通过网络从服务端获取,具体生成代码可以参考服务端 SDK 的文档。
功能简介
- 上传
- 大于 4M 时可分块上传,小于 4M 时直传
- 分块上传时,支持断点续传
- 数据处理(图片)
- imageView2(缩略图)
- imageMogr2(高级处理,包含缩放、裁剪、旋转等)
- imageInfo (获取基本信息)
- exif (获取图片 EXIF 信息)
- watermark (文字、图片水印)
- pipeline (管道,可对 imageView2、imageMogr2、watermark 进行链式处理)
后端返回给你的获取token的json格式必须是这种格式的。必须是一个json对象内部包裹着uptoken字段,带上其他字段也是可以的但是必须第一层要能找到uptoken
{ "uptoken": "0MLvWPnyya1WtPnXFy9KLyGHyFPNdZceomL...", "xxx": "..." }
因为在它的sdk源码中是这么获取token的。他会先找定义的option字段中是否有uptoken,如果没有再去找uptoken_url有就发送ajax请求去获取uptoken字段,倘若后端必须要以他的格式为主那你可以修改sdk源码来实现。如果uptoken_url也没有值就回去调用uptoken_func函数都没有则报错,所以这三个必须指定一个。
// getUptoken maybe called at Init Event or BeforeUpload Event // case Init Event, the file param of getUptken will be set a null value // if op.uptoken has value, set uptoken with op.uptoken // else if op.uptoken_url has value, set uptoken from op.uptoken_url // else if op.uptoken_func has value, set uptoken by result of op.uptoken_func var getUpToken = function(file) { if (op.uptoken) { that.token = op.uptoken; return; } else if (op.uptoken_url) { logger.debug("get uptoken from: ", that.uptoken_url); // TODO: use mOxie var ajax = that.createAjax(); ajax.open('GET', that.uptoken_url, false); ajax.setRequestHeader("If-Modified-Since", "0"); // ajax.onreadystatechange = function() { // if (ajax.readyState === 4 && ajax.status === 200) { // var res = that.parseJSON(ajax.responseText); // that.token = res.uptoken; // } // }; ajax.send(); if (ajax.status === 200) { var res = that.parseJSON(ajax.responseText); that.token = res.uptoken; logger.debug("get new uptoken: ", res.uptoken); } else { logger.error("get uptoken error: ", ajax.responseText); } return; } else if (op.uptoken_func) { logger.debug("get uptoken from uptoken_func"); that.token = op.uptoken_func(file); logger.debug("get new uptoken: ", that.token); return; } else { logger.error("one of [uptoken, uptoken_url, uptoken_func] settings in options is required!"); } };
通过npm安装
npm install qiniu-js
在项目中使用import引入
import 'qiniu-js/dist/qiniu.min.js';
HTML
<div id="container"> <div id="pickfiles">上传按钮</div> </div>
JavaScript
var uploader = Qiniu.uploader({ runtimes: 'html5,flash,html4', // 上传模式,依次退化(照着官网来就是了) browse_button: 'pickfiles', // 上传选择的点选按钮,必需(记得定义id并且保持一致) // 在初始化时,uptoken,uptoken_url,uptoken_func三个参数中必须有一个被设置 // 切如果提供了多个,其优先级为uptoken > uptoken_url > uptoken_func // 其中uptoken是直接提供上传凭证,uptoken_url是提供了获取上传凭证的地址,如果需要定制获取uptoken的过程则可以设置uptoken_func uptoken : '<Your upload token>', // uptoken是上传凭证,由其他程序生成 uptoken_url: '/uptoken', // Ajax请求uptoken的Url,强烈建议设置(服务端提供) uptoken_func: function(){ // 在需要获取uptoken时,该方法会被调用 // do something(一般是发送手动发送ajax获取到token,如果后端返回格式不跟官方一致又不想该懂源代码可以通过这个方式调整) return uptoken; }, get_new_uptoken: false, // 设置上传文件的时候是否每次都重新获取新的uptoken(没有特殊需求一般为false) // downtoken_url: '/downtoken',(未使用到,可以不设置) // Ajax请求downToken的Url,私有空间时使用,JS-SDK将向该地址POST文件的key和domain,服务端返回的JSON必须包含url字段,url值为该文件的下载地址 // unique_names: true, // 默认false,key为文件名。若开启该选项,JS-SDK会为每个文件自动生成key(文件名) // save_key: true, // 默认false。若在服务端生成uptoken的上传策略中指定了sava_key,则开启,SDK在前端将不对key进行任何处理 domain: '<Your bucket domain>', // bucket域名,下载资源时用到,必需(找后端拿) container: 'container', // 上传区域DOM ID,默认是browser_button的父元素(如果不实现拖拽上传可以不设置) max_file_size: '100mb', // 最大文件体积限制(可以调大) flash_swf_url: 'path/of/plupload/Moxie.swf', //引入flash,相对路径(如果没用到flash上传的话可以不设置,一般支持html5上传的浏览器都不会用到它) max_retries: 3, // 上传失败最大重试次数(自动帮你续传分片) dragdrop: true, // 开启可拖曳上传(如果不实现拖拽上传可以不设置) drop_element: 'container', // 拖曳上传区域元素的ID,拖曳文件或文件夹后可触发上传(如果不实现拖拽上传可以不设置) chunk_size: '4mb', // 分块上传时,每块的体积 auto_start: true, // 选择文件后自动上传,若关闭需要自己绑定事件触发上传 //x_vars : { // (未使用到,可以不设置) // 查看自定义变量 // 'time' : function(up,file) { // var time = (new Date()).getTime(); // do something with 'time' // return time; // }, // 'size' : function(up,file) { // var size = file.size; // do something with 'size' // return size; // } //}, init: { 'FilesAdded': function(up, files) { plupload.each(files, function(file) { // 文件添加进队列后,处理相关的事情 }); }, 'BeforeUpload': function(up, file) { // 每个文件上传前,处理相关的事情 // (上传文件前做一些处理) }, 'UploadProgress': function(up, file) { // 每个文件上传时,处理相关的事情 // (可以设置进度条信息) }, 'FileUploaded': function(up, file, info) { // 每个文件上传成功后,处理相关的事情 // 其中info是文件上传成功后,服务端返回的json,形式如: // { // "hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98", // "key": "gogopher.jpg" // } // 查看简单反馈 // var domain = up.getOption('domain'); // var res = parseJSON(info); // var sourceLink = domain +"/"+ res.key; 获取上传成功后的文件的Url }, 'Error': function(up, err, errTip) { //上传出错时,处理相关的事情 }, 'UploadComplete': function() { //队列文件处理完毕后,处理相关的事情 }, 'Key': function(up, file) { // 若想在前端对每个文件的key进行个性化处理,可以配置该函数 // 该配置必须要在unique_names: false,save_key: false时才生效 var key = ""; // do something with key here // (可以自定义key不设定默认是文件名) return key } } }); // domain为七牛空间对应的域名,选择某个空间后,可通过 空间设置->基本设置->域名设置 查看获取 // uploader为一个plupload对象,继承了所有plupload的方法
总结
由于本次项目中只涉及到大文件上传,没有图像处理等相关的api使用经验官方的案例就不多讲了。总结起来七牛云上传的套路就是后台为你提供uptoken或者获取uptoken的接口地址之后上传的时候要带上这个token。返回的字段最好是按照官方的格式来,如果不是的话也可以修改源代码或者在uptoken_func中手动获取,另外如果要修改上传的服务器也是要在qiniu.js中修改
/** * qiniu upload urls * 'qiniuUploadUrls' is used to change target when current url is not avaliable * @type {Array} */ var qiniuUploadUrls = [ // "http://upload.qiniu.com", // "http://up.qiniu.com", "修改成你需要的地址", ];
如果使用表单上传的话可以不引用任何插件,代码实现如下:
<form id="testform" method="post" enctype="multipart/form-data"> <input name="key" id="key" type="hidden" value=""> <input name="token" type="hidden" id="token" value=""> <input id="userfile" name="file" type="file" /> <!-- take photo with phone --> <!-- <input id="userfile" name="file" accept="image/*" type="file" /> --> <!-- take video with phone --> <!-- <input id="userfile" name="file" type="file" accept="video/*"/> --> <input name="accept" type="hidden" /> </form> js: upload() { const formdata = new FormData(document.getElementById('testform')); $.ajax({ url: '上传的七牛云服务器,后端提供', // 'http://up.qiniu.com' method: 'post', success: function(data) { console.log(data); }, }) ... }
需要注意的是,每个input都需要定义好那么属性,并且token不能为空,需要提前通过ajax去后端获取或者使用后端给定的token否则上传会失败~