zoukankan      html  css  js  c++  java
  • web大文件上传(web应用---SSH框架)

    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", /** 根据文件名、大小等信息获取TokenURI(用于生成断点续传、跨域的令牌) */

    uploadURL : "${ctx}/servlet/upload.servlet", /** HTML5上传的URI */

    这个Maven项目源码,后台使用的是servlet  而我们项目SSH使用的是struts

    这里就涉及到了,servletstruts的共存问题

    !!此时先将后台代码加入到自己的项目中,在streamMaven项目中,直接将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:UsersAdministrator.m2 epository 中找)

     实在找不到的,直接百度找。。。。

    到此为止,前台页面有了。后台代码也有了。Jar包已经buildpath 完毕。

    是时候配置web.xml,打通前台和后台了。

    前面提到:这个Maven项目源码,后台使用的是servlet  而我们项目SSH使用的是struts

    这里就涉及到了,servletstruts的共存问题

    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", /** 根据文件名、大小等信息获取TokenURI(用于生成断点续传、跨域的令牌) */

    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

    aseInfoaction的名字,editBigFileaction中的方法

    详情页面,点击【超大资料】按钮

    <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", /** 根据文件名、大小等信息获取TokenURI(用于生成断点续传、跨域的令牌) */

    tokenURL不用改

    uploadURL : "${ctx}/servlet/upload.servlet ", /** HTML5上传的URI */

    uploadURL不用改

    StreamServlet.java中

    这个doPost方法中

    1File 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);

    IoUtilgetFile方法。

    加上 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);">u5220u9664</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;
         980px;/*这里可以更改容器的宽度!!高度在stremFile.jsp中的filesQueueHeight : 370, /** 文件上传容器的高度(px), 默认: 450 */ 
    }
    stream-main-upload-box {
         980px; /*这里可以更改容器的宽度*/
        background-color: #FFFFFF;
        border-style: solid;
        border- 1px;
    /*     border-color: #50A05B; */
        border-color: #299BE8;/*这里可以修改边框颜色为蓝色*/
        clear: both;
        overflow: hidden;
    }

    注意:upload.gif  和bgx.png  要对应加上。自己找去吧。

    关于文件的下载。和删除。以后再说

    The end

    最终效果图

  • 相关阅读:
    StackOverFlow 最有影响力的IT书箱 [Share]
    Principles of Distributed Computing [Share]
    C/C++ 命令行参数
    函数指针
    coursera-北大-计算概论【已完成】
    有关宏定义一题
    blog.163.com 崩溃以后~
    一步步学习SPD2010--第十一章节--处理母版页
    一步步学习SPD2010--第十章节--SP网站品牌化(12)--关键点
    一步步学习SPD2010--第十章节--SP网站品牌化(11)--使用CSS报表
  • 原文地址:https://www.cnblogs.com/duoyansanwei/p/5431787.html
Copyright © 2011-2022 走看看