formData批量上傳的多種實現

前言

  最近項目須要批量上傳附件,查了下資料,網上不少但看着一臉懵,只貼部分代碼,介紹也不詳細,這裏記錄一下本身的採坑與多種實現,以避免之後忘記。css

 

  這裏先介紹下FormData對象,如下內容摘自:https://developer.mozilla.org/zh-CN/docs/Web/API/FormDatahtml

  XMLHttpRequest Level 2添加了一個新的接口FormData.利用FormData對象,咱們能夠經過JavaScript用一些鍵值對來模擬一系列表單控件,咱們還可使用XMLHttpRequest的send()方法來異步的提交這個"表單".比起普通的ajax,使用FormData的最大優勢就是咱們能夠異步上傳一個二進制文件.前端

 

  在個人自定義input文件上傳樣式裏就已經實現裏單文件上傳,而且實現了自定義input樣式;若是構造FormData對象是傳入表單js對象,formData會自動注入表單裏的值;若是是new一個空對象,而後手動append的表單類型爲file時要注意:這裏append進去的是File對象,而不是FileList對象java

   

效果

  先看一下大概效果:ajax

 代碼編寫

 

  controller有兩種方法:三種方式調的都是用一個接口apache

    /**
     * 批量上傳
     */
    @PostMapping("upload")
    public ResultModel<List<AttachmentVo>> upload(HttpServletRequest request, @RequestParam("applyId") String applyId){
        List<MultipartFile> multipartFileList = ((MultipartHttpServletRequest) request).getFiles("attachment");
        System.out.println(multipartFileList.size());
        System.out.println(applyId);
        return null;
    }

    /**
     * 批量上傳2 (推薦使用)
     */
    @PostMapping("upload2")
    public ResultModel<List<AttachmentVo>> upload2(MultipartFile[] attachment,@RequestParam("applyId") String applyId){
        System.out.println(attachment.length);
        System.out.println(applyId);
        return null;
    }

 

  自定義樣式:(三種方式都是用這個樣式),要引入bootstrap, 圖標用的是font awesomejson

  

      .nav-bar {
          border-top: 1px solid #9E9E9E;
          margin: 10px 0 20px;
      }

      .nav-bar-title {
          margin: -13px 0 0 35px;
          background-color: white;
          padding: 0 10px;
          float: left;
          color: #199ED8;
      }
       
       .attachment-remove {
            font-size: 25px;
            color: red;
            margin-left: 5px;
            cursor: pointer;
        }

        .attachment-text-p {
            border: 1px solid #c2cad8;
            padding: 5px 5px;
            margin: 0;
            float: left;
            height: 30px;
            width: 90%;
        }

        .attachment-text-p + i {
            float: left;
            line-height: 30px !important;
        }

        .input-attachment {
            width: 90% !important;
            padding: 4px 12px !important;
        }

 

 

方式1

  點擊Add,追加一個input,點擊Delete,刪除一個input,點擊叉號也能夠刪除對應的input,須要單獨爲每一個input選擇文件bootstrap

  效果後端

 

  html數組

<form id="attachments" enctype="multipart/form-data" class="form-horizontal nice-validator n-yellow" novalidate="novalidate">
        <div class='form-body'>
            <div class='form-group'>
                <label class="control-label col-md-1">附件管理:</label>
                <div class="col-md-4">
                    <button id="attachmentAddBtn" type="button" class="btn btn-default">Add Attachment</button>
                    <button id="attachmentDeleteBtn" type="button" class="btn btn-default">Delete Attachment</button>
                    <button id="attachmentUploadBtn" type="button" class="btn btn-default">Upload</button>
                </div>
            </div>
            <div class='form-group'>
                <label class="control-label col-md-1">附件上傳:</label>
                <div id="attachmentInputs" class="col-md-3">

                </div>
            </div>
        </div>
    </form>

  js

    //attachment-remove
    $("#attachmentInputs").on("click", ".attachment-remove", function (even) {
        $(this).prev().remove();//刪除上一個兄弟節點
        $(this).remove();//刪除本身
    });

    //add but
    $("#attachmentAddBtn").click(function (even) {
        //name值同樣就能夠
        $("#attachmentInputs").append("<input name=\"attachment\" type=\"file\" class=\"form-control input-attachment\"/><i class=\"fa fa-times attachment-remove\"></i>");
    });

    //delete
    $("#attachmentDeleteBtn").click(function (even) {
        var files = $("#attachmentInputs input[type='file']");
        files.each(function (index, element) {
            //從最下面開始刪除,至少保留一個
            if (!(index === 0) && index === (files.length - 1)) {
                $(element).next().remove();
                $(element).remove();
            }
        });
    });

    //upload
    $("#attachmentUploadBtn").click(function (even) {
        //一、經過HTML表單建立FormData對象 自動注入
        // var formData = new FormData($("#attachments")[0]);

        //二、從零開始建立FormData對象 手動注入
        var formData = new FormData();
        //注入 name=file
        var files = $("#attachmentInputs input[type='file']");
        for (var i = 0; i < files.length; i++) {
            //注意:這裏append進去的是File對象,而不是FileList對象
            formData.append("attachment", files[i].files[0]);
        }
        //注入name=text
        formData.append("applyId", "123456");

        console.log(formData.getAll("attachment"));
        
        //執行上傳
        $.ajax({
            url: ctx + "/attachment/upload2",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function (data) {
            },
            error: function (e) {
            }
        });
    });

    //add one input
    $("#attachmentAddBtn").click();

 

 

方式2

  第二種方式只有一個input,用的是multiple="multiple"屬性,能夠再彈窗裏選擇多個文件提交,若是再加工一下,也作成第三種同樣,展現出文件名,同時能夠刪除對應的文件

  效果

 

 

  html

<form id="attachments2" enctype="multipart/form-data" class="form-horizontal" novalidate="novalidate">
        <div class='form-body'>
            <div class='form-group'>
                <label class="control-label col-md-1">附件管理:</label>
                <div class="col-md-4">
                    <button id="attachmentUploadBtn2" type="button" class="btn btn-default">Upload</button>
                </div>
            </div>
            <div class='form-group'>
                <label class="control-label col-md-1">附件上傳:</label>
                <div id="attachmentInputs2" class="col-md-3">
                    <input name="attachment" type="file" class="form-control input-attachment" multiple="multiple"/>
                </div>
            </div>
        </div>
    </form>

  js

   //upload2
    $("#attachmentUploadBtn2").click(function (even) {
        //一、經過HTML表單建立FormData對象 自動注入
        // var formData = new FormData($("#attachments2")[0]);

        //二、從零開始建立FormData對象 手動注入
        var formData = new FormData();
        //注入 name=file
        var files = $("#attachmentInputs2 input[type='file']");
        for (var i = 0; i < files[0].files.length; i++) {
            formData.append("attachment", files[0].files[i]);
        }
        //注入name=text
        formData.append("applyId", "123456");

        console.log(formData.getAll("attachment"));

        //執行上傳
        $.ajax({
            url: ctx + "/attachment/upload2",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function (data) {
            },
            error: function (e) {
            }
        });
    });

 

 

方式3

   定義了一個隱藏的input,並將Select File按鈕的click與input的click對等,點擊按鈕至關於點擊input,彈出選擇文件對話框,監聽了input的change事件,將選擇的file對象push到全局數組變量attachmentArray中,點擊Upload時再遍歷注入到formData中

  效果

 

  html

<form id="attachments3" enctype="multipart/form-data" class="form-horizontal" novalidate="novalidate">
        <div class='form-body'>
            <div class='form-group'>
                <label class="control-label col-md-1">附件管理:</label>
                <div class="col-md-4">
                    <button id="selectFile" type="button" class="btn btn-default">Select File</button>
                    <button id="attachmentUploadBtn3" type="button" class="btn btn-default">Upload</button>
                </div>
            </div>
            <div class='form-group'>
                <label class="control-label col-md-1">附件上傳:</label>
                <input id="attachmentInputs3" type="file" style="display: none;"/>
                <div id="attachmentText3" class="col-md-3">
                </div>
            </div>
        </div>
    </form>

 

  js

    //存放file對象
    var attachmentArray = [];
    //attachment-remove
    $("#attachmentText3").on("click", ".attachment-remove", function (even) {
        //刪除attachmentArray數據
        attachmentArray.splice($(this).data("index"), 1);
        //刪除html對象
        $(this).prev().prev().remove();
        $(this).prev().remove();
        $(this).remove();
    });

    //Select File
    $("#selectFile").click(function (even) {
        // 獲取input
        $("#attachmentInputs3").click();
    });

    //input change
    $("#attachmentInputs3").change(function (even) {
        // 獲取input
        var fileName = $(this).val();
        var file = $(this)[0].files[0];
        //是否選擇了文件
        if (fileName) {
            attachmentArray.push(file);
            $("#attachmentText3").append("<div><p class='attachment-text-p'>" + fileName + "</p><i data-index='" + (attachmentArray.length - 1) + "' class=\"fa fa-times attachment-remove\"></i></div>")
        }
    });

    //upload3
    $("#attachmentUploadBtn3").click(function (even) {
        //這裏只能手動注入
        var formData = new FormData();
        //遍歷數據,手動注入formData
        for (var i = 0; i < attachmentArray.length; i++) {
            formData.append("attachment", attachmentArray[i]);
        }
        formData.append("applyId", "123456");
        console.log(formData.getAll("attachment"));
        //執行上傳
        $.ajax({
            url: ctx + "/attachment/upload",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function (data) {
            },
            error: function (e) {
            }
        });
    });

 

後記

  最後看一下file數據、請求頭、還有振奮人心的後臺成功接參圖

  file數據

 

請求頭

 

成功接參

 

新需求

  項目須要支持同一張單上面有多個上傳組件,按照咱們以前的三種方式並不知足,第一種使用了id的方式去綁定,當多個組件在同一個html的時候就不行了,第三種咱們採用一個全局數組變量來存選中的file,但以前一個組件有引一次js,當多個的時候就會重複引入,後面引入的變量、方法就會覆蓋前面,同時,應該用的是id,當咱們調用upload方式時不知道applyId工單號對應的form是哪個,沒法綁定附件的工單號,這裏改進一下,將第一種跟第三種整合一下。

 

  上傳組件html

  使用的是thymeleaf,th:text="#{attachment.title}"是國際化,<script th:replace="common/head::static"></script>引入的是公用的js、css,上傳組件的js、css寫在common裏面,全部的頁面都會引入它們,並且只引入一次。這裏給每一個form表單綁定一個applyId屬性,對應具體的工單號,這樣咱們調用upload的時候就能夠找到對應的form表單

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title th:text="#{attachment.title}"></title>
    <script th:replace="common/head::static"></script>
</head>
<body>
<!--
  使用方法:在任意工單頁面添加此DIV
  <div th:replace="attachment/attachment::attachmentPage(${applyId})"></div>

  調用上傳方法:Attachment.upload(${applyId});
-->
<div th:fragment="attachmentPage(applyId)">
    <div class="nav-bar"><span class="nav-bar-title" th:text="#{attachment.title}"></span></div>
    <form th:applyId="${applyId}" class="form-horizontal attachments-form" enctype="multipart/form-data">
        <div class='form-body'>
            <div class='form-group'>
                <label class="control-label col-md-1">附件管理:</label>
                <div class="col-md-4">
                    <button type="button" class="btn btn-default" onclick="Attachment.appendAttachmentInput(this)">
                        Select File
                    </button>
                </div>
            </div>
            <div class='form-group'>
                <label class="control-label col-md-1">附件列表:</label>
                <div class="col-md-10 attachments-list"></div>
            </div>
        </div>
    </form>
</div>
</body>
</html>

 

  其餘任意html調用

  thymeleaf的傳值方式之一,與組件html的 th:fragment="attachmentPage(applyId)" 配合使用,後面就能夠這樣使用 th:applyId="${applyId}"

        <div th:replace="attachment/attachment::attachmentPage(123456)"></div>
        <div th:replace="attachment/attachment::attachmentPage(111111)"></div>

 

 

  common.js  上傳組件部分

  removeAttachmentInputListener,監聽×號的點擊事件,要在common.js執行一次。

/**
 * 3、附件上傳的方法
 */
var Attachment = {
    //上傳附件
    upload: function (applyId) {
        //終止上傳
        if (!applyId) {
            layer.msg(i18n('attachment.applyid.is.null'));
            return;
        }

        //添加附件
        var formData = new FormData();
        $("form[applyId='"+applyId+"']").find("input[name='attachment']").each(function (index, element) {
            //過濾操做:input框有值,才append到formData
            if ($(element).val()) {
                formData.append("attachment",element.files[0]);
            }
        });

        //追加applyId到formData
        formData.append("applyId", applyId);

        //執行上傳
        $.ajax({
            url: ctx + "/attachment/upload",
            type: "post",
            data: formData,
            processData: false,
            contentType: false,
            success: function (data) {
                if (checkResult(data)) {
                    console.log('附件上傳成功:', data);
                } else {
                    throw e;
                }
            },
            error: function (e) {
                console.log('附件上傳失敗');
                throw e;
            }
        });
    },
    //添加附件
    appendAttachmentInput: function (btn) {
        //先追加html
        $(btn).parents('.attachments-form').find(".attachments-list").append("<div><input type=\"file\" name=\"attachment\" class=\"hidden\"/></div>");

        //最新追加的input
        var attachments = $(btn).parents('.attachments-form').find(".attachments-list").find("input[name='attachment']");

        //綁定input的change事件,注意:當咱們點擊取消或×號時並不觸發,可是無所謂,咱們在upload方法進行過濾空的input就能夠了
        attachments[attachments.length - 1].onchange = function(){
            var fileName = $(this).val();
            if (fileName) {
                $(this).parent("div").append("<p class='attachment-text-p'>" + fileName + "</p><i class=\"fa fa-times attachment-remove\"></i>");
            }else{
                $(this).parent("div").remove();
            }
        };

        //觸發最新的input的click
        attachments[attachments.length - 1].click();
    },
    //刪除附件
    removeAttachmentInputListener: function () {
        $(".attachments-form").on("click", ".attachment-remove", function (even) {
            $(this).parent().remove();
        });
    }
};

 

  common.css 上傳組件部分

    .attachment-remove {
        font-size: 25px;
        color: red;
        margin-left: 5px;
        cursor: pointer;
    }
    
    .attachment-text-p {
        border: 1px solid #c2cad8;
        padding: 5px 5px;
        margin: 0;
        float: left;
        height: 30px;
        width: 90%;
        margin-top: 5px;
    }
    
    .attachment-text-p + i {
        float: left;
        line-height: 30px !important;
        margin-top: 5px;
    }

 

新需求效果

 

 

報錯記錄:org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field images exceeds its maximum permitted size of 1048576 bytes.

解決:調大http的最大上傳大小

string:
  http:
    multipart:
      max-file-size: 5Mb #單個文件大小
      max-request-size: 50Mb #總大小
string:
  servlet:
    multipart:
      max-file-size: 5Mb #單個文件大小
      max-request-size: 50Mb #總大小

 

  導出文件到瀏覽器

  2019-10-24補充:上傳、下載一般是密不可分的兩個功能,這裏記錄一下如何導出文件到瀏覽器而後下載到本地

  前端js

//數據數組,ids
let data = [1,2,3,4];
//ajax不支持下載類型,使用location.href或者表單提交
//window.location.href,get提交,數據會暴露在URL,相對不安全
//建立臨時的、隱藏的form表單,post提交,數據在請求體裏,相對安全
var $form = $(document.createElement('form')).css({display: 'none'}).attr("method", "POST").attr("action", ctx + "/downLoad");
var $input = $(document.createElement('input')).attr('name', "ids").val(JSON.stringify(data));
$form.append($input);
$("body").append($form);
$form.submit();
//提交完成後remove掉
$form.remove();

 

 

  java後端

@PostMapping("/downLoad")
public ResponseEntity downLoad(String ids) throws IOException {
    //json字符串轉換成對象
    List<String> idList  = new ObjectMapper().readValue(ids, TypeFactory.defaultInstance().constructCollectionType(List.class, String.class));

    //處理、拼接數據
    List<StringBuilder> students = new ArrayList<>();
    assert idList != null;
    idList.forEach((id) -> {
        //帳號-密碼-區服-角色-道具
        StringBuilder str = new StringBuilder();
        SuperSearchUcidVo searchUcidVo = superSearchUcidService.get(Integer.valueOf(id)).getData();
        str.append(searchUcidVo.getUserName()).append("-");
        str.append(searchUcidVo.getPassword()).append("-");
        str.append(searchUcidVo.getDivision()).append("-");
        if(!StringUtils.isEmpty(searchUcidVo.getRoleList())){
            String[] roleList = searchUcidVo.getRoleList().split(",");
            for (String role : roleList) {
                str.append(role).append("|");
            }
        }
        str.append("-");
        if(!StringUtils.isEmpty(searchUcidVo.getPropsList())){
            String[] propsList = searchUcidVo.getPropsList().split(",");
            for (String props : propsList) {
                str.append(props).append("|");
            }
        }

        students.add(str);
    });
    StringBuilder write = new StringBuilder();
    students.forEach((str) -> write.append(str).append("\n"));

    //文件數據、文件名
    byte[] fileBytes = write.toString().getBytes("GBK");
    String fileName = "GameAccountExport_" + new Date().getTime() + ".txt";

    //設置響應頭
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    headers.setContentDispositionFormData("attachment", fileName);

    //下載文件
    return new ResponseEntity<>(fileBytes, headers, HttpStatus.CREATED);
}

 

  效果

 

 

 

相關文章
相關標籤/搜索