• Java结合WebUploader文件上传(分块、断点续传)附源码下载


    之前自己写小项目的时候也碰到过文件上传的问题,没有找到很好的解决方案。虽然之前网找各种解决方案的时候也看到过WebUploader,但没有进一步深究。这次稍微深入了解了些,这里也做个小结。
    因为有很多人问我要源码,特附上源码下载链接在文章的末尾,供有需要时参考 -- 20171212 更新

    一、 简单的文件和普通数据上传并保存

    jsp页面:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    	<form action="${pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
    		文件:<input type="file" value="请选择文件" name="file" /> <br/>
    		信息:<input type="text" name="info" /> <br/>
    		<input type="submit" value="提交" />
    	</form>
    </body>
    </html>
    

    servlet:

    package com.yihengliu.web.action;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    import org.apache.commons.io.FileUtils;
    
    /**
     * Servlet user to accept file upload
     */
    public class FileUploadServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    
    	private String serverPath = "e:/";
    
    	protected void doGet(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		response.getWriter().append("Served at: ").append(request.getContextPath());
    
    		System.out.println("进入后台...");
    
    		// 1.创建DiskFileItemFactory对象,配置缓存用
    		DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
    
    		// 2. 创建 ServletFileUpload对象
    		ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
    
    		// 3. 设置文件名称编码
    		servletFileUpload.setHeaderEncoding("utf-8");
    
    		// 4. 开始解析文件
    		try {
    			List<FileItem> items = servletFileUpload.parseRequest(request);
    			for (FileItem fileItem : items) {
    
    				if (fileItem.isFormField()) { // >> 普通数据
    					String info = fileItem.getString("utf-8");
    					System.out.println("info:" + info);
    				} else { // >> 文件
    					// 1. 获取文件名称
    					String name = fileItem.getName();
    					// 2. 获取文件的实际内容
    					InputStream is = fileItem.getInputStream();
    
    					// 3. 保存文件
    					FileUtils.copyInputStreamToFile(is, new File(serverPath + "/" + name));
    				}
    
    			}
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    	}
    
    	protected void doPost(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		doGet(request, response);
    	}
    
    }
    

    二 、使用WebUploader组件上传

    主要功能分为:分片、并发,预览、压缩,多途径添加文件夹(文件多选,拖拽等)、秒传

    页面样式使用

    <html>
            <title>使用webuploader上传</title>
            <!-- 1.引入文件 -->
            <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/js/webuploader.css" rel="external nofollow" >
            <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-2.1.4.min.js"></script>
            <script type="text/javascript" src="${pageContext.request.contextPath }/js/webuploader.js"></script>
            </head>
            <body>
             <!-- 2.创建页面元素 -->
             <div id="upload">
              <div id="filePicker">文件上传</div>
             </div>
             <!-- 3.添加js代码 -->
             <script type="text/javascript">
              var uploader = WebUploader.create(
               {
                swf:"${pageContext.request.contextPath }/js/Uploader.swf",
                server:"${pageContext.request.contextPath }/FileUploadServlet",
                pick:"#filePicker",
                auto:true
               }          
              );
             </script>
            </body>
    </html>
    

    生成文件名列表、实时显示上传进度、显示缩略图

    • 增加文件列表div, <div id="fileList"></div>

    • 生成缩略图和显示上传进度

      // 生成缩略图和上传进度
      uploader.on("fileQueued", function(file) {
      		// 把文件信息追加到fileList的div中
      		$("#fileList").append("<div id='" + file.id + "'><img/><span>" + file.name + "</span><div><span class='percentage'><span></div></div>")
      		
      		// 制作缩略图
      		// error:不是图片,则有error
      		// src:代表生成缩略图的地址
      		uploader.makeThumb(file, function(error, src) {
      			if (error) {
      				$("#" + file.id).find("img").replaceWith("<span>无法预览&nbsp;</span>");
      			} else {
      				$("#" + file.id).find("img").attr("src", src);
      			}
      		});
      	}
      );
      
      // 监控上传进度
      // percentage:代表上传文件的百分比
      uploader.on("uploadProgress", function(file, percentage) {
      	$("#" + file.id).find("span.percentage").text(Math.round(percentage * 100) + "%");
      });
      

    拖拽上传、粘贴上传

    • 创建拖拽区域并设置样式:
        <style type="text/css">
          #dndArea {
        		 200px;
        		height: 100px;
        		border-color: red;
        		border-style: dashed;
        	}
        </style>        
    
        <!-- 创建用于拖拽的区域 -->
    	<div id="dndArea"></div>
    
    • 基本配置中增加dnd区域配置(开启拖拽)
      屏蔽拖拽区域外的响应
      开启粘贴功能
          var uploader = WebUploader.create(
    		{
    			swf:"${pageContext.request.contextPath }/js/Uploader.swf",
                     server:"${pageContext.request.contextPath }/FileUploadServlet",
    			pick:"#filePicker",
    			auto:true,
    			// 开启拖拽
    			dnd:"#dndArea",
    			// 屏蔽拖拽区域外的响应
    			disableGlobalDnd:true,
    			// 
    		}		
    	);
    

    文件的分块上传

    前端根据需要发送的文件生成一个md5字符串发送给后台,后台创建以该md5字符串命名的文件夹。前端分块发送文件并发送文件块序号给后台,后台接收到文件后按序号名称保存。前端发送完成后通知后台合并文件。

    • 前端配置,开启是否分块、分块大小、线程个数等

      // 上传基本配置
      var uploader = WebUploader.create(
      	{
      		swf:"${pageContext.request.contextPath }/js/Uploader.swf",
      		server:"${pageContext.request.contextPath }/FileUploadServlet",
      		pick:"#filePicker",
      		auto:true,
      		dnd:"#dndArea",
      		disableGlobalDnd:true,
      		paste:"#uploader",
      		
      		// 分块上传设置
      		// 是否分块
      		chunked:true,
      		// 每块文件大小(默认5M)
      		chunkSize:5*1024*1024,
      		// 开启几个并非线程(默认3个)
      		threads:3,
      		// 在上传当前文件时,准备好下一个文件
      		prepareNextFile:true
      	}		
      );
      
    • 前端监听分块
      可以分为三个时间点:

      • before-send-file: 该方法在文件上传前调用(只会在一个文件上传前调用)。
        可以在该方法中获取文件的md5字符串作为后台保存分块文件的目录名
      • before-send: 该方法在每个分块文件上传前调用(每个分块上传前都会调用)。
        可以在该方法中发送md5字符串到后台,后台判断是否已经存在分块决定是否发送以达到断点续传的功能
      • after-send-file: 该方法在所有文件上传完成没有错误之后调用(所有分块上传完成后调用)。
        可以在该方法中通知后台合并所有分块
    1. 前端获取文件md5字符串,发送每个分块时发送到后台,后台接收如果不存在文件夹创建文件夹,保存分块发送的文件
    ```javascript
    // 监听分块上传的时间点,断点续传
    var fileMd5;
    WebUploader.Uploader.register({
    	"before-send-file":"beforeSendFile",
    	"before-send":"beforeSend",
    	"after-send-file":"afterSendFile"
    	},{
    		beforeSendFile:function(file) {
    			// 创建一个deffered,用于通知是否完成操作
    			var deferred = WebUploader.Deferred();
    			
    			// 计算文件的唯一标识,用于断点续传和妙传
    			(new WebUploader.Uploader()).md5File(file, 0, 5*1024*1024)
    				.progress(function(percentage){
    					$("#"+file.id).find("span.state").text("正在获取文件信息...");
    				})
    				.then(function(val) {
    					fileMd5 = val;
    					$("#" + file.id).find("span.state").text("成功获取文件信息");
    					// 放行
    					deferred.resolve();
    				});
    			// 通知完成操作
    			return deferred.promise();
    		},
    		beforeSend:function() {
    			var deferred = WebUploader.Deferred();
    			// 发送文件md5字符串到后台
    			this.owner.options.formData.fileMd5 = fileMd5;
    			deferred.resolve();
    			return deferred.promise();
    		},
    		afterSendFile:function() {
    			
    		}
    	}
    );
    ```
    
    1. 添加state标签
    ```javascript
    $("#fileList").append("<div id='" + file.id + "'><img/><span>" + file.name + "</span><div><span class='state'></span></div><div><span class='percentage'></span></div></div>");
    ```
    
    1. 保存文件
    ```java
    // 4. 开始解析文件
    // 文件md5获取的字符串
    String fileMd5 = null;
    // 文件的索引
    String chunk = null;
    try {
              List<FileItem> items = servletFileUpload.parseRequest(request);
    		for (FileItem fileItem : items) {
    
    			if (fileItem.isFormField()) { // >> 普通数据
    				String fieldName = fileItem.getFieldName();
    				if ("info".equals(fieldName)) {
    					String info = fileItem.getString("utf-8");
    					System.out.println("info:" + info);
    				}
    				if ("fileMd5".equals(fieldName)) {
    					fileMd5 = fileItem.getString("utf-8");
    					System.out.println("fileMd5:" + fileMd5);
    				}
    				if ("chunk".equals(fieldName)) {
    					chunk = fileItem.getString("utf-8");
    					System.out.println("chunk:" + chunk);
    				}
    			} else { // >> 文件
    				// 如果文件夹没有创建文件夹
    				File file = new File(serverPath + "/" + fileMd5);
    				if (!file.exists()) {
    					file.mkdirs();
    				}
    				// 保存文件
    				File chunkFile = new File(serverPath + "/" + fileMd5 + "/" + chunk);
    				FileUtils.copyInputStreamToFile(fileItem.getInputStream(), chunkFile);
    			}
    		}
    ```    
    
    1. 前端通知action进行合并文件
      前端增加:
    ```javascript
    // 通知合并分块
    $.ajax(
        {
    		type:"POST",
    		url:"${pageContext.request.contextPath}/UploadActionServlet?action=mergeChunks",
    		data:{
    			fileMd5:fileMd5
    		},
    		success:function(response){
    			
    		}
    	}
    );
    ```
    
    1. 新增合并action:
    ```java
    package com.yihengliu.web.action;
    
    import java.io.File;
    import java.io.FileFilter;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.channels.FileChannel;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import java.util.UUID;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 合并上传文件
     */
    public class UploadActionServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    
    	private String serverPath = "e:/";
    
    	protected void doGet(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		System.out.println("进入合并后台...");
    		String action = request.getParameter("action");
    		if ("mergeChunks".equals(action)) {
    			// 获得需要合并的目录
    			String fileMd5 = request.getParameter("fileMd5");
    
    			// 读取目录所有文件
    			File f = new File(serverPath + "/" + fileMd5);
    			File[] fileArray = f.listFiles(new FileFilter() {
    
    				// 排除目录,只要文件
    				@Override
    				public boolean accept(File pathname) {
    					if (pathname.isDirectory()) {
    						return false;
    					}
    					return true;
    				}
    			});
    
    			// 转成集合,便于排序
    			List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));
    			// 从小到大排序
    			Collections.sort(fileList, new Comparator<File>() {
    
    				@Override
    				public int compare(File o1, File o2) {
    					if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
    						return -1;
    					}
    					return 1;
    				}
    
    			});
    
    			// 新建保存文件
    			File outputFile = new File(serverPath + "/" + UUID.randomUUID().toString() + ".zip");
    
    			// 创建文件
    			outputFile.createNewFile();
    
    			// 输出流
    			FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
    			FileChannel outChannel = fileOutputStream.getChannel();
    
    			// 合并
    			FileChannel inChannel;
    			for (File file : fileList) {
    				inChannel = new FileInputStream(file).getChannel();
    				inChannel.transferTo(0, inChannel.size(), outChannel);
    				inChannel.close();
    
    				// 删除分片
    				file.delete();
    			}
    
    			// 关闭流
    			fileOutputStream.close();
    			outChannel.close();
    
    			// 清除文件加
    			File tempFile = new File(serverPath + "/" + fileMd5);
    			if (tempFile.isDirectory() && tempFile.exists()) {
    				tempFile.delete();
    			}
    
    			System.out.println("合并文件成功");
    
    		}
    	}
    
    	protected void doPost(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		doGet(request, response);
    	}
    
    }
    ```
    

    断点续传

    • 前端页面发送前添加校验,校验是否已经上传分块

      beforeSend:function(block) {
        var deferred = WebUploader.Deferred();
        
        // 支持断点续传,发送到后台判断是否已经上传过
        $.ajax(
          {type:"POST",
          url:"${pageContext.request.contextPath}/UploadActionServlet?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();
            }
          }
        }
        );
      
        // 发送文件md5字符串到后台
        this.owner.options.formData.fileMd5 = fileMd5;
        return deferred.promise();
      }
      
    • action中添加校验

       else if ("checkChunk".equals(action)) {
      		// 校验文件是否已经上传并返回结果给前端
      
      		// 文件唯一表示								
      		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}");
      		}
      	}
      

    源码下载

    源码点击下载

  • 相关阅读:
    安卓系统
    移动互联网产品设计的原则
    Java 笔记10
    Get Android Source Code
    Spinner Animation
    Java 笔记09
    Java 笔记07
    Java 笔记08
    例题
    例题
  • 原文地址:https://www.cnblogs.com/liuchengcc/p/6643884.html
Copyright © 2020-2023  润新知