1. 程式人生 > >SpringMVC中的上傳和下載

SpringMVC中的上傳和下載

一、概述

檔案的上傳和下載,一直以來都是開發中必不可少的功能。在沒有SpringMVC之前對於檔案的上傳和下載的操作,一般都是通過Apache 開源組織提供了一個用來處理表單檔案上傳的一個開源元件( Commons-fileupload )來實現的。現在SpringMVC提供了檔案的上傳和下載功能,而且使用起來十分簡便。

二、SpringMVC的檔案的上傳和下載

配置Spring-web.xml

檔案的上傳是通過 MultipartResolver 實現的,所以要實現上傳,只要註冊相應的 MultipartResolver 即可。

MultipartResolver 的實現類有兩個:

  • CommonsMultipartResolver (需要 Apache 的 commons-fileupload 支援,它能在比較舊的 servlet 版本中使用,相容性好)

  • StandardServletMultipartResolver (不需要第三方 jar 包支援,它使用 servlet 內建的上傳功能,但是隻能在 Servlet 3 以上的版本使用)

這裡我們註冊使用第二個,無需配置額外的架包

<!--配置上傳下載-->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

配置web.xml

<servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-web.xml</param-value>
    </init-param>

    <!--配置檔案的上傳和下載-->
    <load-on-startup>1</load-on-startup>
    <multipart-config>
	  <!--裡面的配置可以在程式碼中實現,也可已在這裡進行配置-->
      <!--<location>D:\IdeaWork\ssm_blog\src\main\webapp\images</location>-->
      <max-file-size>-1</max-file-size>
      <max-request-size>-1</max-request-size>
    </multipart-config>
</servlet>

上傳檔案的後臺程式碼

我們對於檔案的上傳將會以常用的圖片上傳為例進行講解。

//頁面跳轉
@GetMapping("/up")
public String getIndex(Model model) {
    return "upFile";
}


@PostMapping("/up")
public String upFiles(@RequestPart("file") MultipartFile file, HttpServletRequest re,Model model) {

    // 獲得上傳的檔案格式
    String contextType = file.getContentType();

    // 判斷檔案是否符合要求的型別.
    if (!contextTypecontains("image/")) {
        model.addAttribute("msg", "只允許上傳圖片");
        return "upFile";
    }
    // 判斷上傳的檔案大小
    if (file.getSize() > 1024 * 1024 * 5) {
        model.addAttribute("msg", "檔案過大");
        return "upFile";
    }

    try {
        // 獲取當前類載入的路徑
        String path = re.getServletContext().getRealPath(File.separator+"img");
        File img = new File(path);
        // 如果資料夾不存在就建立一個
        if(!img.exists()){
            img.mkdir();
        }
        // file.getOriginalFilename()為檔案的邏輯名
        File fileName= new File(path+File.separator+getNewName(file.getOriginalFilename()));
        // 這裡是寫入檔案,最為重要的一段程式碼
        file.transferTo(fileName);
    } catch (IOException e) {
        model.addAttribute("msg", "寫入失敗");
    }
    return "upFile";
}

表單上傳

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>${msg}</h1>
<form action="/up" method="post" enctype="multipart/form-data">
    <label>檔案上傳:</label>
    <input type="file" name="file"><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

上傳圖片的新需求

上面我們採取的是表單上傳的方式,實際開發中,我們用的更多的是採取非同步上傳的方式。接下來我們提出以下幾點需求:

  • 上傳的圖片左下角加上水印
  • 動態顯示圖片上傳百分比
  • 動態展示上傳的精度
  • 壓縮圖片

後臺程式碼不變,前臺實現

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<input type="file" multiple="multiple" id="myFile"/>
<br/>
<canvas id="myCanvas"></canvas>
<br/>
<button id="myBtn">上傳</button>
<progress value="0" max="100" id="myPro"></progress>
</body>
<script type="text/javascript" src="../js/jquery.js"></script>
<script>

    var myFile = document.getElementById("myFile");
    var myBtn = document.getElementById("myBtn");
    var myCanvas = document.getElementById("myCanvas");
    window.addEventListener("load", () => {
        all();
    });

    function all() {
        myFile.addEventListener("change", () => {
            $("#myPro").val(0);
            loadImg();
        });
        myBtn.addEventListener("click", () => {
            ajax(myFile.files[0]);
        });
    }


    var myImg = new Image();
    var pen = myCanvas.getContext("2d");

    function loadImg(callback) {
        var url = URL.createObjectURL(myFile.files[0]);
        myImg.src = url;
        //console.log(myImg);
        console.log("壓縮前:" + myFile.files[0].size);
        // 這裡是非同步
        myImg.onload = function () {
            URL.revokeObjectURL(url);
            // 壓縮圖片
            myCanvas.width = myImg.width / 2;
            myCanvas.height = myImg.height / 2;
            pen.drawImage(myImg, 0, 0, myImg.width / 2, myImg.height / 2);
            pen.font = "30px yahei";
            pen.fillText("lzx繪製", (myImg.width / 2) - 100, (myImg.height / 2) - 10);
            if (callback) () => callBack();
        }
    }


    function ajax(blob) {
        var form = new FormData();
        form.append("file", blob);
        $.ajax({
            url: '/up',
            type: 'post',
            data: form,
            cache: false,
            contentType: false, //必須false才會自動加上正確的Content-Type
            processData: false,  //必須false才會避開jQuery對 formdata 的預設處理
            xhr: function () {
                var myXhr = $.ajaxSettings.xhr();
                if (myXhr.upload) { // check if upload property exists
                    myXhr.upload.addEventListener('progress', function (e) {
                        var loaded = e.loaded;//已經上傳大小情況
                        var total = e.total;//附件總大小
                        var per = Math.floor(100 * loaded / total);  //已經上傳的百分比
                          if (e.lengthComputable) {
                            $("#myPro").val(per);
                            //清空畫板
                            pen.clearRect(0, 0, myCanvas.width, myCanvas.height);
                            //從左下角不停地畫
                            pen.globalAlpha = 0.5;
                            pen.drawImage(myImg, 0, myCanvas.height, myCanvas.width, -myCanvas.height * per/100);
                            pen.font = "30px yahei";
                            pen.fillText("lzx繪製", (myImg.width / 2) - 100, (myImg.height / 2) - 10);
                            //顯示百分比
                            pen.font = "80px yahei";
                            pen.fillText(per + "%", myCanvas.width / 2 - 20, myCanvas.height / 2 - 20);

                        }
                    }, false);
                }
                return myXhr;
            }
        });
    }
</script>
</html>

檔案的下載

檔案的下載主要是對位元組流的操作,它也是具有一定的套路的,直接複製貼上使用即可。

這裡我們以匯出資料為excel表格並進行下載為例,這裡我們是使用了poi來實現對錶格的操作

service匯出表格資料,存入位元組流

public byte[] writeExcel(List<Employee> list) throws IOException {
    HSSFWorkbook workbook = new HSSFWorkbook();
    HSSFSheet sheet = workbook.createSheet("客戶資訊表");
    HSSFRow row1 = sheet.createRow(0);
    row1.createCell(0).setCellValue("編號");
    row1.createCell(1).setCellValue("姓名");
    row1.createCell(2).setCellValue("性別");
    row1.createCell(3).setCellValue("學歷");
    row1.createCell(4).setCellValue("月薪");

    HSSFRow row = null;
    for (int i = 1; i <= list.size(); i++) {
        employee = list.get(i - 1);
        row = sheet.createRow(i);
        row.createCell(0).setCellValue(employee.getId());
        row.createCell(1).setCellValue(employee.getName());
        row.createCell(2).setCellValue(employee.getSex());
        row.createCell(3).setCellValue(employee.getEducation());
        row.createCell(4).setCellValue(String.valueOf(employee.getSalary()));
    }

    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    workbook.write(stream);
    
    return stream.toByteArray();
}

controller下載表格到本地

@GetMapping("/outExcel")
public ResponseEntity outExcel() throws IOException {

//        HSSFWorkbook workbook = writeExcel();
//        File files = new File( UUID.randomUUID() + ".xls");
//        workbook.write(files);
//        FileSystemResource file = new FileSystemResource(files);
        // 這種方法也是可行的
//        HttpHeaders headers = new HttpHeaders();
//        headers.setCacheControl("no-cache, no-store, must-revalidate");
//        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//        headers.setContentLength(file.contentLength());
//        headers.setContentDispositionFormData("attachment", file.getFilename());
//        return ResponseEntity.ok().headers(headers).body(new InputStreamResource(file.getInputStream()));

		
        byte[] file = service.writeExcel(service.listAll());
        HttpHeaders headers = new HttpHeaders();
        headers.setCacheControl("no-cache, no-store, must-revalidate");
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentLength(file.length);
        headers.setContentDispositionFormData("attachment", UUID.randomUUID()+".xls");
        return ResponseEntity.ok().headers(headers).body(file);

}

我們發現contoller位元組是對byte[]陣列,位元組流進行操作的。拓展使用起來就十分方便了,我們只需要把要下載的檔案轉為位元組流,存入byte[]陣列,再controller中呼叫即可。