005-html+js+spring multipart文件上傳

1、概述

  需求:經過html+js+java上傳最大500M的文件,須要作MD5 消息摘要以及SHA256簽名,文件上傳至雲存儲javascript

1.一、理解http協議

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

  http傳輸的都是二進制數據,能夠當作傳輸的都是字符。html

  http協議其實就是對socket接受到的數據進行解析,或者將按照http協議的格式把數據寫到socket中前端

  HTTP文件上傳是作Web開發時的常見功能,例如上傳圖片、上傳影片等。實現HTTP文件上傳也比較簡單,用任何Web端的腳本均可以輕鬆實現,例如PHP、JSP都有現成的函數或者類來調用。java

  通過分析後發現,原來PHP、JAVA的上傳是先由服務器緩存爲臨時文件,或者服務器將上傳數據緩存到內存中後,再由腳本調用相關的上傳文件處理函數來移動臨時文件來保存文件數據;因爲PHP、JAVA等處理文件上傳須要分兩步,對於大文件與超大文件來講, 再次移動文件也是比較耗時間與系統資源的,因爲瀏覽器將文件提交到服務器上後就會等待服務器端的響應,服務器端移動文件耗時太長,致使瀏覽器等待超時而報錯。jquery

1.二、HTTP文件上傳的技術原理

  HTTP文件上傳是經過  multipart/form-data 協議實現的,multipart/form-data其實是一種數據的編碼分割方式,例如在瀏覽器端編寫一個文件上傳的頁面,向服務器發送POST請求後,服務器端將會收到數據。nginx

  multipart/form-data須要首先在HTTP請求頭設置一個分隔符,例如:WebKitFormBoundarydCC44akR5BzKXSP1:參看請求頭數據web

  而後,將每一個字段用「--分隔符」分隔,最後一個「--分隔符--」表示結束。spring

  例如,要上傳一個name字段"Today"和一個文件11.gif,HTTP正文能夠經過Chrome瀏覽器開發者工具查看【F12】,目前我使用的沒有展現Request  Payload ,可使用wireshark抓包查看apache

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

打開網站

  

點擊上傳文件按鈕

  

  

分析

  一、三次握手創建tcp連接:57行,客戶端發送syc,58行服務端回覆syc和ack,59行客戶端回覆ack,其中60行  TCP Window Update:滑動窗口爲0後,發送方中止發送數據,若是接收方滑動窗口出現空閒空間,則接收方主動發送TCP Window Update來更新發送方的滑動窗口。

  二、數據傳輸:61行,Push+ACK包:數據包協議+ACK包,這樣是爲了減小網絡流量;62行,服務器端返回ack,63行,具體數據傳輸,以及ack,後續就是沒三行一次的循環上傳數據    

1.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、代碼開發

2.一、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.二、基本上傳

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.三、使用前端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="width: 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、技術點說明

3.一、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;

 

其實這裏面已經包含了 輸入流、文件的字節數組

文件的md五、sha256可使用上述的文件字節數組

3.二、文件輸入流轉二進制數據、二進制轉輸出流文件

//將文件寫入程序(以字節數組的形式);
    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.五、容器配置

  若是使用tomcat 注意配置:004-tomcat優化-Catalina中JVM優化、Connector優化、NIO化

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發送到

相關文章
相關標籤/搜索