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 {
    		width: 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}");
    		}
    	}
    

源碼下載

源碼點擊下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM