插件描述:
WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。 采用大文件分片并发上传,极大的提高了文件上传效率。
插件特点:
分片、并发
分片与并发结合,将一个大文件分割成多块,并发上传,极大地提高大文件的上传速度。
当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。
预览、压缩
支持常用图片格式jpg,jpeg,gif,bmp,png预览与压缩,节省网络数据传输。
解析jpeg中的meta信息,对于各种orientation做了正确的处理,同时压缩后上传保留图片的所有原始meta数据。
多途径添加文件
支持文件多选,类型过滤,拖拽(文件&文件夹),图片粘贴功能。
粘贴功能主要体现在当有图片数据在剪切板中时(截屏工具如QQ(Ctrl + ALT + A), 网页中右击图片点击复制),Ctrl + V便可添加此图片文件。
HTML5 & FLASH
兼容主流浏览器,接口一致,实现了两套运行时支持,用户无需关心内部用了什么内核。
同时Flash部分没有做任何UI相关的工作,方便不关心flash的用户扩展和自定义业务需求。
MD5秒传
当文件体积大、量比较多时,支持上传前做文件md5值验证,一致则可直接跳过。
如果服务端与前端统一修改算法,取段md5,可大大提升验证性能,耗时在20ms左右。
易扩展、可拆分
采用可拆分机制, 将各个功能独立成了小组件,可自由搭配。
采用AMD规范组织代码,清晰明了,方便高级玩家扩展。
资源介绍:
// SWF文件,当使用Flash运行时需要引入。
Uploader.swf
// 完全版本。
webuploader.js
webuploader.min.js
// 只有Flash实现的版本。
webuploader.flashonly.js
webuploader.flashonly.min.js
// 只有Html5实现的版本。
webuploader.html5only.js
webuploader.html5only.min.js
// 去除图片处理的版本,包括HTML5和FLASH.
webuploader.withoutimage.js
webuploader.withoutimage.min.js
使用方法:
接下来以图片上传实例,讲解如何使用WebUploader。
我们首先将css和相关js文件加载。
1 <link rel="stylesheet" type="text/css" href="css/webuploader.css"> 2 <script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> 3 <script type="text/javascript" src="js/webuploader.min.js"></script>
然后我们需要准备一个按钮#imgPicker,和一个用来存放添加的文件信息列表的容器#fileList,在body中加入如下代码:
1 <div id="uploadimg"> 2 <div id="fileList" class="uploader-list"></div> 3 <div id="imgPicker">选择图片</div> 4 </div>
首先创建Web Uploader实例:
1 var uploader = WebUploader.create({
2 auto: true, // 选完文件后,是否自动上传
3 swf: 'js/Uploader.swf', // swf文件路径
4 server: '/uploadServlet?methodName=fileupload', // 文件接收服务端
5 pick: '#imgPicker', // 选择文件的按钮。可选
6 // 只允许选择图片文件。
7 accept: {
8 title: 'Images',
9 extensions: 'gif,jpg,jpeg,bmp,png',
10 mimeTypes: 'image/*'
11 }
12 });
接着监听fileQueued事件,即当有文件添加进来的时候,通过uploader.makeThumb来创建图片预览图。
1 uploader.on( 'fileQueued', function( file ) {
2 var $list = $("#fileList"), //获取文件列表
3 $li = $(
4 '<div id="' + file.id + '" class="file-item thumbnail">' +
5 '<img>' +
6 '<div class="info">' + file.name + '</div>' +
7 '</div>'
8 ),
9 $img = $li.find('img');
10 $list.append( $li ); // $list为容器jQuery实例
11 // 创建缩略图
12 uploader.makeThumb( file, function( error, src ) {
13 if ( error ) {
14 $img.replaceWith('<span>不能预览</span>');
15 return;
16 }
17 $img.attr( 'src', src );//设置预览图
18 }, 100, 100 ); //100x100为缩略图尺寸
19 });
最后是上传状态提示了:
文件上传过程中对应uploadProgress事件。
1 // 文件上传过程中创建进度条实时显示。
2 uploader.on( 'uploadProgress', function( file, percentage ) {
3 var $li = $( '#'+file.id ),
4 $percent = $li.find('.progress span');
5 //避免重复创建
6 if ( !$percent.length ) {
7 $percent = $('<p class="progress"><span></span></p>').appendTo( $li ).find('span');
8 }
9 $percent.css( 'width', percentage * 100 + '%' );
10 });
文件上传成功对应uploadSuccess事件。
1 // 文件上传成功,给item添加成功class, 用样式标记上传成功。
2 uploader.on( 'uploadSuccess', function( file, res ) {
3 console.log(res.filePath);//这里可以得到上传后的文件路径
4 $( '#'+file.id ).addClass('upload-state-done');
5 });
文件上传失败对应uploadError事件。
1 // 文件上传失败,显示上传出错。
2 uploader.on( 'uploadError', function( file ) {
3 var $li = $( '#'+file.id ),
4 $error = $li.find('div.error');
5 // 避免重复创建
6 if ( !$error.length ) {
7 $error = $('<div class="error"></div>').appendTo( $li );
8 }
9 $error.text('上传失败');
10 });
文件上传完成对应uploadComplete事件。
1 // 完成上传,成功或者失败,先删除进度条。
2 uploader.on( 'uploadComplete', function( file ) {
3 $( '#'+file.id ).find('.progress').remove();
4 });
到这里,我们就实现了一个简单的图片上传实例,点击“选择图片”会弹出文件选择对话框,当选择图片后,即进入上传图片流程,会将图片对应的缩略图现实在列表里。
=====================================================================================
接下来贴上文件分片上传的完整代码:
先引入相关js文件:
<script type="text/javascript" src="http://www.qiyinwang.com.cn/js/newperson/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="http://www.qiyinwang.com.cn/js/webuploader/webuploader.nolog.js"></script>
页面引入相关控件:
<div id="uploadBtn" class="btn btn-upload">上传文件</div>
<div id="list" class="upload-box clearfix"></div>
前端js代码:
1 //验证数组方法
2 Array.prototype.contains = function (obj) {
3 var i = this.length;
4 while (i--) {
5 if (this[i] === obj) {
6 return true;
7 }
8 }
9 return false;
10 }
11
12 var arr_md5 = [];//加载页面时,将已上传成功的分片数组化
13 var chunks = 0;//分片总数量,用来上传成功以后校验分片文件总数
14 var repart_chunks = 0;
15 var upload_success = false;
16 var times = 0;
17 var interval = 0;
18
19 //注册组件
20 WebUploader.Uploader.register({
21 'before-send': 'preupload'
22 }, {
23 preupload: function( block ) {
24 var me = this,
25 owner = this.owner,
26 deferred = WebUploader.Deferred();
27 chunks = block.chunks;
28 owner.md5File(block.blob)
29 //及时显示进度
30 .progress(function(percentage) {
31 console.log('Percentage:', percentage);
32 })
33 //如果读取出错了,则通过reject告诉webuploader文件上传出错。
34 .fail(function() {
35 deferred.reject();
36 console.log("==1==");
37 })
38 //md5值计算完成
39 .then(function( md5 ) {
40 if(arr_md5.contains(md5)){//数组中包含+(block.chunk+1)
41 deferred.reject();
42 console.log("分片已上传"+block.file.id);
43 $("#speed_"+block.file.id).text("正在断点续传中...");
44 if(block.end == block.total){
45 $("#speed_"+block.file.id).text("上传完毕");
46 $("#pro_"+block.file.id).css( 'width', '100%' );
47 }else{
48 $("#pro_"+block.file.id).css( 'width',(block.end/block.total) * 100 + '%' );
49 }
50 }else{
51 deferred.resolve();
52 console.log("开始上传分片:"+md5);
53 }
54 });
55 return deferred.promise();
56 }
57 });
58
59 //初始化WebUploader
60 var uploader = WebUploader.create({
61 //swf文件路径
62 //swf: 'http://www.ssss.com.cn/js/webuploader/Uploader.swf',
63 //文件接收服务端。
64 server: '/upload/UploadPhotoSlt?methodName=fileupload&tokenid='+$("#tokenid").val(),
65 //选择文件的按钮,可选。内部根据当前运行是创建,可能是input元素,也可能是flash.
66 pick: '#uploadBtn',
67 //不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
68 resize: false,
69 auto:true,
70 //是否分片
71 chunked :true,
72 //分片大小
73 chunkSize :1024*1024*2,
74 chunkRetry :3,
75 threads :3,//最大并发
76 fileNumLimit :1,
77 fileSizeLimit :1024*1024*1024*1024,
78 fileSingleSizeLimit: 1024*1024*1024,
79 duplicate :true,
80 accept: {
81 title: 'file',
82 extensions: 'jpg,png,ai,zip,rar,psd,pdf,cdr,psd,tif',
83 mimeTypes: '*/*'
84 }
85 });
86
87 //当文件被加入队列之前触发,此事件的handler返回值为false,则此文件不会被添加进入队列。
88 uploader.on('beforeFileQueued',function(file){
89 if(",jpg,png,ai,zip,rar,psd,pdf,cdr,psd,tif,".indexOf(","+file.ext+",")<0){
90 alert("不支持的文件格式");
91 }
92 });
93
94 //当有文件添加进来的时候
95 uploader.on( 'fileQueued', function( file ) {
96 times = 1;
97 var src = "http://www.wodexiangce.cn/images/type/JPG.jpg";
98 if(file.ext == 'jpg'){
99 src = "http://www.wodexiangce.cn/images/type/JPG.jpg";
100 }else if(file.ext == 'png'){
101 src = "http://www.wodexiangce.cn/images/type/PNG.jpg";
102 }else if(file.ext == 'ai'){
103 src = "http://www.wodexiangce.cn/images/type/AI.jpg";
104 }else if(file.ext == 'zip'){
105 src = "http://www.wodexiangce.cn/images/type/ZIP.jpg";
106 }else if(file.ext == 'rar'){
107 src = "http://www.wodexiangce.cn/images/type/RAR.jpg";
108 }else if(file.ext == 'psd'){
109 src = "http://www.wodexiangce.cn/images/type/PSD.jpg";
110 }else if(file.ext == 'pdf'){
111 src = "http://www.wodexiangce.cn/images/type/PDF.jpg";
112 }else if(file.ext == 'cdr'){
113 src = "http://www.wodexiangce.cn/images/type/CDR.jpg";
114 }else if(file.ext == 'tif'){
115 src = "http://www.wodexiangce.cn/images/type/TIF.jpg";
116 }
117 upload_success = false;
118 $("#list").html(
119 '<div class="clearfix"><img src='+src+' width="50px" class="icon-file" ></img>'+
120 '<div class="fl" style="margin-left: 70px;">'+
121 '<p class="speed font-12" id="speed_'+file.id+'">校验文件...</p>'+
122 '<div class="progress">'+
123 '<span id="pro_'+file.id+'" class="progressing"></span>'+
124 '</div>'+
125 '<span class="file-size">'+(file.size/1024/1024).toFixed(2)+'MB</span>'+
126 '<a href="javascript:void(0);" id="stopOretry_'+file.id+'" onclick="stop(\''+file.id+'\');" class="a-pause">暂停</a>'+
127 '</div></div><span class="file-name">'+file.name+'</span><br/>' );
128 //文件开始上传后开始计时,计算实时上传速度
129 interval = setInterval(function(){times++;},1000);
130
131 });
132
133 //上传过程中触发,携带上传进度。文件上传过程中创建进度条实时显示。
134 uploader.on( 'uploadProgress', function( file, percentage ) {
135 var status_pre = file.size*percentage-arr_md5.length*2*1024*1024;
136 if(status_pre<=0){
137 return;
138 }
139 $("#pro_"+file.id).css( 'width', percentage * 100 + '%' );
140 var speed = ((status_pre/1024)/times).toFixed(0);
141 $("#speed_"+file.id).text(speed +"KB/S");
142 if(percentage == 1){
143 $("#speed_"+file.id).text("上传完毕");
144 }
145 });
146
147 //文件上传成功时触发
148 uploader.on( 'uploadSuccess', function( file,response) {
149 console.log("成功上传"+file.name+" res="+response);
150 $.ajax({
151 type:'get',
152 url:'/upload/UploadPhotoSlt',
153 dataType: 'json',
154 data: {
155 methodName:'composeFile',
156 name:file.name,
157 chunks:chunks,
158 tokenid:$("#tokenid").val()
159 },
160 async:false,
161 success: function(data) {
162 console.log("==compose=="+data.status);
163 if(data.status == "success"){
164 upload_success = true;
165 $("#url").val(data.url);
166 console.log(data.url);
167 }else{
168 upload_success = false;
169 alert(data.errstr);
170 }
171 }
172 });
173 });
174
175 //文件上传异常失败触发
176 uploader.on( 'uploadError', function( file,reason ) {
177 console.log("失败"+reason );
178 });
179
180 //某个文件开始上传前触发,一个文件只会触发一次。
181 uploader.on( 'uploadStart', function(file) {
182 console.info("file="+file.name);
183 //分片文件上传之前
184 $.ajax({
185 type:'get',
186 url:'/upload/UploadPhotoSlt',
187 dataType: 'json',
188 data: {
189 methodName:'md5Validation',
190 name:file.name,
191 tokenid:$("#tokenid").val()
192 },
193 async:false,
194 success: function(data) {
195 if(data.md5_arr != ""){
196 arr_md5 = data.md5_arr.split(",")
197 }else{
198 arr_md5 = new Array();
199 }
200 }
201 });
202 });
203
204 //当所有文件上传结束时触发。
205 uploader.on( 'uploadFinished', function() {
206
207 });
208
209 function stop(id){
210 uploader.stop(true);
211 $("#stopOretry_"+id).attr("onclick","retry('"+id+"')");
212 $("#stopOretry_"+id).text("恢复");
213 clearInterval(interval);
214 }
215 function retry(id){
216 uploader.retry();
217 $("#stopOretry_"+id).attr("onclick","stop('"+id+"')");
218 $("#stopOretry_"+id).text("暂停");
219 interval = setInterval(function(){times++;},1000);
220 }
后台Java代码:使用到commons-fileupload-1.1.1.jar
servlet:HttpServletBasic
1 package com.wodexiangce.web.servlet;
2
3 import java.lang.reflect.InvocationTargetException;
4 import java.lang.reflect.Method;
5
6 import javax.servlet.ServletContext;
7 import javax.servlet.ServletException;
8 import javax.servlet.http.HttpServlet;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11
12 import org.springframework.web.context.WebApplicationContext;
13 import org.springframework.web.context.support.WebApplicationContextUtils;
14
15 public class HttpServletBasic extends HttpServlet{
16 private static final long serialVersionUID = 1L;
17
18 protected WebApplicationContext wac = null;
19
20 public void init() throws ServletException {
21 super.init();
22 ServletContext sc = this.getServletContext();
23 wac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
24 }
25
26 /**
27 * 方法转发器,通过方法名调用相应的方法 。
28 * 可以看出,不能分出方法同名而参数不同的方法 。
29 */
30 protected void service(HttpServletRequest request,HttpServletResponse response){
31
32 String methodName = request.getParameter("methodName") ;
33
34 //当前类所有的方法名称
35 Method[] methods = this.getClass().getDeclaredMethods() ;
36 for(Method m:methods){
37 if(m.getName().equals(methodName)){
38 try {
39 m.invoke(this, new Object[]{request,response}) ;
40 } catch (IllegalArgumentException e) {
41 e.printStackTrace();
42 } catch (IllegalAccessException e) {
43 e.printStackTrace();
44 } catch (InvocationTargetException e) {
45 e.printStackTrace();
46 }
47 break ;
48 }
49 }
50 }
51
52 public void destroy() {
53 wac = null;
54 }
55
56 }
servlet:UploadPhotoSlt
1 package com.wodexiangce.web.servlet;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.Date;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9
10 import javax.servlet.http.HttpServletRequest;
11 import javax.servlet.http.HttpServletResponse;
12
13 import net.sf.json.JSONObject;
14
15 import org.apache.commons.fileupload.FileItem;
16 import org.apache.commons.fileupload.FileItemFactory;
17 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
18 import org.apache.commons.fileupload.servlet.ServletFileUpload;
19 import org.apache.commons.lang.math.NumberUtils;
20
21 import com.wodexiangce.persistence.model.WdxcQywFiles;
22 import com.wodexiangce.services.QywFileService;
23 import com.wodexiangce.util.FileUtils;
24
25
26 public class UploadPhotoSlt extends HttpServletBasic{
27
28 private static final long serialVersionUID = 1L;
29
30 /**
31 * 文件上传
32 * @param request
33 * @param response
34 * @throws IOException
35 */
36 @SuppressWarnings({ "unchecked", "deprecation" })
37 public void fileupload(HttpServletRequest request, HttpServletResponse response)throws IOException{
38 try {
39 System.out.println("=================fileupload===================");
40 response.addHeader("Access-Control-Allow-Origin", "*");
41 String useridStr = request.getParameter("tokenid");
42 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){
43 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校验错误'}").toString());
44 return;
45 }
46 long userid = Long.parseLong(useridStr);
47 boolean isMultipart = ServletFileUpload.isMultipartContent(request);
48 if (isMultipart) {
49 FileItemFactory factory = new DiskFileItemFactory();
50 ServletFileUpload upload = new ServletFileUpload(factory);
51
52 // 得到所有的表单域,它们目前都被当作FileItem
53 List<FileItem> fileItems = upload.parseRequest(request);
54
55 String id = "";
56 String fileName = "";
57 // 如果大于1说明是分片处理
58 int chunks = 1;
59 int chunk = 0;
60 FileItem tempFileItem = null;
61 for (FileItem fileItem : fileItems) {
62 if (fileItem.getFieldName().equals("id")) {
63 id = fileItem.getString();
64 } else if (fileItem.getFieldName().equals("name")) {
65 fileName = new String(fileItem.getString().getBytes("ISO-8859-1"), "UTF-8");
66 } else if (fileItem.getFieldName().equals("chunks")) {
67 chunks = NumberUtils.toInt(fileItem.getString());
68 } else if (fileItem.getFieldName().equals("chunk")) {
69 chunk = NumberUtils.toInt(fileItem.getString());
70 } else if (fileItem.getFieldName().equals("file")) {
71 tempFileItem = fileItem;
72 }
73 }
74 System.out.println("id="+id+" filename="+fileName+" chunks="+chunks+" chunk="+chunk+" size="+tempFileItem.getSize());
75 //临时目录用来存放所有分片文件
76 String tempFileDir = getTempFilePath()+ File.separator + userid;
77 File parentFileDir = new File(tempFileDir);
78 if (!parentFileDir.exists()) {
79 parentFileDir.mkdirs();
80 }
81 //分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
82 File tempPartFile = new File(parentFileDir, fileName + "_" + chunk+ ".part");
83
84 String MD5 = FileUtils.copyInputStreamToFile(tempFileItem.getInputStream(),tempPartFile);
85 int count = 0;
86 while(MD5==null&&count<3){
87 MD5 = FileUtils.copyInputStreamToFile(tempFileItem.getInputStream(),tempPartFile);
88 count++;
89 }
90 if(MD5==null){
91 throw new Exception("分片文件:"+chunk+"上传失败");
92 }
93 //分片文件上传成功以后,重命名分片文件,规则:原名之前拼接MD5码
94 tempPartFile.renameTo(new File(parentFileDir, fileName + "_" + chunk+"_"+MD5+ ".part"));
95 System.out.println("MD5="+MD5);
96 response.getWriter().write(JSONObject.fromObject("{\"md5\":\""+MD5+"\"}").toString());
97 }
98 } catch (Exception e) {
99 e.printStackTrace();
100 }
101 }
102
103 /**
104 * 重复验证
105 * @param request
106 * @param response
107 * @throws IOException
108 */
109 public void md5Validation(HttpServletRequest request, HttpServletResponse response)throws IOException{
110 try {
111 System.out.println("=================md5Validation===================");
112 response.addHeader("Access-Control-Allow-Origin", "*");
113 response.setCharacterEncoding("utf-8");
114 String useridStr = request.getParameter("tokenid");
115 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){
116 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校验错误'}").toString());
117 return;
118 }
119 long userid = Long.parseLong(useridStr);
120 String tempFileDir = getTempFilePath()+ File.separator + userid;
121 File parentFileDir = new File(tempFileDir);
122 if (!parentFileDir.exists()) {
123 response.getWriter().write(JSONObject.fromObject("{\"md5_arr\":\"\"}").toString());
124 return;
125 }
126 String fileName = request.getParameter("name");
127 fileName = new String(fileName.getBytes("ISO-8859-1"),"UTF-8");
128 System.out.println("fileName="+fileName);
129
130 StringBuffer sb = new StringBuffer();
131 for(File file:parentFileDir.listFiles()){
132 String name = file.getName();
133 if(name.endsWith(".part") && name.startsWith(fileName)){
134 //此文件有上传记录,解析MD5
135 name = name.substring(name.lastIndexOf("_")+1,name.lastIndexOf(".part"));
136 if(name.length()==32){
137 if(sb.length()>0){
138 sb.append(",");
139 }
140 sb.append(name);
141 }
142 }
143 }
144 response.getWriter().write(JSONObject.fromObject("{\"md5_arr\":\""+sb.toString()+"\"}").toString());
145 } catch (Exception e) {
146 e.printStackTrace();
147 }
148 }
149
150 /**
151 * 文件所有分片上传完毕之后,保存文件数据到数据库
152 * @param request
153 * @param response
154 * @throws IOException
155 */
156 public void composeFile(HttpServletRequest request, HttpServletResponse response)throws IOException{
157 try {
158 System.out.println("=================composeFile===================");
159 response.addHeader("Access-Control-Allow-Origin", "*");
160 response.setCharacterEncoding("utf-8");
161 String useridStr = request.getParameter("tokenid");
162 if(useridStr==null||"".equals(useridStr)||useridStr.length()<3){
163 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'token校验错误'}").toString());
164 return;
165 }
166 long userid = Long.parseLong(useridStr);
167 String tempFileDir = getTempFilePath()+ File.separator + userid;
168 File parentFileDir = new File(tempFileDir);
169 if (!parentFileDir.exists()) {
170 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'目录不存在'}").toString());
171 return;
172 }
173 String fileName = request.getParameter("name");
174 fileName = new String(fileName.getBytes("ISO-8859-1"),"UTF-8");
175 String chunks = request.getParameter("chunks");
176 System.out.println("fileName="+fileName);
177 Map<String,File> chunkFileMap = new HashMap<String, File>();
178
179 String md5 = null;
180 for(File file:parentFileDir.listFiles()){
181 String name = file.getName();
182 if(name.endsWith(".part") && name.startsWith(fileName)){
183 md5 = name.substring(name.lastIndexOf("_")+1,name.lastIndexOf(".part"));
184 System.out.println("md5="+md5);
185 if(md5.length()==32){//完整的分片文件
186 String index = name.replace(fileName, "").replace("_"+md5+".part", "").replace("_", "");
187 chunkFileMap.put(index, file);
188 }
189 }
190 }
191 //分片总数和分片文件数一致,则证明分片文件已完整上传,可以持久化数据
192 if(chunkFileMap.size() == Integer.parseInt(chunks.trim())){
193 System.out.println("===========开始合并文件分片==========");
194 WdxcQywFiles QywFile = new WdxcQywFiles();
195 QywFile.setFilename(fileName);
196 QywFile.setCreatetime(new Date());
197 QywFile.setFilepath("/site/photos/file/"+userid+"/"+fileName);
198 QywFile.setFiledownurl("http://www.sssss.cn/file/"+userid+"/"+fileName);
199
200
201 //AI、PDF、EPS、CDR、PSD、JPG、TIFF
202 if(fileName.toLowerCase().endsWith(".tif")){
203 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/TIF.jpg");
204 }else if(fileName.toLowerCase().endsWith(".jpg")){
205 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/JPG.jpg");
206 }else if(fileName.toLowerCase().endsWith(".psd")){
207 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PSD.jpg");
208 }else if(fileName.toLowerCase().endsWith(".ai")){
209 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/AI.jpg");
210 }else if(fileName.toLowerCase().endsWith(".cdr")){
211 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/CDR.jpg");
212 }else if(fileName.toLowerCase().endsWith(".zip")){
213 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/ZIP.jpg");
214 }else if(fileName.toLowerCase().endsWith(".pdf")){
215 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PDF.jpg");
216 }else if(fileName.toLowerCase().endsWith(".png")){
217 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/PNG.jpg");
218 }else if(fileName.toLowerCase().endsWith(".rar")){
219 QywFile.setFilepreview("http://www.wodexiangce.cn/images/type/RAR.jpg");
220 }
221 QywFile.setUserid(userid);
222 FileUtils.fileCompose(QywFile, chunkFileMap);//合并文件
223 File file = new File(QywFile.getFilepath());
224 if(!file.exists()){
225 System.out.println("文件合并失败:"+QywFile.getFilepath());
226 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'文件合并失败'}").toString());
227 return;
228 }
229
230 //把文件路径保存到数据库
231 QywFileService fileService = (QywFileService)wac.getBean("qywFileService");
232 Long fileid = (Long)fileService.saveQywFile(QywFile);
233 System.out.println("文件保存成功:"+fileid);
234
235 response.getWriter().write(JSONObject.fromObject("{\"status\":\"success\",'url':'"+QywFile.getFiledownurl()+"'}").toString());
236 }else{
237 System.out.println("分片数量不正确,实际分片数量:"+chunkFileMap.size()+" 总分片数量:"+chunks);
238 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'分片数量不正确'}").toString());
239 }
240 } catch (Exception e) {
241 e.printStackTrace();
242 System.err.println("文件合并失败:"+e.getMessage());
243 response.getWriter().write(JSONObject.fromObject("{\"status\":\"error\",'errstr':'文件合并失败'}").toString());
244 }
245 }
246
247 /**
248 * 指定临时目录
249 * @return
250 */
251 private String getTempFilePath(){
252 return "/site/xxxxxx/temp";
253 }
254
255 }
补充:涉及到的实体类和工具类
1 package com.wodexiangce.persistence.model;
2
3 import java.io.Serializable;
4 import java.math.BigDecimal;
5 import java.util.Date;
6
7
8 public class WdxcQywFiles implements Serializable{
9
10 private static final long serialVersionUID = 1L;
11
12 private long id;
13
14 private long userid;
15
16 private String filename;
17
18 private String filepreview;
19
20 private String filepath;
21
22 private int deleted;
23
24 private Date deletedtime;
25
26 private Date createtime;
27
28 private String filedownurl;
29
30 private BigDecimal filesize;
31
32 public long getId() {
33 return id;
34 }
35 public void setId(long id) {
36 this.id = id;
37 }
38 public long getUserid() {
39 return userid;
40 }
41 public void setUserid(long userid) {
42 this.userid = userid;
43 }
44 public String getFilename() {
45 return filename;
46 }
47 public void setFilename(String filename) {
48 this.filename = filename;
49 }
50 public String getFilepreview() {
51 return filepreview;
52 }
53 public void setFilepreview(String filepreview) {
54 this.filepreview = filepreview;
55 }
56 public String getFilepath() {
57 return filepath;
58 }
59 public void setFilepath(String filepath) {
60 this.filepath = filepath;
61 }
62 /**
63 * 删除状态 0:正常 1:已删除
64 * @return
65 */
66 public int getDeleted() {
67 return deleted;
68 }
69 public void setDeleted(int deleted) {
70 this.deleted = deleted;
71 }
72 public Date getDeletedtime() {
73 return deletedtime;
74 }
75 public void setDeletedtime(Date deletedtime) {
76 this.deletedtime = deletedtime;
77 }
78 public Date getCreatetime() {
79 return createtime;
80 }
81 public void setCreatetime(Date createtime) {
82 this.createtime = createtime;
83 }
84 /**
85 * 文件下载URL前缀(需拼写分片名称)
86 * @return
87 */
88 public String getFiledownurl() {
89 return filedownurl;
90 }
91 public void setFiledownurl(String filedownurl) {
92 this.filedownurl = filedownurl;
93 }
94 /**
95 * 文件大小
96 * @return
97 */
98 public BigDecimal getFilesize() {
99 return filesize;
100 }
101 public void setFilesize(BigDecimal bigDecimal) {
102 this.filesize = bigDecimal;
103 }
104 }
1 package com.wodexiangce.util;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileOutputStream;
6 import java.io.InputStream;
7 import java.lang.reflect.Method;
8 import java.math.BigDecimal;
9 import java.nio.MappedByteBuffer;
10 import java.nio.channels.FileChannel;
11 import java.security.AccessController;
12 import java.security.MessageDigest;
13 import java.security.PrivilegedAction;
14 import java.util.Map;
15
16 import com.wodexiangce.persistence.model.WdxcQywFiles;
17
18 /**
19 * 文件处理工具类
20 */
21 public class FileUtils {
22 /**
23 * 保存文件流到文件,同时返回文件MD5
24 * @param inputStream
25 * @param file
26 */
27 public static String copyInputStreamToFile(InputStream inputStream,File file){
28 MessageDigest md = null;
29 try {
30 if(inputStream == null || inputStream == null) {
31 return null;
32 }
33 md = MessageDigest.getInstance("MD5");
34 byte[] b = new byte[102400];//set b 100Kb byte.
35 int n = inputStream.read(b);
36 int totalBytes = n;
37 FileOutputStream fos = new FileOutputStream(file);
38 while (n > -1) {
39 fos.write(b, 0, n);
40 fos.flush();
41 n = inputStream.read(b);
42 totalBytes += n;
43 }
44 fos.close();
45 inputStream.close();
46 System.out.println("文件总大小:"+totalBytes);
47 //生成文件MD5值
48 FileInputStream in = new FileInputStream(file);
49 //文件内存映射,提高读写超大文件可能和速度,但会造成文件锁定不可操作。
50 MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
51 md.update(byteBuffer);
52 clean(byteBuffer);
53
54 byte[] encrypt = md.digest();
55 StringBuffer sb = new StringBuffer();
56 for (int i = 0; i < encrypt.length; i++) {
57 String hex = Integer.toHexString(0xff & encrypt[i]);
58 if (hex.length() == 1)
59 sb.append('0');
60 sb.append(hex);
61 }
62 in.close();
63 return sb.toString();
64 } catch (Exception e) {
65 e.printStackTrace();
66 return null;
67 }
68 }
69
70 /**
71 * 文件合并
72 * @param QywFile
73 * @param chunkFileMap
74 */
75 public static void fileCompose(WdxcQywFiles QywFile,Map<String,File> chunkFileMap){
76 String path = QywFile.getFilepath();
77 File mainFile = new File(path);
78 if(!mainFile.getParentFile().exists()){
79 mainFile.getParentFile().mkdirs();
80 }
81 try {
82 FileChannel out = new FileOutputStream(mainFile).getChannel();
83 FileChannel in = null;
84 long start = System.currentTimeMillis();
85 for(int i=0;i<chunkFileMap.size();i++){
86 File file = chunkFileMap.get(String.valueOf(i));
87 System.out.println("file="+file.getName());
88 in = new FileInputStream(file).getChannel();
89 MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
90 out.write(buf);
91 in.close();
92 FileUtils.clean(buf);
93 }
94 System.out.println("文件大小:"+mainFile.length()/1024/1024+" M");
95 QywFile.setFilesize(new BigDecimal(mainFile.length()));
96 long end = System.currentTimeMillis();
97 System.out.println("常规方法合并耗时:"+(end-start)/1000+" 秒");
98 }catch (Exception e) {
99 e.printStackTrace();
100 }
101 }
102
103
104 @SuppressWarnings("unchecked")
105 public static void clean(final Object buffer) throws Exception {
106 AccessController.doPrivileged(new PrivilegedAction() {
107 public Object run() {
108 try {
109 Method getCleanerMethod = buffer.getClass().getMethod("cleaner",new Class[0]);
110 getCleanerMethod.setAccessible(true);
111 sun.misc.Cleaner cleaner =(sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]);
112 cleaner.clean();
113 } catch(Exception e) {
114 e.printStackTrace();
115 }
116 return null;}
117 });
118 }
119 }