SSM框架+Plupload實現(xiàn)分塊上傳大文件示例
關(guān)于Plupload的介紹,相信它的官網(wǎng)http://www.plupload.com/已經(jīng)給得很詳細了。Plupload的上傳原理簡單點說,就是將用戶選中的文件(可多個)分隔成一個個小塊,依次向服務(wù)器上傳,這是它能駕馭上傳大文件的原因之一,而且在這個過程可以暫停上傳,暫停后再繼續(xù)上傳(異于斷點續(xù)傳)。最重要的是,從頭到尾沒有一點點UI阻塞,保證了用戶體驗。下面會開始講Plupload的實現(xiàn)流程,分析原理,并在最后給出效果圖。
在此之前先說說我的項目,做的j2ee項目運用到spring+SpringMVC+MyBatis的框架集合,是關(guān)于一個社交平臺的網(wǎng)站,類似于facebook,twitter,微博等,起了一個名字叫YouAndMe。我大膽地構(gòu)想了這個項目應(yīng)該有一個用戶資料共享的平臺,或是一部好看的電影,或是一套電視劇,或是居家必備的食譜,也有可能是好看的風景圖,各式各樣。用戶可以搜索想要的資料并下載。因此首先要解決的就是各式各樣(大)文件的上傳。
一:下載Plupload插件并引入相應(yīng)文件
值得一提的是這個插件只是前端的,后臺怎么獲取怎么將一個個小塊合并等代碼是要自己寫的。
下載地址:http://www.plupload.com/download,我下的是Plupload 2.1.9 GPLv2版本的,里面有要用到的css以及js。
在項目中需要引入:jQuery.plupload.queue.css,jquery-2.0.0.min.js,plupload.full.min.js,jquery.plupload.queue.js,zh_CN.js這些文件。
二:前端準備
1.首先在html中寫入如下代碼:
<div id="uploader"> <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p> </div> <button id="toStop">暫停一下</button> <button id="toStart">再次開始</button>
注意div的id必須是uploader,這在插件源碼里是有規(guī)定的;id為toStop與toStart的按鈕是我自己加的,目的是為了實現(xiàn)暫停上傳與暫停過后的繼續(xù)上傳。
2.頁面加載后通過js初始化組件
<script type="text/javascript">
$(function() {
// Initialize the widget when the DOM is ready
var uploader = $("#uploader").pluploadQueue({
// General settings
runtimes: 'html5,flash,silverlight,html4',
url: "../pluploadUpload",
// Maximum file size
max_file_size: '10000mb',
chunk_size: '1mb',
// Resize images on clientside if we can
resize: {
width: 200,
height: 200,
quality: 90,
crop: true // crop to exact dimensions
},
// Specify what files to browse for
filters: [
{title: "Image files", extensions: "jpg,gif,png"},
{title: "Vedio files", extensions: "mp4,mkv"},
{title: "Zip files", extensions: "zip,avi"}
],
// Rename files by clicking on their titles
rename: true,
// Sort files
sortable: true,
// Enable ability to drag'n'drop files onto the widget (currently only HTML5 supports that)
dragdrop: true,
// Views to activate
views: {
list: true,
thumbs: true, // Show thumbs
active: 'thumbs'
},
// Flash settings
flash_swf_url: 'js/Moxie.swf',
// Silverlight settings
silverlight_xap_url: 'js/Moxie.xap'
});
$("#toStop").on('click', function () {
uploader.stop();
});
$("#toStart").on('click', function () {
uploader.start();
});
});
</script>
關(guān)于這部分的功能可以查看pluploadQueue的文檔:http://www.plupload.com/docs/pluploadQueue。也很容易看懂,這里簡單地說說部分參數(shù)的意義。
url就是服務(wù)器處理該上傳的地址。filters是過濾器的意思,規(guī)定哪些格式的文件可以上傳。dragdrop:true設(shè)置了可以拖拽文件至選定框。
注意:在暫停與繼續(xù)上傳時要用到uploader.stop()與uploader.start(),這個uploader實例由$("#uploader").pluploadQueue({...})時產(chǎn)生。在官網(wǎng)給出的例子中有兩種情況:一是注冊時一次性全部給定參數(shù),但是這樣是不會返回一個uploader實例的;二是注冊時不給參數(shù),會返回uploader實例,再對這個uploader實例綁定事件時一步步給出參數(shù)。但很明顯我這里給定了參數(shù)又同時返回了一個uploader實例,只要修改一個源碼:打開jquery.plupload.queue.js源碼找到定義pluploadQueue這塊,將if (settings) 內(nèi)的返回return this,改成return uploaders[$(this[0]).attr('id')]。這樣,點擊暫停按鈕時,當前上傳會暫停,點擊開始按鈕時,又繼續(xù)。
三:Controller映射
@Autowired
private PluploadService pluploadService;
/**Plupload文件上傳處理方法*/
@RequestMapping(value="/pluploadUpload")
public void upload(Plupload plupload,HttpServletRequest request,HttpServletResponse response) {
String FileDir = "pluploadDir";//文件保存的文件夾
plupload.setRequest(request);//手動傳入Plupload對象HttpServletRequest屬性
int userId = ((User)request.getSession().getAttribute("user")).getUserId();
//文件存儲絕對路徑,會是一個文件夾,項目相應(yīng)Servlet容器下的"pluploadDir"文件夾,還會以用戶唯一id作劃分
File dir = new File(request.getSession().getServletContext().getRealPath("/") + FileDir+"/"+userId);
if(!dir.exists()){
dir.mkdirs();//可創(chuàng)建多級目錄,而mkdir()只能創(chuàng)建一級目錄
}
//開始上傳文件
pluploadService.upload(plupload, dir);
}
在這里,我規(guī)定不同用戶上傳的資料會根據(jù)唯一id分開不同的文件夾,基于注釋代碼很容易看懂,你或許會困惑于Plupload與PluploadService,下面就會給出。
四:Plupload類
package web.plupload;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* Plupload實體類固定格式,屬性名不可修改
* 因為MultipartFile要用到Spring web的依賴,而該依賴在web模塊中才引入,所以不把該實體類放在entity模塊
*/
public class Plupload {
/**文件原名*/
private String name;
/**用戶上傳資料被分解總塊數(shù)*/
private int chunks = -1;
/**當前塊數(shù)(從0開始計數(shù))*/
private int chunk = -1;
/**HttpServletRequest對象,不會自動賦值,需要手動傳入*/
private HttpServletRequest request;
/**保存文件上傳信息,不會自動賦值,需要手動傳入*/
private MultipartFile multipartFile;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChunks() {
return chunks;
}
public void setChunks(int chunks) {
this.chunks = chunks;
}
public int getChunk() {
return chunk;
}
public void setChunk(int chunk) {
this.chunk = chunk;
}
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public MultipartFile getMultipartFile() {
return multipartFile;
}
public void setMultipartFile(MultipartFile multipartFile) {
this.multipartFile = multipartFile;
}
}
再次提醒類名與屬性名不可隨意改變。通過規(guī)定好的正確的屬性名,客戶端通過Http發(fā)送塊文件至服務(wù)端Controller,得到的plupload對象才能傳入正確的文件信息。
關(guān)于屬性的說明代碼注釋已經(jīng)說得很清楚了,可以自行研究學習。
五:PluploadService類
package web.plupload;
import entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.io.*;
import java.sql.Timestamp;
import java.util.Iterator;
import java.util.List;
import service.youandmeService;
/**
* Plupload Service模塊,同Plupload實體類一樣,因為要用到Spring web相關(guān)依賴,所以不將其放在Service模塊
*/
@Component //將寫好的類注入SpringIOC容器中讓Controller自動裝載
public class PluploadService {
@Autowired
private youandmeService youandmeService;
public void upload(Plupload plupload,File pluploadDir){
String fileName = ""+System.currentTimeMillis()+plupload.getName();//在服務(wù)器內(nèi)生成唯一文件名
upload(plupload, pluploadDir, fileName);
}
private void upload(Plupload plupload,File pluploadDir,String fileName){
int chunks = plupload.getChunks();//用戶上傳文件被分隔的總塊數(shù)
int nowChunk = plupload.getChunk();//當前塊,從0開始
//這里Request請求類型的強制轉(zhuǎn)換可能出錯,配置文件中向SpringIOC容器引入multipartResolver對象即可。
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest)plupload.getRequest();
//調(diào)試發(fā)現(xiàn)map中只有一個鍵值對
MultiValueMap<String,MultipartFile> map = multipartHttpServletRequest.getMultiFileMap();
if(map!=null){
try{
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
List<MultipartFile> multipartFileList = map.get(key);
for(MultipartFile multipartFile:multipartFileList){//循環(huán)只進行一次
plupload.setMultipartFile(multipartFile);//手動向Plupload對象傳入MultipartFile屬性值
File targetFile = new File(pluploadDir+"/"+fileName);//新建目標文件,只有被流寫入時才會真正存在
if(chunks>1){//用戶上傳資料總塊數(shù)大于1,要進行合并
File tempFile = new File(pluploadDir.getPath()+"/"+multipartFile.getName());
//第一塊直接從頭寫入,不用從末端寫入
savePluploadFile(multipartFile.getInputStream(),tempFile,nowChunk==0?false:true);
if(chunks-nowChunk==1){//全部塊已經(jīng)上傳完畢,此時targetFile因為有被流寫入而存在,要改文件名字
tempFile.renameTo(targetFile);
//每當文件上傳完畢,將上傳信息插入數(shù)據(jù)庫
Timestamp now = new Timestamp(System.currentTimeMillis());
youandmeService.uploadInfo(fileName,((User)(plupload.getRequest().getSession().getAttribute("user"))).getUsername(),now);
}
}
else{
//只有一塊,就直接拷貝文件內(nèi)容
multipartFile.transferTo(targetFile);
//每當文件上傳完畢,將上傳信息插入數(shù)據(jù)庫
Timestamp now = new Timestamp(System.currentTimeMillis());
youandmeService.uploadInfo(fileName, ((User) (plupload.getRequest().getSession().getAttribute("user"))).getUsername(), now);
}
}
}
}
catch (IOException e){
e.printStackTrace();
}
}
}
private void savePluploadFile(InputStream inputStream,File tempFile,boolean flag){
OutputStream outputStream = null;
try {
if(flag==false){
//從頭寫入
outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
}
else{
//從末端寫入
outputStream = new BufferedOutputStream(new FileOutputStream(tempFile,true));
}
byte[] bytes = new byte[1024];
int len = 0;
while ((len = (inputStream.read(bytes)))>0){
outputStream.write(bytes,0,len);
}
}
catch (FileNotFoundException e){
e.printStackTrace();
}
catch (IOException e){
e.printStackTrace();
}
finally {
try{
outputStream.close();
inputStream.close();
}
catch (IOException e){
e.printStackTrace();
}
}
}
}
1.PluploadService這個類名是我自己起的,還可以吧~
2.在Controller的最后一行PluploadService.upload(plupload, dir);中將客戶端提交至服務(wù)器生成的plupload對象與規(guī)定保存的文件夾目錄,以參數(shù)的形式傳入PluploadService的upload方法中。
3.在upload(Plupload plupload,File pluploadDir)方法中,為文件生成一個唯一的文件名以便存儲在服務(wù)器中。
4.chunks是用戶一次性選中要上傳的文件中當前文件被分隔后的總塊數(shù);nowChunk是這次上傳中塊的編號,從0開始,為什么用”這次“呢?前面提到過Plupload就是依次地將塊從客戶端提交至服務(wù)器,因此在文件上傳中,會有很多次Http請求,而同一個文件的chunks是不變的,nowChunk會一次次增加。
5.將HttpServletRequest強制轉(zhuǎn)換為MultipartHttpServletRequest時可能會出錯,但這個錯誤可以避免,只需在SpringIOC容器中注入一個名為multipartResolver的對象
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- set the max upload size100MB -->
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="maxInMemorySize">
<value>4096</value>
</property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
6.通過MultipartHttpServletRequest拿到MultiValueMap(經(jīng)過調(diào)試發(fā)現(xiàn)這個map只有一對鍵值對),其Value類型為MultipartFile,這個MultipartFile其實就是當前的塊,也難怪為什么map中只有一個鍵值對了。
7.plupload.setMultipartFile(multipartFile);手動為plupload對象傳入MultipartFile屬性值
8.如果總塊數(shù)chunks大于1,那就要考慮將上傳過來的一個個小塊合成一個文件,否則那就直接拷貝塊文件到目標文件multipartFile.transferTo(targetFile);
9.在chunks大于1時,首先要新建一個臨時文件tempFile,用于不斷不斷將一個個小塊寫入這個tempFile,等寫完后(chunks-nowChunk==1),就將其重命名(tempFile.renameTo(targetFile);)。
10.savePluploadFile(multipartFile.getInputStream(),tempFile,nowChunk==0?false:true);方法用于合并一個個小塊文件,如果是第一塊的話,就從頭開始寫入(new FileOutputStream(tempFile)),否則全部從末端寫入(new FileOutputStream(tempFile,true))。
寫到這里,基于Plupload實現(xiàn)斷點續(xù)傳的代碼已經(jīng)全部給出了,大家要自己整合至項目中,這里沒有給出完整的demo,嗯還是那句話,授之于魚不如授之以漁。
上傳項目效果圖:
1.選定上傳文件:

2.開始上傳,有進度條顯示:

3.暫停上傳:

4.暫停后繼續(xù)上傳:

5.上傳完畢:

6.目標文件夾下有相應(yīng)的文件:

7.上傳過程中的網(wǎng)絡(luò)請求,體現(xiàn)分塊上傳:

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用BigDecimal進行精確運算(實現(xiàn)加減乘除運算)
這篇文章主要介紹了如何使用BigDecimal進行精確運算,最后提供了一個工具類,該工具類提供加,減,乘,除運算2013-11-11
java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法
這篇文章主要介紹了java獲取新insert數(shù)據(jù)自增id的實現(xiàn)方法,在具體生成id的時候,我們的操作順序一般是:先在主表中插入記錄,然后獲得自動生成的id,以它為基礎(chǔ)插入從表的記錄,需要的朋友可以參考下2019-06-06
java通過模擬post方式提交表單實現(xiàn)圖片上傳功能實例
這篇文章主要介紹了java通過模擬post方式提交表單實現(xiàn)圖片上傳功能實例,涉及Java針對表單的提交操作響應(yīng)及文件傳輸?shù)南嚓P(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Spring Boot利用Docker快速部署項目的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring Boot利用Docker快速部署項目的完整步驟,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Spring Boot具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-07-07
springboot lua檢查redis庫存的實現(xiàn)示例
本文主要介紹了springboot lua檢查redis庫存的實現(xiàn)示例,為了優(yōu)化性能,通過Lua腳本實現(xiàn)對多個馬戲場次下的座位等席的庫存余量檢查,感興趣的可以了解一下2024-09-09

