struts提供的上傳文件功能,文件太大就報錯。
需要解決超大文件(幾個G)上傳的問題。斷點續傳的功能。
技術選型,查百度之后。最終選擇了這個stream插件。
stream官方資料地址:http://www.twinkling.cn/
去下載源碼。源碼地址。
項目源碼托管在git.oschina.net,地址:http://git.oschina.net/jiangdx/stream
解壓安裝包之后,在eclipse中導入此項目。(注,這是一個Maven項目,服務器用的是tomcat 不是jetty)
(如果沒有maven項目選項,eclipse需要安裝maven插件,詳情見其他文章)
如下圖配置 maven命令, 點擊Run 就會啟動項目
日志可以看到[INFO] Running war on http://localhost:8080/stream
瀏覽器地址:http://localhost:8080/stream 就可以訪問項目根目錄(src/main/webapp目錄)
比如訪問 webapp目錄中的s1.html
http://localhost:8080/stream/s1.html
就可以看到示例了
下一步:如何整合到我自己的項目中(項目使用的是SSH)
借鑒了方塊糖的頁面。方塊糖這個是整合到SpringMVC的。而不是SSH的
首先,選定一個樣式,我選擇的是如下的樣式,在這個基礎上進行更改。命名為streamDemo.jsp (html轉為jsp格式,你可以在你項目中復制一個jsp的文件,然后將代碼選擇性的復制過來)
streamDemo.jsp借鑒了方塊糖的頁面,將頁面代碼拷貝過來。拷貝地址:http://www.fangkuaitang.net/?p=2164
此時,需要導入兩個文件:
這個頁面可以看到,需要引用的文件stream-v1.css 和 stream-v1.js
(在Maven項目源碼中自己找一下。然后復制到自己的項目中的對應位置)
<link type="text/css" rel="stylesheet" href="${ctx}/css/stream-v1.css" />
<script type="text/javascript" src="${ctx}/javascript/stream-v1.js"></script>
${ctx}是項目的根目錄。請根據自己項目的實際的文件位置,相應更改,以保證streamDemo.jsp中可以引用到。
這個streamDemo.jsp中最重要的兩個url
tokenURL : "${ctx}/servlet/tk.servlet", /** 根據文件名、大小等信息獲取Token的URI(用於生成斷點續傳、跨域的令牌) */
uploadURL : "${ctx}/servlet/upload.servlet", /** HTML5上傳的URI */
這個Maven項目源碼,后台使用的是servlet 而我們項目SSH使用的是struts
這里就涉及到了,servlet和struts的共存問題。
!!此時先將后台代碼加入到自己的項目中,在stream的Maven項目中,直接將cn.twinkling.stream包下的所有文件拷貝到自己的項目中,放置到src中。
為了方便,包的名字也沒有更改。
此時還需要引入必須的jar包
引入相關依賴包: commons-fileupload-1.3.jar
commons-io-2.2.jar
json-20090211.jar(這個json可能還需要其他的jar包,如果報錯,百度一下,加上即可。)
引入 stream-*.jar
(可以從maven的本地倉庫中獲取到jar包
C:\Users\Administrator\.m2\repository 中找)
實在找不到的,直接百度找。。。。
到此為止,前台頁面有了。后台代碼也有了。Jar包已經buildpath 完畢。
是時候配置web.xml,打通前台和后台了。
前面提到:這個Maven項目源碼,后台使用的是servlet 而我們項目SSH使用的是struts
這里就涉及到了,servlet和struts的共存問題。
http://blog.csdn.net/huilangeliuxin/article/details/10495403
使用第一種方式。方式一:修改servlet的相關配置,統一在servlet后面加上“.servlet”
借鑒stream Maven項目源碼中的web.xml中的配置。對應修改自己項目的web.xml
在其中加上。
<!-- fileUpload --> <servlet> <servlet-name>TokenServlet</servlet-name> <servlet-class>cn.twinkling.stream.servlet.TokenServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>TokenServlet</servlet-name> <url-pattern>/servlet/tk.servlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>StreamServlet</servlet-name> <servlet-class>cn.twinkling.stream.servlet.StreamServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>StreamServlet</servlet-name> <url-pattern>/servlet/upload.servlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>FormDataServlet</servlet-name> <servlet-class>cn.twinkling.stream.servlet.FormDataServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>FormDataServlet</servlet-name> <url-pattern>/servlet/fd.servlet</url-pattern> </servlet-mapping> <!-- fileUpload -->
再修改streamDemo.jsp中的URL ,和上面web.xml中的配置相對應。
tokenURL : "${ctx}/servlet/tk.servlet", /** 根據文件名、大小等信息獲取Token的URI(用於生成斷點續傳、跨域的令牌) */
uploadURL : "${ctx}/servlet/upload.servlet", /** HTML5上傳的URI */
-----------
Stream Maven項目中src/main/resources中的配置文件stream-config.properties應該復制到自己的項目中,復制到哪里呢?!
和struts.xml同一級別(最后都編譯到classes中。經過測試放置到WEB-INF中是不行的)
stream-config.properties的配置。放置的位置問題
在這個配置文件中,你可以指定上傳文件存放的地址。
#STREAM_FILE_REPOSITORY=D\:\\XXX
會保存到D盤的 XXX文件夾,如果沒有這個文件夾,就會自動創建。
Configurations.java中會讀取這個配置文件。
到這里,你可以用debug模式運行,並在cn.twinkling.stream.servlet.TokenServlet
cn.twinkling.stream.servlet.StreamServlet
中打上斷點。進行進一步的調試了。
但是還不夠!!!!!我們要根據自己的需求進一步更改源碼,以符合我們項目的實際需求!!
項目實際需求:
1、我們需要在文件上傳完畢的時候,同時保存一條記錄到數據庫中。
(包括文件的名字,上傳的時間,上傳的類型,等等)
2、文件是明文存儲的,沒有加密,所以需要加密
3、文件如果名字重復的時候,是不能讓其上傳的。
4、顏色樣式進一步調整。
5、拖拽,更改為【選擇文件】按鈕。
6、加上備注!!
7、文件上傳按月份創建不同的文件夾。
---------------------------------------試卷的詳細頁面,點擊【超大資料】按鈕跳轉到大文件上傳頁面----------------------------------------------------
此項目是一個關於試卷的系統,詳細頁面就是一個考試的試卷相關信息。
每個試卷都有一個id
下面的id=${model.id} 就是試卷的id
aseInfo是action的名字,editBigFile是action中的方法
詳情頁面,點擊【超大資料】按鈕
<input name="" type="button" class="btn" value="超大資料" onclick="window.location.href='${ctx}/case/caseInfo!editBigFile?id=${model.id}'"/>
public class CaseInfoAction{ protected static final String STREAMFILE = "streamFile";// 上傳超大附件頁面 /**進入超大附件上傳頁面jzk*/ public String editBigFile() { caseInfo = caseInfoService.getById(caseInfo.getId()); ServletActionContext.getRequest().setAttribute("caseInfo", caseInfo); return “STREAMFILE ”;//和struts.xml中的配置對應 } }
Struts.xml中配置上
<result name="streamFile">/view/case/{1}/streamFile.jsp</result>
跳轉到streamFile.jsp中 (還看不懂的,復習一下struts2)
---------------------------------跳轉到大文件上傳頁面 end-------------------------------------------------
新建一個streamFile.jsp
streamFile.jsp在原來streamDemo.jsp基礎上進行更改
頁面中的js
okenURL : "${ctx}/servlet/tk.servlet", /** 根據文件名、大小等信息獲取Token的URI(用於生成斷點續傳、跨域的令牌) */
tokenURL不用改
uploadURL : "${ctx}/servlet/upload.servlet ", /** HTML5上傳的URI */
uploadURL不用改
StreamServlet.java中
這個doPost方法中
1、File f = new File(“D://xxxx/20160303/”); 實現,按月份建不同文件夾
2、文件的名字加密,更改文件名。 實現文件加密
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { File f = IoUtil.getTokenedFile(token); /** * Acquired the file. * @param key * @return * @throws IOException */ public static File getTokenedFile(String key) throws IOException { if (key == null || key.isEmpty()) return null; File f = new File(Configurations.getFileRepository() + File.separator + key); if (!f.getParentFile().exists()) f.getParentFile().mkdirs(); if (!f.exists()) f.createNewFile(); return f; }
Configurations.getFileRepository() 注意這個,
更改為如下:(實現了,按月份建立不同的文件夾,
Constants.FILE_UPLOAD_PATH是系統的全局配置。D://
所以getConfig("STREAM_FILE_REPOSITORY")可以配置為相對路徑,保存在數據庫中/xxxx/)
Configurations.java中
public static String getFileRepository() { String val =Constants.FILE_UPLOAD_PATH+getConfig("STREAM_FILE_REPOSITORY")+DateTimeUtil.format("yyyyMM",new Date()); if (val == null || val.isEmpty()) val = REPOSITORY; return val; } 同時加上如下這個,這個地址用來保存到數據中,是相對路徑。 //獲取保存到數據庫中的地址,相對路徑。 public static String getFileRepositorySaveToDataBase(String fileName) { String val = getConfig("STREAM_FILE_REPOSITORY")+DateTimeUtil.format("yyyyMM",new Date())+"/"+PasswordHash.encrypt(fileName.replaceAll("/", Matcher.quoteReplacement(File.separator))); if (val == null || val.isEmpty()) val = REPOSITORY; return val; }
名字加密:
dopost中
/** rename the file */ if (range.getSize() == start) { /** fix the `renameTo` bug */ File dst = IoUtil.getFile(fileName);
在IoUtil中getFile方法。
加上 md5加密
String name = PasswordHash.encrypt(filename.replaceAll("/", Matcher.quoteReplacement(File.separator)));
md5加密方法如下:
MD5加密: public static String encrypt(String inString) { try { MessageDigest md; // MD5, SHA, SHA-1 md = MessageDigest.getInstance("MD5"); byte[] output = md.digest(inString.getBytes()); StringBuffer sb = new StringBuffer(2 * output.length); for (int i = 0; i < output.length; ++i) { int k = output[i] & 0xFF; if (k < 0x10) { sb.append('0'); } sb.append(Integer.toHexString(k)); } return sb.toString(); } catch (java.security.NoSuchAlgorithmException e) { } return null; }
此時:文件加密,和按照月份創建不同的文件夾完畢。
我們需要在文件上傳完畢的時候,同時保存一條記錄到數據庫中。
由於,文件上傳時候,可以多次暫停,開始,所以在后台不好添加,
StreamFile.jsp中
onComplete: function(file) {},/** 單個文件上傳完畢的響應事件,在這里文件保存完畢之后,通過ajax保存到表中 */
一個文件上傳完畢之后,就會調用一下這里,所以可以在這里通過ajax來將已經上傳的文件的相關信息保存到數據庫表中。
所以,在這里改為如下
onComplete: function(file) { // console.log(file); var fileName = file.name; var remark=""; $("li").each(function(){ if($(this).find("strong").eq(0).html()==fileName) { remark= $(this).find("#fileRemarkId").eq(0).val(); } }); $.post('${pageContext.request.contextPath}/mycfs/uploadFile!insertToUploadFile', {fileName:fileName,instanceId:'${model.id}',instanceType:"CaseInfo",remark:remark}, function (text, status) { var list = eval(text); var type = list[0]; var state = type.state; if(state== true){ console.log("上傳完畢,並成功保存到數據庫記錄"); } }); }, /** 單個文件上傳完畢的響應事件,在這里文件保存完畢之后,通過ajax保存到表中 */
UploadFileAction.java 代碼
/** * 超大文件保存之后,插入一條數據 * @throws JSONException * */ public String insertToUploadFile() throws JSONException{ UploadFile up = new UploadFile(); String fileName = ServletActionContext.getRequest().getParameter("fileName"); String instanceIdStr = ServletActionContext.getRequest().getParameter("instanceId"); Long instanceId =Long.valueOf(instanceIdStr); String instanceType = ServletActionContext.getRequest().getParameter("instanceType"); String remark = ServletActionContext.getRequest().getParameter("remark"); String fileExt = UpLoadFile.getFileExtension(fileName); String fileType = "";//未做,file封裝 Date createDate = new Date(); String filePathStr = Configurations.getFileRepositorySaveToDataBase(fileName); up.setFileName(fileName); up.setFileType(fileType); up.setFileExt(fileExt); up.setRemark(remark); up.setCreateDate(createDate); up.setInstanceId(instanceId); up.setFilePath(filePathStr); up.setInstanceType(instanceType); uploadFileService.save(up); //保存成功 boolean flagStr = true; StringBuffer sb = new StringBuffer( "["); sb.append( "{state:"+ flagStr); sb.append( "}]"); ActionContext. getContext().put("json", sb.toString());//[{state:false}] return "ajax"; }
數據庫結構
可能你已經發現上面的 remark 備注問題了。!!
在stream-v1.js中 sCellFileTemplate 中的后面加上
'<div class="">' + '<br/>' + ' 添加備注:<input class="scinput1" type="text" id="fileRemarkId" name="fileRemark" />' + '</div>'
此時重新進入streamFile.jsp中。打開控制台 火狐下的F12 ………………
就會看到如下DOM結構
如果上傳多個文件,remark名字相同,會重復沖突。
解決:
通過一個jquery循環,每上傳完畢一個文件,執行onComplete函數,
此時上傳的文件名字是知道的,通過循環,如果名字相同,那么就獲取對應的備注,然后保存。
$("li").each(function(){ if($( this).find( "strong").eq(0).html()==fileName) { remark= $(this).find("#fileRemarkId" ).eq(0).val(); } });
通過ajax保存的代碼上面已經列出。
文件的名字是否重復的校驗。
//創建一個全局的js變量,//將所有的上傳的文件名字都匯總到這個變量,以|分隔 arrayObjTotal = "";
首先在streamFile.jsp中設置ajax為同步,等待server端處理之后,再向下執行其他代碼。
$(function(){ $.ajaxSetup({ async: false }); });
stream-v1.js中的修改
如何在js獲取根目錄
//獲取當前網址,如: http://localhost:8080/ems/Pages/Basic/Person.jsp var curWwwPath = window.document.location.href; //獲取主機地址之后的目錄,如: /ems/Pages/Basic/Person.jsp var pathName = window.document.location.pathname; var pos = curWwwPath.indexOf(pathName); //獲取主機地址,如: http://localhost:8080 var localhostPath = curWwwPath.substring(0, pos); //獲取帶"/"的項目名,如:/ems var basePath = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
//addStreamTask是當你選擇文件之后,就會顯示一行文件。如果有重復的文件名,就不讓其顯示。
addStreamTask : function(a) { booleanFlag = null;//全局標識,能否繼續向下執行。 // ######################## var name = a.get( "name"); if(arrayObjTotal!= null&&arrayObjTotal!= ""&&arrayObjTotal.indexOf(name)!=-1){ //包含 alert(name+ "名字重復!" ); return; //如果重復,不執行下面增加的函數 } else{ arrayObjTotal += name+ "|"; } //復用,原file.jsp的上傳校驗 $.post(basePath+ '/mycfs/uploadFile!checkFileNameUnique', {arrayObj:arrayObjTotal}, function (text, status) { var list = eval(text); var type = list[0]; var state = type.state; var rname = type.rname; var msg = type.msg; if(state== false){ if(msg!= null&&msg== 'haveReapeatMsg'){ alert( "有重復的文件名,請重試" ); } else{ //已經存在,將這個名字去除掉 arrayObjTotal = arrayObjTotal.replace(rname+"|" , "" ); alert( "文件名【"+rname+"】已經存在,請重新上傳" ); } booleanFlag = false; return; } else{ booleanFlag = true; } }); // //如果重復,不執行下面增加的函數 if(booleanFlag== false){ return;} //################## var file_id = a.get("id"), cell_file = fCreateContentEle("<li id='" + file_id + "' class='stream-cell-file'></li>"); cell_file.innerHTML = this.template; }
/** * ajax驗證上傳的附件名是否有重復的。只根據名字過濾 * @return */ public String checkFileNameUnique (){ //獲取所有的上傳附件的名稱並以|分隔 String totalFileName = ServletActionContext.getRequest().getParameter( "arrayObj"); //360瀏覽器獲取上傳文件名時,會有C:\ fakepath\ 將其去掉 if(totalFileName!= null){ totalFileName = totalFileName.replaceAll( "\\\\", ""); totalFileName = totalFileName.replaceAll("C:fakepath" , "" ); } boolean flagStr = false; //默認有重復 String[] aArray = totalFileName.split( "\\|"); //特殊字符,使用轉義 if(aArray. length==0){ //都刪除之后,傳過來的是空。返回true flagStr= true; } //判斷一起上傳的是否有重復的名字。(數據庫中沒有這個名字) Set<String> set= new HashSet<String>(); for( int i=0;i<aArray. length;i++){ set.add(aArray[i]); } if(aArray. length>set.size()){ flagStr = false; StringBuffer sb = new StringBuffer( "["); sb.append( "{state:"+flagStr); sb.append( ",rname:'',msg:'haveReapeatMsg'"); sb.append( "}]"); ActionContext. getContext().put("json", sb.toString()); return "ajax"; } //判斷一起上傳的是否有重復的名字。(數據庫中沒有這個名字)END for( int i=0;i<aArray. length;i++){ // List<UploadFile> repeatFile = uploadFileDao.findAllBy("fileName",aArray[i] ); List<UploadFile> repeatFile = uploadFileDao.findAllByFileNameAndNotHistory(aArray[i]); if(repeatFile.size()>0){ //一旦存在重復的文件名,立即返回 flagStr = false; StringBuffer sb = new StringBuffer( "["); sb.append( "{state:"+flagStr); sb.append( ",rname:'"+aArray[i]+ "'"); sb.append( "}]"); ActionContext. getContext().put("json", sb.toString());//[{state:false}] return "ajax"; } else{ flagStr = true; } } //都為true返回 StringBuffer sbTrue = new StringBuffer( "["); sbTrue.append( "{state:"+flagStr); sbTrue.append( "}]"); ActionContext. getContext().put("json", sbTrue.toString());//[{state:true}] return "ajax"; }
所有上傳的文件名都保存在arrayObjTotal
同時上傳兩個文件名字相同,會alert(name+ "名字重復!" );
所以,其中的名字都是不相同的,且以 | 分隔。
此時,如果點擊刪除,此時應該將此名字從arrayObjTotal中刪除掉,以保證再次添加時候,可以正常校驗。
stream-v1.js中:
' <a class="stream-cancel" href="javascript:void(0)" onclick="restNameTotalStr(this);">\u5220\u9664</a>'
在stream-v1.js最后面加上
//將所有的上傳的文件名字都匯總到這個變量,以|分隔 arrayObjTotal = ""; //點擊刪除按鈕時,將當前的文件名字從arrayObjTotal,名字總的字符串中去掉。 function restNameTotalStr(a){ var thisName = a.parentNode.parentNode.childNodes[1].childNodes[0].innerHTML;//獲取當前的文件名字 arrayObjTotal = arrayObjTotal.replace(thisName+"|", ""); }
當上傳成功之后,接着上傳bug問題。上傳成功之后,將全局變量置空即可。
streamFile.jsp中
onQueueComplete: function(data) { //將所有的上傳的文件名字都匯總到這個變量,以|分隔 //全部上傳完畢。置空 arrayObjTotal=""; }, /** 所有文件上傳完畢的響應事件 */
樣式調整:
上傳容器高度的調整:
filesQueueHeight : 370, /** 文件上傳容器的高度(px), 默認: 450 */
按鈕樣式根據自己的項目統一即可。
拖拽上傳,更改為按鈕上傳 streamFile.jsp
<div id="i_select_files">
</div>
從上面移動到下面:
<button class= "btn" id ="i_select_files"></ button>|
browseFileBtn : "<div>請選擇文件</div>" ,
更改為: browseFileBtn : "選擇文件", 去掉<div>標簽
將下面stream-v1.js中下面代碼中的? "block" 改為""。。否則會換行。
!this.browseFileBlockDisplay && (this.browseFileBlockDisplay = this.startPanel.style.display == "" ? "block" : this.startPanel.style.display);
同時更改
stream-v1.css中的stream-browse-drag-files-area 要注釋掉,否則
.stream-files-scroll { height: 450px; overflow: auto; width: 980px;/*這里可以更改容器的寬度!!高度在stremFile.jsp中的filesQueueHeight : 370, /** 文件上傳容器的高度(px), 默認: 450 */ }
stream-main-upload-box { width: 980px; /*這里可以更改容器的寬度*/ background-color: #FFFFFF; border-style: solid; border-width: 1px; /* border-color: #50A05B; */ border-color: #299BE8;/*這里可以修改邊框顏色為藍色*/ clear: both; overflow: hidden; }
注意:upload.gif 和bgx.png 要對應加上。自己找去吧。
關於文件的下載。和刪除。以后再說
The end
最終效果圖