本文对最近学习的Web文件上传方法做一些总结,主要用到了百度的Web Uploader。
Web文件上传通过Http请求进行传输,可以通过Java工具进行接收。要注意包含文件上传内容的表单的类型必须为enctype=”multipart/form-data”
请求正文中的内容如下图:
最常见是使用Apache commons中的两个jar包实现Web文件上传
第一步是文件上传的jsp页面,上传文件和一个普通text输入框:
<form action="${pageContext.request.contextPath}/UploaderServlet" method="post" enctype="multipart/form-data"> 选择要上传的文件:<input type="file" name="attach"/><br/> 普通数据:<input type="text" name="info"><br/> <input type="submit" value="点击上传"/> </form>新建一个文件夹当做服务器保存上传的文件:F:\uploader
接下来是新建一个servlet用于保存数据,接收到前台数据之后,对数据信息进行判断是普通数据还是文件(文件就是type=”file”),然后分别进行处理。文件就把它保存到服务器,就是前面新建的文件夹。
// 1. 创建DiskFileItemFactory对象,配置缓存信息 DiskFileItemFactory factory = new DiskFileItemFactory(); // 2. 创建ServletFileUpload对象 ServletFileUpload sfu = new ServletFileUpload(factory); // 3. 设置文件名称的编码 sfu.setHeaderEncoding("utf-8"); // 4. 开始解析文件 try { List<FileItem> items = sfu.parseRequest(request); // 服务器的目录 String serverPath = "F:/uploader"; // 5. 获取文件信息 for (FileItem item : items) { // 6. 判断是文件还是普通的数据 if (item.isFormField()) { // 普通数据 String fileName = item.getFieldName(); if (fileName.equals("info")) { // 获取文件信息 String info = item.getString("utf-8"); System.out.println(info); } } else { // 文件 // 获取文件的名称 String name = item.getName(); // 获取文件的实际内容 InputStream is = item.getInputStream(); // 保存文件 FileUtils.copyInputStreamToFile(is, new File(serverPath + "/" + name)); } } } catch (FileUploadException e) { // TODO Auto-generated catch block e.printStackTrace(); } }完成之后上传测试文件.txt以及输入文本信息弄浪的鱼文件,再看http请求头已经包含上传信息,然后文件夹会有文件,控制台输出弄浪的鱼:
从这里开始到后面的内容都用到了百度的Web Uploader,所以先下载Web Uploader,然后导入到工程中再将jsp页面进行修改
文件上传时会有三种状态:文件上传前,文件上传中和文件上传后。上传前可以进行一些初始操作,比如追加一些div用于显示上传信息;上传中显示上传的进度‘上传后显示上传完成。Web Uploader通过js对这三种状态进行监听,每个状态传递不同的参数就行。
所以先导入js和css并且在jsp中引用,注意有些地方用了jquery:
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/webuploader.css"> <script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.7.2.js"> </script> <script type="text/javascript" src="${pageContext.request.contextPath}/js/webuploader.js"> </script>然后是网页代码
<div id="uploader"> <!-- 显示文件列表 --> <ul id="fileList"></ul> <!-- 选择文件区域 --> <div id="filePicker">点击上传文件</div> </div>javascript初始化及注册三种状态监听
<script type="text/javascript"> //1.初始化WebUploader,以及配置全局参数 var uploader = WebUploader.create({ // swf文件路径 swf : "${pageContext.request.contextPath}/js/Uploader.swf", // 文件接收服务端。 server : "${pageContext.request.contextPath}/UploaderServlet", // 选择文件的按钮。可选。 // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick : '#filePicker', // 自动上传 auto : true }); //2. 选择文件后,文件信息队列展示 //注册fileQueued事件:当文件加入队列后触发 uploader.on("fileQueued",function(file){ //追加文件信息div $("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name + "</span><div class='state'>等待上传...</div><span class='text'><span></div>"); }); //3. 注册上传监听 //percentage:当前上传进度0-1 uploader.on("uploadProgress",function(file,percentage){ var id=$("#"+file.id); //更新状态信息 id.find("div.state").text("上传中..."); //更新上传的百分比 id.find("span.text").text(Math.round(percentage*100)+"%"); }); //4. 注册上传完毕监听 //response:后台回送数据,json格式 uploader.on("uploadProgress",function(file,percentage){ //更新状态信息 $("#"+file.id).find("div.state").text("上传完毕"); }); </script> ~~~ 生成缩略图,明显是在文件上传过程中生成的,所以在上传过程中的监听中添加生成缩略图的内容 ~~~JavaScript uploader.on("fileQueued",function(file){ //追加文件信息div $("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name + "</span><div class='state'>等待上传...</div><span class='text'><span></div>"); //生成缩略图:调用makeThumb()方法 //error:制造缩略图失败 //src:缩略图的路径 uploader.makeThumb(file,function(error,src){ var id = $("#" + file.id); //如果失败,则显示不能预览 if(error){ id.find("img").replaceWith("不能预览"); } //成功,则显示缩略图到指定位置 id.find("img").attr("src",src); }); });很多网站支持拖拽上传,拖到指定区域就能上传感觉很酷很爽,其实不难。只需要在全局参数中开启拖拽功能:
var uploader = WebUploader.create({ // swf文件路径 swf : "${pageContext.request.contextPath}/js/Uploader.swf", // 文件接收服务端。 server : "${pageContext.request.contextPath}/UploaderServlet", // 选择文件的按钮。可选。 // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick : '#filePicker', // 自动上传 auto : true, //开启脱宅功能,指定拖拽区域 dnd:"#dndArea", //禁止页面其他地方拖拽功能 disableGlobalDnd:true, //开启黏贴功能 paste:"#uploader" });分块上传:上传的文件可能比较大比如说有19M,如果单线程进行上传速度比较慢,但是如果把19M的文件分成多块采用多线程上传,最后将文件再次拼起来也能够完成文件上传的过程,而且速度更快了。
MD5值:将文件分块再拼接用到了一个概念MD5值。简单讲就是一个文件会有一个MD5就像人的身份证一样,是它在网上的身份标识,根据MD5值就能判断是哪一个文件。拿百度云举例子,有时候上传一步电影能够做到秒传,那是因为百度云的服务器存了一份相同的电影,正好和你上传电影的MD5值相同也就是说是同一部电影,所以只要给你一个指向这份文件的索引就相当于上传了电影,达到了秒传。
知道了这两个概念看一下分块上传图解:
首先很好理解,在初始化过程中需要开启分块上传功能:
var uploader = WebUploader.Uploader({ // 其他同上,略 // 开启分片上传。 chunked: true });接下来可以结合上面的图来看了: 第一步:肯定是先要获取MD5值,并且将值传递到后台。这样才能创建一个MD5命名的文件夹,用于保存分片。所以由前台js获取文件的MD5值,有一个后台程序用于创建文件夹。 第二步:本应该是将文件分片,但是分片的操作不需要我们进行,Web Uploader帮我们做好了,我们要做的就是将分片索引名传递到后台,并保存到相应文件夹下。 第三步:将分片根据索引排序,然后用I/O流合并分片,最后再给个文件名。所以这一步要有一个文件合并的后台程序,用Ajax传递数据到后台
后面有整个源码,就不贴所有代码了。在原来基础上添加如下js,注意要放在web uploader初始化之前:
//获取文件的标记 var fileMd5; //5.监控文件的三个上传时间点 //时间点一:所有分块进行上传之前(1.计算文件的MD5 2.判断是否秒传) //时间点二:如果分块上传,每个分块上传之前(选文后台该分块是否保存成功) //时间点三:分块上传成功(通知后台合并) WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send": "beforeSend", "after-send-file": "afterSendFile" },{ //时间点一 beforeSendFile:function(file){ //创建一个deffered var deferred = WebUploader.Deferred(); //1.计算文件的唯一标记,用于断点续传和秒传 (new WebUploader.Uploader()).md5File(file,0,5*1024*1024) .progress(function(percentage){ $("#"+file.id).find("div.state").text("正在获取文件信息..."); }) .then(function(val){ fileMd5 = val; $("#"+file.id).find("div.state").text("成功获取文件信息"); //获取文件信息之后需要进入到下一步 deferred.resolve(); }); //返回deffered return deferred.promise(); }, //时间点2:如果有分块上传,则 每个分块上传之前调用此函数 //block:代表当前分块对象 beforeSend:function(block){ //alert(fileMd5); //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能 var deferred = WebUploader.Deferred(); //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录 this.owner.options.formData.fileMd5 = fileMd5; //进入下一步 deferred.resolve(); return deferred.promise(); }, //时间点三 afterSendFile:function(file){ //1.如果分块上传,则通过后台合并所有分块文件 //请求后台合并文件 $.ajax( { type:"POST", url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=mergeChunks", data:{ //文件唯一标记 fileMd5:fileMd5, //文件名称 fileName:file.name }, dataType:"json", success:function(response){ alert(response.msg); } } ); }, });断点续传:用百度云举例子,一个文件上传了一般有事把电脑关了,下次打开百度云上传会在原来的基础上继续上传,这个就是断点续传。上面已经知道了文件可以分块上传,所以有些块已经上传了而有些块没有。重新上传文件时,那些已经上传过的文件块就不需要上传了。
前台js
$.ajax( { type:"POST", url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=checkChunk", data:{ //文件唯一标记 fileMd5:fileMd5, //当前分块下标 chunk:block.chunk, //当前分块大小 chunkSize:block.end-block.start }, dataType:"json", success:function(response){ if(response.ifExist){ //分块存在,跳过该分块 deferred.reject(); }else{ //分块不存在或者不完整,重新发送该分块内容 deferred.resolve(); } } } );后台代码:
if("checkChunk".equals(action)){ System.out.println("checkChunk..."); String fileMd5 = request.getParameter("fileMd5"); String chunk = request.getParameter("chunk"); String chunkSize = request.getParameter("chunkSize"); File checkFile = new File(serverPath+"/"+fileMd5+"/"+chunk); response.setContentType("text/html;charset=utf-8"); //检查文件是否存在,且大小是否一致 if(checkFile.exists() && checkFile.length()==Integer.parseInt(chunkSize)){ response.getWriter().write("{\"ifExist\":1}"); }else{ response.getWriter().write("{\"ifExist\":0}"); } }源码地址:https://github.com/shuishui17/JavaUploader.git 代码都贴上来有点冗长就放git了,git我不怎么会用,也就会把代码传传上去,接下来会好好学下怎么使用
