內容屬原創,轉載請注明出處
為什么做這個東東
項目中需要用到一個多附件上傳的控件,找了一圈沒找到中意的(唯一一個中意點的還不開源,費用比較高),這不,只得自己掄刀上了。
需求是什么
這么個上傳的東東,要做哪些事情呢?
必須要干的事情:
1. 不能太丑,可以很素。
原生的input file實在和項目主體不太搭配,需要另外想辦法。
2. 需要支持上傳多個附件,比如后台有個字段叫做 影像資料,這個影像資料,也許就是一張正面照,也許,還有一堆的證件照,需要支持多個。
3. 需要一個頁面上支持多個這樣的控件,今天有個影像資料,明天可能就出來一個 資質證書,這不,就得倆了。
4. 需要能查列表,上傳、下載、刪除
最好有的內容:
5. 實時進度條
6. 圖片類型的附件可以預覽。
7. 可以限制上傳的附件類型。
8. 支持配置上傳單個或者多個
上面這些東西,一個一個來唄。
怎么開工
做這么個東東,至少要涉及到兩塊內容:
1. 前端展示
2. 后端處理
既然咱項目用了spring,用了jquery,那么,就從這兩玩意入手唄,於是,決定這樣干:
1. 前端自定義一個jquery插件
2. 后台基於springMVC和commons-fileupload-1.2.1.jar實現上傳的具體業務
閑話莫提,開始捉刀。
前端插件的那些事
jquery是個好東東,要搞個插件有非常具體的套路,直接往上面套就好了,關鍵在於,邏輯怎么實現?
以前的項目用的是flash的插件,如今既然不想再依賴flash,那么,就用form提交來搞吧,步驟變成了這樣子的:
1. 初始化時調用后台query接口,生成列表,列表上支持下載和刪除。
2. 初始化時生成一個類型為 file的input,並綁定一個change事件 callback
3. 在callback中,動態生成一個form和一個iframe,並且把原來的file移到新的form中,另外再生成一個file放到原來的位置。把該form扔到隊列
4. 如果當前沒有提交的任務存在,那么生成一個提交的任務。
提交的任務中又在干嘛呢?
主要有下面幾個事情要做:
1. 當前有沒有正在提交還沒提交成功的form?如果有,繼續下面的步驟,如果沒有,從隊列中拿一個未提交的form,提交,如果隊列空了,任務結束。
2. 當前提交的form的目的地(iframe)的內容有沒有發生改變(通過檢測某個具體的dom)?如果發生了改變,說明提交已經結束,進入結束的處理。如果沒發生改變,那么,取進度條吧。
2.1 提交已經結束,看下,成功還是失敗?成功了,設置進度條,生成刪除按鈕,再從第一個步驟開始。失敗了,提示下上傳失敗。
2.2 提交還未結束,調用接口取下進度吧,同時,過個500毫秒從第一個步驟開始再來一次。
嗯,前端所有的邏輯基本上都在這里了。
后端的那些事
既然用了commons-fileupload-1.2.1.jar,那么只要做如下事情:
1. 新增類UploadProgressListener實現ProgressListener接口,在該接口的實現類中,首先從url里根據規則解析出來uploadId參數,然后往session例如該uploadId對應的進度。
session里存的是個map,key為uploadId,value為進度值。
2. 繼承 CommonsMultipartResolver 實現一個類xx.xx.xx.CommonsMultipartResolver,在該實現類中通過類似下面代碼注入進度條監聽:
String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); ProgressListener pListener = new UploadProgressListener(request); fileUpload.setProgressListener(pListener);
3. 在spring的配置文件中配置multipartResolver為上面步驟中新實現的類:
<bean id="multipartResolver" class="xx.xx.xx.CommonsMultipartResolver" p:defaultEncoding="UTF-8" >
嗯,差不多就這樣了。只是,這里面有個比較大的坑:
在 UploadProgressListener 這個類中拿到的request的parameter一直是空的。。要等這個步驟做完之后request的參數才有值,這樣,支持多個附件上傳的那個參數uploadId怎么樣都拿不到。。最后,幸好MVC支持在url里帶參數,於是上傳的url就變成了:
@RequestMapping(value="/file/{uploadId}/upload.json")
這樣,可以通過規則在request中拿到getRequestURI()之后再解析出來uploadId的值
差不多可以收工了。
寫在題外的
用的時候,還要注意什么下面這么些東西:
1. 后台存儲附件需要這么張關系表,里面保存了 file_group_id,file_id,file_name,file_path,file_size 至少這么些數據。
2. 控件基於jquery 1.7.2,未測試其他版本
3. 配置控件 上傳、新增、刪除、下載以及獲取進度條的action時,注意需要相對工程的根目錄配置,前面不要帶 /
4. 控件的action需要返回json數據,需要注意如下內容:
a. 新增返回的json為如下格式,至少需要返回fileId和fileGroupId字段:
{model_list:[{"file_group_id":"XXXXX","file_id":"XXXXXXX"}]}
同時,注意返回時,需要設置返回數據的頭部信息為 HTML,java中為:
response.setContentType("text/html;charset=UTF-8");
b. 刪除時后台通過參數 file_id 接收要刪除的 文件編號,不拋異常則認為是刪除成功
c. 下載時后台通過參數 file_id 接收要下載的文件編號,返回文件流
d. 獲取文件列表時,后台通過url里的參數 fileGroupId查詢該組號下的所有附件,的返回json數據為:
{model_list:[{file_group_id:'XX',file_id:'XXX1',file_name:'XX文件',file_path:'test/test/XX文件_20140810010101.html',file_size:1001},{file_group_id:'XX',file_id:'XXX2',file_name:'XX文件',file_path:'test/test/XX文件_20140810010102.html',file_size:1001}]}
e. 獲取文件上傳進度,返回的格式為json格式:
{"percent":10}
注意,為了支持多個文件上傳讀取進度,每一個文件上傳時有一個唯一的 uploadId,獲取文件上傳進度需要根據該參數進行,提交時的參數名為 upload_id
下載
這個fileupload的前端插件的地址已經放在github上,路徑:
https://github.com/kevin82008/fileupload
效果圖:

水平所限,如有不對,歡迎拍磚。
