设为首页 收藏本站
查看: 1298|回复: 0

[经验分享] web大文件上传(web应用---SSH框架)

[复制链接]

尚未签到

发表于 2017-3-1 11:09:21 | 显示全部楼层 |阅读模式
  struts提供的上传文件功能,文件太大就报错。
  需要解决超大文件(几个G)上传的问题。断点续传的功能。
  技术选型,查百度之后。最终选择了这个stream插件。
  stream官方资料地址:http://www.twinkling.cn/
  去下载源码。源码地址。
项目源码托管在git.oschina.net,地址:http://git.oschina.net/jiangdx/stream
DSC0000.png

  解压安装包之后,在eclipse中导入此项目。(注,这是一个Maven项目,服务器用的是tomcat 不是jetty)
  (如果没有maven项目选项,eclipse需要安装maven插件,详情见其他文章)
   DSC0001.png
  如下图配置 maven命令, 点击Run  就会启动项目
DSC0002.jpg

  日志可以看到[INFO] Running war on http://localhost:8080/stream
   DSC0003.png
  浏览器地址:http://localhost:8080/stream    就可以访问项目根目录(src/main/webapp目录)
  比如访问 webapp目录中的s1.html
  http://localhost:8080/stream/s1.html
  就可以看到示例了
DSC0004.png

  下一步:如何整合到我自己的项目中(项目使用的是SSH)
  借鉴了方块糖的页面。方块糖这个是整合到SpringMVC的。而不是SSH的
  首先,选定一个样式,我选择的是如下的样式,在这个基础上进行更改。命名为streamDemo.jsp  (html转为jsp格式,你可以在你项目中复制一个jsp的文件,然后将代码选择性的复制过来)
  streamDemo.jsp借鉴了方块糖的页面,将页面代码拷贝过来。拷贝地址:http://www.fangkuaitang.net/?p=2164
   DSC0005.png
  此时,需要导入两个文件:
  这个页面可以看到,需要引用的文件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中。
  为了方便,包的名字也没有更改。
DSC0006.png

  此时还需要引入必须的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应该复制到自己的项目中,复制到哪里呢?!
DSC0007.png

  和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="超大资料"/>



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 & 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";
}
  数据库结构
DSC0008.png

  可能你已经发现上面的  remark 备注问题了。!!
  在stream-v1.js中  sCellFileTemplate  中的后面加上



'<div class="">' +
'<br/>' +
'    添加备注:<input class="scinput1" type="text" id="fileRemarkId" name="fileRemark" />' +
'</div>'
  此时重新进入streamFile.jsp中。打开控制台 火狐下的F12 ………………
  就会看到如下DOM结构
DSC0009.png

  如果上传多个文件,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);
           }
            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 );
                List<UploadFile> repeatFile = uploadFileDao.findAllByFileNameAndNotHistory(aArray);
                 if(repeatFile.size()>0){
                      //一旦存在重复的文件名,立即返回
                     flagStr = false;
                     StringBuffer sb = new StringBuffer( "[");      
                     sb.append( "{state:"+flagStr);
                     sb.append( ",rname:'"+aArray+ "'");
                     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)" >\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+"|", "");
}
DSC00010.png

  当上传成功之后,接着上传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  要注释掉,否则
   DSC00011.png



.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
  最终效果图
DSC00012.jpg

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348761-1-1.html 上篇帖子: 【Scala】Scala技术栈 下篇帖子: 成为Java顶尖高手要看的11本书
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表