Java+超大文件上传
之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目中,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需求,都能得到满足。小小开心了一把。
但无论插件再怎么灵活,也难以应付所有的需求,比如,你要上传一个2G的文件。以现在我们的网速,恐怕再快也得传半小时。要命的是,如果你在上传到90%的时候不小心关掉了浏览器,或者是手一抖摁了F5,完了,一切还得从头再来。这种用户体验简直太糟糕了。所以,断点续传就十分有必要了。什么是续传我就不解释了,用QQ传文件这么多年,大家都见过了。
这里要说的是断点续传都有哪些技术要点。使用传统的表单提交文件或是HTML5的FormData都是将文件“整块”提交,服务端取到该文件后再进行转移、重命名等操作,因此,无法实时保存文件的已上传部分。而且在http协议下,我们无法保持浏览器与服务端的长连接,不能以文件流的形式来提交。所以要解决的问题具体来讲有以下几点:
对上传的文件进行分割,每次只上传一小片。服务端接收到文件后追加到原来部分,最后合并成完整的文件。
每次上传文件片前先获取已上传的文件大小,确定本次应切割的位置
每次上传完成后更新已上传文件大小的记录
标识客户端和服务端的文件,保证不会把A文件的内容追加到B文件上
在参考了张鑫旭大哥的这篇文章后,我将学到的技术应用在了我的插件Huploadify中,成功的添加了断点续传功能。在此将技术和插件都分享给大家。
工作原理/技术要点
首先的首先,要明确,如果我们有一个10M的文件,每次切割上传1M,那么是需要发10次请求来完成的。在http协议下,只能这么搞。断点上传分三步来完成:
选择一个文件后,获取该文件在服务器上的大小,通过本地存储或自定义的函数来获取。
根据已上传大小切割文件,发出n次请求不断向服务器提交文件片,服务端不断追加文件内容
当已上传文件大小达到文件总大小时,上传结束
首先是文件的分割,HTML5新增了Blob数据类型,并且提供了一个可以分割数据的方法:slice(),其用法和字符串、数组的slice()方法一样,可以截取一个二进制文件的一部分。
其次是文件片的保存与追加,我后台用PHP写的,先用file_get_contents获取文件的二进制格式,再用file_put_contents每次将文件追加,具体的写法可以参照后面,或者是下载我打包好的文件。
接下来我们还需要实时保存已上传文件的大小,以便于下次上传前进行正确切割。使用HTML5的localStorage是一种方法,将已上传的大小保存在本地,下次上传前先从本地读取。不过这种方式是很局限的,抛开用户可能通过各种管家清除掉本地数据不讲,假如用户在A页面上传了一个文件的50%,然后在B页面想把该文件上传到另外一个地方,结果从本地一读文件已上传50%了,直接从51%的位置开始上传了,显然是个错误。问题就在于本地不能存太多的信息,通过File API只能获取到文件的原始名称,无法正确的与服务器上的文件正确匹配。所以真正在项目中用,还得依靠服务端来保存这些数据。
关于如何将数据存在服务端,已经前端如何取数据,我在下面会讲到。
技术要点就上面的那么多了,其实也没有多少技术含量哈~来看看我的插件如何使用吧。
续传功能的使用方法
文件的引入就不讲了,可参考上一篇关于插件的介绍。关键点是新增的几个配置,先来看一下:
breakPoints:false,//是否开启断点续传 fileSplitSize:1024*1024,//断点续传的文件块大小,单位Byte,默认1M getUploadedSize:null,//类型:function,自定义获取已上传文件的大小函数,用于开启断点续传模式,可传入一个参数file,即当前上传的文件对象,需返回number类型 saveUploadedSize:null,//类型:function,自定义保存已上传文件的大小函数,用于开启断点续传模式,可传入两个参数:file:当前上传的文件对象,value:已上传文件的大小,单位Byte saveInfoLocal:false,//用于开启断点续传模式,是否使用localStorage存储已上传文件大小
这是插件中的默认配置值。一个续传功能竟然要配置五个项,真要命!不要着急听我慢慢道来,这五个并不是要同时出现的,是为了满足可能出现的复杂业务而准备的。
breakPoints是开启断点续传的开关,要使用的话设为true,默认是不开启的。
fileSplitSize是每次切割的文件片的大小,默认是1M,可根据实际情况来定。如果你的系统上传的文件普遍都在1G以上,可以配置的大一点。
getUploadedSize是用来自定义获取已上传的文件大小的函数,还记得上面说过的localStorage的局限吧,所以我这里直接把获取文件大小的函数交给你来定义,你可以从session、cookie,从文件、数据库或者任何地方取,可以发送一个ajax请求到你想要的地址,传递你需要的参数。注意你定义的函数将来会被插件调用,所以一定要返回一个Number类型的结果。
saveUploadedSize与getUploadedSize对应,你自己定义如何保存已上传文件的大小,只要你存的数据你自己能取到就OK。当然前提是你要注意到上面说过的localStorage的局限,确保你的逻辑正确能够操作到正确的文件。
saveInfoLocal是当你使用localStorage保存数据时需要开启的一个开关。插件默认提供使用localStorage方式的支持。只要开启此选项就可以了。当然,这种情况下你的业务逻辑必须足够简单,比如只是做一个上传的demo,或者这系统的用户只有你一个人,你明白如何避开那些局限的地方。
掌握了这五个配置的作用,你就可以实现一个足够灵活的断点上传功能了!在我打包好的文件里,提供了使用localStorage方式的demo,抱歉我无法将数据库表都发给你,所以只能用本地存储来演示。
在服务端保存数据
用户在使用上传的时候可能有各种你意想不到的操作,这里我发挥想象描述一下用户可能的行为:
同一台机器使用不同帐号登录,上传同一个文件
文件上传了一部分,然后修改了文件内容,再次上传
文件上传完成100%,再次上传该文件
同一个页面有多个上传按钮,上传同一个文件,或在不同页面上传同一个文件
仅仅上面四条,是不是情况就够复杂了?再加上你系统还有自己的业务逻辑,所以在服务端保存已上传文件数据是非常有必要的。而且保存数据和获取数据的函数都交给你来定义,抱着插件有足够的灵活性。
因为涉及到了服务端的技术,无法演示,我将我项目中的真实使用场景在此讲解一下,来展示一下如何自已定义方法来实现服务端保存数据的可靠上传。我定义的getUploadedSize函数如下:
getUploadedSize:function(file){
var data = {
data : {
fileName : file.name,
lastModifiedDate : file.lastModifiedDate.getTime()
}
};
var url = 'http://localhost/uploadfile/';
var uploadedSize = 0;
$.ajax({
url : url,
data : data,
async : false,
type : 'POST',
success : function(returnData){
returnData = JSON.parse(returnData);
uploadedSize = returnData.uploadedSize;
}
});
return uploadedSize;
}
我向后台的某个地址发送一个请求,传递文件名和文件的最后修改时间为参数,后台根据这两个参数来找到与前台所选择的文件对应的服务器上的文件,将服务器返回的文件大小return出去,来被插件使用。为什么要传递这两个参数呢?我们在前台无法知道服务器上的这个文件的名称,所以使用原始文件名作为一个辅助标识。为了防止用户在两次上传间隔修改了文件,我们把文件的最后修改时间也传给服务端,让服务端进行比较,若时间不对应则返回已上传大小为0,重新上传此文件。
再来看后台都要做哪些工作。数据库中需要有一张表来记录每个已文件的情况,包含的字段大致有:
字段 | 描述 |
client_filename | 文件在客户端的原始名称 |
server_filename | 文件在服务器上重命名后的名称 |
last_modified_date | 文件的最后修改时间,时间戳 |
status | 文件的状态,已完成、未完成 |
uploaded_size | 已上传文件的大小 |
根据client_filename和last_modified_date,再加上系统中的其他关联信息,可以定位到本次上传的文件在服务端的大小,然后返回给客户端。当然这是我自己的用法,你也可以根据自己的需求灵活设计。总之最终的目的就是要找到前台选择的文件在服务器上真正对应的文件,并将已上传大小正确返回。
另外需注意的一点,就是在续传的第二步,不断提交文件片的过程中,也需要服务端准确定位到相应的文件,不能把A的数据追加到B上。采用的方式也是提交fileName和lastModifyDate两个参数(已写在插件内部,可服务端直接获取),服务端找到对应的文件进行追加。
另外再啰嗦一句,后台获取文件的时候需要取成二进制的,而我们提交是使用FormData来提交的,所以PHP代码需要这么写:
file_put_contents('uploads/'.$filename,file_get_contents($_FILES["file"]["tmp_name"]),FILE_APPEND);
如果上面的说明还是不够清楚,就需要你自己来探索一下了,毕竟考虑到插件可能应用在复杂的系统中,很多工作还是需要你来做的。或者你也可以给我留言,我很乐意为你解答疑惑。
该版本的其他改动
从1.0到2.0,Huploadify又新加了很多东西,不过只是新加,使用方式跟之前的没有变化。例如上面的断点续传功能,你如果不想使用,只需设置breakPoints为false即可,插件仍按照以前的方式工作。除了断点续传这个大头,插件还做了如下改动:
增加了onSelect回调函数,在选择了文件之后触发,用法与uploadify官网的一致
删除掉正在上传的文件,中断发送请求
完善了input file组件的accept属性支持,浏览时只显示运行的文件格式,就是这个东东:
4. 对外开放了方法调用接口,upload、stop、cancel、disable、ennable。我在demo中有演示。使用方法如下:var up = $('#upload').Huploadify({
auto:false, fileTypeExts:'*.jpg;*.png;*.exe;*.mp3;*.mp4;*.zip;*.doc;*.docx;*.ppt;*.pptx;*.xls;*.xlsx;*.pdf', multi:true }); up.upload(1);//开始上传文件,接收一个参数,表示上传第几个文件,可传入*上传队列中的所有文件 up.stop();//暂停上传队列中的所有文件,不接收参数。用于开启了断点需传 up.cancel(1);//删除队列中的某个文件,接收一个参数,表示删除第几个文件,可传入*删除队列中的所有文件 up.disable();//使选择文件按钮失效,不接收参数 up.ennable();//使选择文件按钮生效,不接收参数 5. 修改其他已知bug
结束
插件刚刚完成,与我们的后端程序员调试完成了断点续传功能暂未发现问题,欢迎大家在使用的时候给我提任何问题。老实来讲这个功能使用起来还是挺费解的,为了最大程度的保证灵活做成这样,大家可以与我多多交流~
我在demo中使用了本地存储来做已上传文件大小的保存,下载压缩包后可看一下效果。上传一个比较大的视频文件,上传到中间关闭浏览器,再次打开浏览器上传同一个文件,会看到从上次断掉的地方继续上传。
详细内容可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/09/%e5%a4%a7%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88/
Java+超大文件上传的更多相关文章
- Java超大文件上传解决办法
这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...
- java+超大文件上传与下载
这里先说下spring mvc 遇到的坑,就是如果文件上传时,后端这样写public String file1(HttpServletRequest request),根据request拿到的东西是 ...
- java超大文件上传
上周遇到这样一个问题,客户上传高清视频(1G以上)的时候上传失败. 一开始以为是session过期或者文件大小受系统限制,导致的错误. 查看了系统的配置文件没有看到文件大小限制, web.xml中se ...
- web超大文件上传
文件夹数据库处理逻辑 publicclass DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject(); ...
- java+大文件上传解决方案
众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...
- H5+JAVA的文件上传,断点续传
这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...
- 4GB以上超大文件上传和断点续传服务器的实现
随着视频网站和大数据应用的普及,特别是高清视频和4K视频应用的到来,超大文件上传已经成为了日常的基础应用需求. 但是在很多情况下,平台运营方并没有大文件上传和断点续传的开发经验,往往在网上找一些简单的 ...
- java进行文件上传,带进度条
网上看到别人发过的一个java上传的代码,自己写了个完整的,附带源码 项目环境:jkd7.tomcat7. jar包:commons-fileupload-1.2.1.jar.commons-io-1 ...
- Java Web文件上传
参考资料:http://www.cnblogs.com/xdp-gacl/p/4200090.html 一.问题描述 Java Web文件上传需要借助一些第三方库,常用的是借助Apache的包,有两个 ...
随机推荐
- noi.ac-CSP模拟Day5T1 组【二分图最大匹配】
虽然是T3,但是想通了之后还是不难的. 数据规模也不大. 可以考虑先枚举一个班长,根据题意,和班长连边的学生就可以不用管,没有和班长连边的学生就要去找一个和班长连边的学生组队,如果所有没有和班长连边的 ...
- 【转】MySQL-Utilities,mysql工具包
原文:https://blog.csdn.net/leshami/article/details/52795777 MySQL Utilities 是一组基于python语言编写的python库的命令 ...
- Go语言的变量和常量(三)
我想吐槽下网上的很多所谓的“零基础教程”,因为那根本不算零基础.就拿语言教程来说,一上来就说怎么定义变量的怎么算零基础呢?零基础应该是先告诉你啥叫变量. 所以我从不起零基础的标题.我这也不是教程,只是 ...
- unittest中的testCase执行顺序
1.方法顺序 def setUp(self): 在测试方法前执行 def tearDown(self): 在测试方法后执行 class TestMethod(unittest.TestCase): # ...
- Python中字典合并的四种方法
字典是Python语言中唯一的映射类型.映射类型对象里哈希值(键,key)和指向的对象(值,value)是一对多的的关系,通常被认为是可变的哈希表.字典对象是可变的,它是一个容器类型,能存储任意个数的 ...
- wex5 如何在js中给data添加数据
var options = { defaultValues :[ {'xuetang' : xuetang,'time' : time} ] }; this.comp("xuetangDat ...
- JVM常用虚拟机命令汇总
title: JVM常用虚拟机命令汇总 comments: false date: 2019-07-22 11:45:33 description: 总结一下常用的JVM虚拟机启动命令. catego ...
- var与let变量for遍历的问题
var Liarry = document.getElementsByTagName('li'); /**方法一*描述:自执行函数,将变量当参数传入(闭包的思想,保存当前的值).*/ for (var ...
- Ubuntu18.04 安装redis
Redis是常用基于内存的Key-Value数据库,比Memcache更先进,支持多种数据结构,高效,快速.用Redis可以很轻松解决高并发的数据访问问题:作为实时监控信号处理也非常不错. 安装red ...
- SpringBootMVC02——SpringDataJpa与ThymeLeaf
大纲 - SpringDataJpa进阶使用- SpringDataJpa自定义查询- 整合Servlet.Filter.Listener- 文件上传- Thymeleaf常用标签 1.整合Servl ...