• 005-html+js+spring multipart文件上传


    一、概述

      需求:通过html+js+java上传最大500M的文件,需要做MD5 消息摘要以及SHA256签名,文件上传至云存储

    1.1、理解http协议

      https://www.cnblogs.com/bjlhx/category/1198166.html

      http传输的都是二进制数据,可以看成传输的都是字符。

      http协议其实就是对socket接受到的数据进行解析,或者将按照http协议的格式把数据写到socket中

      HTTP文件上传是做Web开发时的常见功能,例如上传图片、上传影片等。实现HTTP文件上传也比较简单,用任何Web端的脚本都可以轻松实现,例如PHP、JSP都有现成的函数或者类来调用。

      经过分析后发现,原来PHP、JAVA的上传是先由服务器缓存为临时文件,或者服务器将上传数据缓存到内存中后,再由脚本调用相关的上传文件处理函数来移动临时文件来保存文件数据;由于PHP、JAVA等处理文件上传需要分两步,对于大文件与超大文件来说, 再次移动文件也是比较耗时间与系统资源的,由于浏览器将文件提交到服务器上后就会等待服务器端的响应,服务器端移动文件耗时太长,导致浏览器等待超时而报错。

    1.2、HTTP文件上传的技术原理

      HTTP文件上传是通过  multipart/form-data 协议实现的,multipart/form-data实际上是一种数据的编码分割方式,例如在浏览器端编写一个文件上传的页面,向服务器发送POST请求后,服务器端将会收到数据。

      multipart/form-data需要首先在HTTP请求头设置一个分隔符,例如:WebKitFormBoundarydCC44akR5BzKXSP1:参看请求头数据

      然后,将每个字段用“--分隔符”分隔,最后一个“--分隔符--”表示结束。

      例如,要上传一个name字段"Today"和一个文件11.gif,HTTP正文可以通过Chrome浏览器开发者工具查看【F12】,目前我使用的没有展示Request  Payload ,可以使用wireshark抓包查看

    1.2.1、wireshark 配置抓包:tcp.port eq 8080

    打开网站

      

    点击上传文件按钮

      

      

    分析

      1、三次握手建立tcp链接:57行,客户端发送syc,58行服务端回复syc和ack,59行客户端回复ack,其中60行  TCP Window Update:滑动窗口为0后,发送方停止发送数据,如果接收方滑动窗口出现空闲空间,则接收方主动发送TCP Window Update来更新发送方的滑动窗口。

      2、数据传输:61行,Push+ACK包:数据包协议+ACK包,这样是为了减少网络流量;62行,服务器端返回ack,63行,具体数据传输,以及ack,后续就是没三行一次的循环上传数据    

    1.2.2、配置查看 TCP 流

      

    查看整个请求响应过程【文件流删除大部分内容】

    POST /manage/uploadFile HTTP/1.1
    Host: zs.jd.com:8080
    Connection: keep-alive
    Content-Length: 335334
    Cache-Control: max-age=0
    Origin: http://zs.jd.com:8080
    Upgrade-Insecure-Requests: 1
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywcj3RACSzuBGHt5g
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Referer: http://zs.jd.com:8080/file.html
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    
    ------WebKitFormBoundarywcj3RACSzuBGHt5g
    Content-Disposition: form-data; name="name"
    
    sss
    ------WebKitFormBoundarywcj3RACSzuBGHt5g
    Content-Disposition: form-data; name="file"; filename="11.gif"
    Content-Type: image/gif
    
    GIF89a+...w..!..NETSCAPE2.0.......=;.@.;
    ------WebKitFormBoundarywcj3RACSzuBGHt5g--
    
    HTTP/1.1 200 
    Transfer-Encoding: chunked
    Date: Thu, 06 Jun 2019 02:24:05 GMT
    
    0
    
    GET /favicon.ico HTTP/1.1
    Host: zs.jd.com:8080
    Connection: keep-alive
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
    Accept: image/webp,image/apng,image/*,*/*;q=0.8
    Referer: http://zs.jd.com:8080/manage/uploadFile
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    
    HTTP/1.1 200 
    X-Content-Type-Options: nosniff
    X-XSS-Protection: 1; mode=block
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Pragma: no-cache
    Expires: 0
    X-Frame-Options: DENY
    Content-Length: 0
    Date: Thu, 06 Jun 2019 02:24:05 GMT

    二、代码开发

    2.1、jar配置

    POM jar

            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>1.3.1</version>
            </dependency>
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.5</version>
            </dependency>

    因为使用springboot,配置基础参数

    spring.http.multipart.max-file-size=500MB
    spring.http.multipart.max-request-size=500MB

    代码类:只要是Spring 生态的应用程序,文件的接收都是使用MutipartFile这个类型,它表示通过 mutipart 请求上传了的一个文件。如果多个文件上传,那就用数组,如 MutipartFile[] 。 

    2.2、基本上传

    html

    <form enctype="multipart/form-data"
          action="/bs/test/uploadFile/cloud" method="post">
    
        姓名:<input type="text"  name="name">
        上传文件: <input type="file" name="file" />
    <br/>
        <input type="submit" value="上传"/>
    </form>

    java代码

    util方法

    import java.io.*;
    import java.security.MessageDigest;
    
    import org.apache.commons.codec.binary.Hex;
    
    public class CommonHelper {
    
        public static String msgSafeBase(String msg, String algorithmName) throws Exception {
            return msgSafeBase(msg.getBytes("UTF8"), algorithmName);
        }
    
        public static String msgSafeBase(byte[] data, String algorithmName) throws Exception {
            MessageDigest m = MessageDigest.getInstance(algorithmName);
            m.update(data);
            byte s[] = m.digest();
            return Hex.encodeHexString(s);
        }
    
        public static String msgSafeBase(InputStream inputStream, String algorithmName) throws Exception {
            MessageDigest m = MessageDigest.getInstance(algorithmName);
    
            //分多次将一个文件读入,对于大型文件而言,比较推荐这种方式,占用内存比较少。
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = inputStream.read(buffer, 0, 1024)) != -1) {
                m.update(buffer, 0, length);
            }
            inputStream.close();
    
            byte s[] = m.digest();
            return Hex.encodeHexString(s);
        }
    
    
        public static String msgSafeBaseMD5(byte[] data) throws Exception {
            return msgSafeBase(data, "MD5");
        }
    
        public static String msgSafeBaseMD5(InputStream inputStream) throws Exception {
            return msgSafeBase(inputStream, "MD5");
        }
    
        public static String msgSafeBaseSHA256(byte[] data) throws Exception {
            return msgSafeBase(data, "SHA-256");
        }
    
        public static String msgSafeBaseSHA256(InputStream inputStream) throws Exception {
            return msgSafeBase(inputStream, "SHA-256");
        }
    }
    View Code

    接收方法

        @RequestMapping("/uploadFile/cloud2")
        public Object bigFile(HttpServletRequest request, HttpServletResponse response, String guid, String md5,
                              Integer chunk, @RequestParam(required = false, value = "file") MultipartFile file, Integer chunks) {
        String baseMD5 = CommonHelper.msgSafeBaseMD5(file.getBytes());
        String sha256 = CommonHelper.msgSafeBaseSHA256(file.getBytes());
      fIleResp.getData().setMd5(baseMD5);
      fIleResp.getData().setSha256(sha256);

      String s = fileService.upLoadFileStream(fileProperty, file.getInputStream(), file.getSize());
    }

    2.3、使用前端webuploader 不分片处理

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
        <title>Title</title>
        <link href="https://cdn.staticfile.org/webuploader/0.1.5/webuploader.css" rel="stylesheet" type="text/css"/>
        <script type="text/javascript" src="http://cdn.staticfile.org/jquery/1.10.2/jquery.js"></script>
        <script type="text/javascript" src="http://cdn.staticfile.org/webuploader/0.1.5/webuploader.min.js"></script>
    </head>
    <body>
    <div id="uploader" class="wu-example">
        <!--用来存放文件信息-->
        <div id="thelist" class="uploader-list"></div>
        <div class="btns">
            <div id="picker">选择文件</div>
            <button id="ctlBtn" class="btn btn-default" style="display: none">开始上传</button>
        </div>
    
        <p>目前的进度如下:</p>
        <div class="progress">
            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
                <span class="sr-only">60% Complete</span>
            </div>
        </div>
    </div>
    </body>
    
    <script type="text/javascript">
        var $ = jQuery,
            $list = $('#thelist'),
            state = 'pending',
            $btn = $('#ctlBtn');
    
        var GUID = WebUploader.Base.guid();//一个GUID
        var uploader = WebUploader.create({
            // swf文件路径
            swf: 'http://cdn.staticfile.org/webuploader/0.1.5/Uploader.swf',
            // 文件接收服务端。
            server: './bs/test/uploadFile/cloud',
            formData: {
                guid: GUID,
                md5: ''
            },
            // 选择文件的按钮。可选。
            // 内部根据当前运行是创建,可能是input元素,也可能是flash.
            pick: '#picker',
            chunked: false, // 分片处理
            chunkSize: 500 * 1024 * 1024, // 每片500M,
            chunkRetry: false,// 如果失败,则不重试
            threads: 5,// 上传并发数。允许同时最大上传进程数。
            // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
    
            auto: false,
            resize: false
        });
        $("#ctlBtn").click(function () {
            uploader.upload();
        });
        //当文件上传成功时触发。
        uploader.on("uploadSuccess", function (file) {
            console.log(file)
    
            $('#' + file.id).find('p.state').append('已经上传');
            alert('上传成功!');
        });
    
        // 文件上传过程中创建进度条实时显示。
        uploader.on('uploadProgress', function (file, percentage) {
            var $li = $('#' + file.id),
                $percent = $li.find('.progress .progress-bar');
    
            // 避免重复创建
            if (!$percent.length) {
                $percent = $('<div class="progress progress-striped active">' +
                    '<div class="progress-bar" role="progressbar" style=" 0%">' +
                    '</div>' +
                    '</div>').appendTo($li).find('.progress-bar');
            }
    
            // $li.find('p.state').append('开始上传……');
    
            $percent.css('width', percentage * 100 + '%');
        });
    
        // 当有文件被添加进队列的时候
        uploader.on('fileQueued', function (file) {
            console.log("文件队列事件被触发..");
            $list.append('<div id="' + file.id + '" class="item">' +
                '<h4 class="info">' + file.name + '</h4>' +
                '<p class="state"></p>' +
                '</div>');
            var _file = $("#" + file.id);
            //计算md5
            uploader.md5File(file)
            // 及时显示进度
                .progress(function (percentage) {
                    //console.log('Percentage:', percentage);
                    _file.find("p").html("准备中:" + percentage * 100 + "%");
                })
                // 完成
                .then(function (val) {
                    uploader.options.formData.md5 = val;
                    _file.find("p").append("md5:" + val);
                    $btn.show();
                });
        });
    </script>
    </html>
    View Code

    三、技术点说明

    3.1、spring 的 MultipartFile 说明

        String getName();
    
        String getOriginalFilename();
    
        String getContentType();
    
        boolean isEmpty();
    
        long getSize();
    
        byte[] getBytes() throws IOException;
    
        InputStream getInputStream() throws IOException;
    
        void transferTo(File var1) throws IOException, IllegalStateException;

    其实这里面已经包含了 输入流、文件的字节数组

    文件的md5、sha256可以使用上述的文件字节数组

    3.2、文件输入流转二进制数据、二进制转输出流文件

    //将文件写入程序(以字节数组的形式);
        public static byte[] FiletoByte(String path){
             File myFile=new File(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             try {
                FileInputStream myInputStream=new FileInputStream(myFile);
                int len=-1;
                byte[]car=new byte[1024*10];
                while((len=myInputStream.read(car))!=-1)
                {
                    bos.write(car, 0, len);
                    bos.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
             try {
                bos.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            }
            return bos.toByteArray();
            
        }
        
        //将字节写入文件(以字节数组的形式);
        public static void BytetoFile(String path,byte[]datas)
        {
            System.out.println(datas.length);
            File myFile=new File(path);
            ByteArrayInputStream bis = new ByteArrayInputStream(datas);
            try {
                    int len;
                    byte[]car=new byte[1024*10];
                    FileOutputStream  fileOutputStream=new FileOutputStream(myFile);
                    while((len=bis.read(car))!=-1)
                    {
                     fileOutputStream.write(car, 0, len);
                     fileOutputStream.flush();
                    }
                
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    3.5、容器配置

      如果使用tomcat 注意配置:004-tomcat优化-Catalina中JVM优化、Connector优化、NIO化

      nginx配置,在location上配置:client_max_body_size 500m;

    发送到

  • 相关阅读:
    MySQL: MySQL数据学习专题
    安装Team Foundation Server 2012过程截图
    如果你喜欢一个程序员小伙
    ASP.net MVC: 一个开源的“留言系统”
    ASP.net MVC 中Security.FormsAuthentication验证用户的状态(匿名|已登录)
    Microsoft Visual Stadio 2012 Ultimate版安整过程安装体验
    win8全面开放民间下载地址!win8下载地址 win8下载链接
    【技术贴】解决右键没有新建文本文档|右键没有新建txt
    【技术贴】虚拟机网络上有重名的解决|虚拟机Net模式提示有重名
    【技术贴】利用myeclipse自动生成java类图|java源代码自动生成类图
  • 原文地址:https://www.cnblogs.com/bjlhx/p/10974756.html
Copyright © 2020-2023  润新知