struts提供的上传文件功能,文件太大就报错。javascript
须要解决超大文件(几个G)上传的问题。断点续传的功能。css
技术选型,查百度以后。最终选择了这个stream插件。html
stream官方资料地址:http://www.twinkling.cn/java
去下载源码。源码地址。
项目源码托管在git.oschina.net,地址:http://git.oschina.net/jiangdx/streamjquery
解压安装包以后,在eclipse中导入此项目。(注,这是一个Maven项目,服务器用的是tomcat 不是jetty)git
(若是没有maven项目选项,eclipse须要安装maven插件,详情见其余文章)web
以下图配置 maven命令, 点击Run 就会启动项目ajax
日志能够看到[INFO] Running war on http://localhost:8080/stream数据库
浏览器地址:http://localhost:8080/stream 就能够访问项目根目录(src/main/webapp目录)json
好比访问 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
最终效果图