首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring MVC 文件上傳下載完整實戰:五種上傳方式、多文件上傳、流式下載與安全實踐

     編輯:Java 綜合教學

Spring MVC 文件上傳下載完整實戰:五種上傳方式、多文件上傳、流式下載與安全實踐

文件上傳下載是 Web 開發高頻需求。本文基於 Spring MVC 框架,系統講解:五種上傳實現方式、多文件上傳、兩種下載模式、工具類封裝、Spring 配置、前端測試頁面,並分析各方式優缺點與適用場景。

一、核心工具類封裝:Files_Helper_DG

統一文件保存邏輯:按日期分目錄、UUID 重命名防衝突、返回相對路徑供 DB 存儲。

public final class Files_Helper_DG {
    private Files_Helper_DG() { throw new Error("工具類不可實例化"); }

    // Spring MultipartFile.transferTo() 方式保存
    public static String FilesUpload_transferTo_spring(HttpServletRequest request,
                                                       MultipartFile multipartFile,
                                                       String filePath) {
        // 1. 構建保存路徑:項目實際路徑 + 相對路徑 + 日期目錄
        String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date());
        String savePath = request.getSession().getServletContext().getRealPath(filePath)
                + File.separator + datePath;
        File dir = new File(savePath);
        if (!dir.exists()) dir.mkdirs();

        // 2. 生成唯一文件名:UUID + 原始後綴
        String suffix = multipartFile.getOriginalFilename()
                .substring(multipartFile.getOriginalFilename().lastIndexOf("."));
        String uuidName = UUID.randomUUID().toString() + suffix;
        String fullPath = savePath + File.separator + uuidName;
        String relativePath = filePath + File.separator + datePath + File.separator + uuidName;

        // 3. 保存文件
        try {
            multipartFile.transferTo(new File(fullPath));
            return relativePath;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 流式下載:設置響應頭、輸出流寫入
    public static void FilesDownload_servlet(HttpServletRequest request,
                                             HttpServletResponse response,
                                             String filePath) {
        String realPath = request.getSession().getServletContext().getRealPath(filePath);
        File file = new File(realPath);
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
             BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {

            response.reset();
            response.setContentType("application/octet-stream");
            response.addHeader("Content-Disposition",
                    "attachment;filename=" + new String(file.getName().getBytes("utf-8"), "iso8859-1"));
            response.addHeader("Content-Length", String.valueOf(file.length()));

            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二、控制器實現:FileUploadController

方式一:@RequestParam + MultipartFile(最常用、最簡潔)

@RequestMapping("fileUpload_multipartFile")
@ResponseBody
public String fileUpload_multipartFile(HttpServletRequest request,
                                       @RequestParam("file_upload") MultipartFile file) {
    String path = Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
    return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\",\"filePath\":\"" + path + "\"}";
}

方式二:MultipartHttpServletRequest 手動獲取

@RequestMapping("fileUpload_multipartRequest")
@ResponseBody
public String fileUpload_multipartRequest(HttpServletRequest request) {
    MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) request;
    MultipartFile file = mpRequest.getFile("file_upload");
    String path = Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
    return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\",\"filePath\":\"" + path + "\"}";
}

方式三:CommonsMultipartResolver 自動掃描所有文件域

@RequestMapping("fileUpload_CommonsMultipartResolver")
@ResponseBody
public String fileUpload_CommonsMultipartResolver(HttpServletRequest request) {
    CommonsMultipartResolver resolver = new CommonsMultipartResolver(
            request.getSession().getServletContext());
    if (resolver.isMultipart(request)) {
        MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) request;
        Iterator<String> it = mpRequest.getFileNames();
        while (it.hasNext()) {
            MultipartFile file = mpRequest.getFile(it.next());
            Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
        }
    }
    return "upload success!";
}

方式四:原生流式處理 - Apache FileUpload(底層控制,適合大文件/斷點續傳)

@RequestMapping("fileUpload_stream")
@ResponseBody
public String fileUpload_stream(HttpServletRequest request) {
    String savePath = request.getSession().getServletContext().getRealPath("/filesOut/Upload");
    File dir = new File(savePath);
    if (!dir.exists()) dir.mkdirs();

    try {
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("UTF-8");

        if (!ServletFileUpload.isMultipartContent(request)) {
            return "非表單上傳數據";
        }

        List<FileItem> items = upload.parseRequest(request);
        for (FileItem item : items) {
            if (item.isFormField()) {
                // 普通表單字段
                System.out.println(item.getFieldName() + "=" + item.getString("UTF-8"));
            } else {
                // 文件字段
                String fileName = item.getName();
                if (fileName == null || fileName.trim().isEmpty()) continue;
                String suffix = fileName.substring(fileName.lastIndexOf("."));
                try (InputStream in = item.getInputStream();
                     FileOutputStream out = new FileOutputStream(savePath + "/stream_" + suffix)) {
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = in.read(buffer)) > 0) {
                        out.write(buffer, 0, len);
                    }
                }
                item.delete(); // 清理臨時文件
            }
        }
        return "upload success!";
    } catch (Exception e) {
        e.printStackTrace();
        return "upload fail";
    }
}

方式五:多文件上傳 - MultipartFile 數組

@RequestMapping("fileUpload_spring_list")
@ResponseBody
public String fileUpload_spring_list(HttpServletRequest request,
                                     @RequestParam("file_upload") MultipartFile[] files) {
    if (files == null || files.length == 0) {
        return "{\"TFMark\":\"false\",\"Msg\":\"參數錯誤\"}";
    }
    try {
        for (MultipartFile file : files) {
            Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
        }
        return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\"}";
    } catch (Exception e) {
        return "{\"TFMark\":\"false\",\"Msg\":\"參數傳遞有誤\"}";
    }
}

三、Spring MVC 關鍵配置:spring-servlet.xml

<mvc:annotation-driven />
<context:component-scan base-package="HelloSpringMVC.controller" />

<!-- 靜態資源映射:讓上傳文件可直接訪問 -->
<mvc:resources location="/filesOut/" mapping="/filesOut/**" />

<!-- 多部分解析器:必須配置 id=multipartResolver -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="104857600" /> <!-- 100MB -->
    <property name="maxInMemorySize" value="4096" />
    <property name="defaultEncoding" value="UTF-8" />
</bean>

四、前端測試頁面:FileUpload.jsp

包含 5 個表單分別測試 5 種上傳方式,2 個下載鏈接測試直接訪問與流式下載。

<form action="/FileUpload/fileUpload_multipartFile" method="post" enctype="multipart/form-data">
    <input type="file" name="file_upload"> <input type="submit" value="upload">
</form>
<!-- 其他 4 個 form 結構類似,action 指向對應 mapping -->

<!-- 下載測試 -->
<a href="<%=basePath%>filesOut/Download/mst.txt">直接訪問下載</a>
<a href="/FileUpload/fileDownload_servlet">流式下載(安全)</a>

五、文件下載實現

@RequestMapping("fileDownload_servlet")
public void fileDownload_servlet(HttpServletRequest request, HttpServletResponse response) {
    Files_Helper_DG.FilesDownload_servlet(request, response, "/filesOut/Download/mst.txt");
}

六、安全性對比:直接訪問 vs 流式下載

七、測試驗證結果

五種上傳方式均成功,文件按 /filesOut/Upload/20170118/uuid.xxx 存儲。流式下載正常彈出保存對話框,直接訪問在瀏覽器打開或下載。

注意:方式四(原生流)測試中發現文件損壞問題,需進一步排查 FileItem.getInputStream() 讀取邏輯,生產環境建議優先使用 Spring 封裝的 MultipartFile.transferTo()。

八、總結

  • 首選方式一:@RequestParam MultipartFile,代碼最少、可讀性最好、Spring 原生支持。
  • 多文件用方式五:數組接收,邏輯同單文件。
  • 複雜場景用方式三:動態表單、未知文件域名稱。
  • 下載首選流式:安全、可控、可擴展。
  • 必須配置:multipartResolver、mvc:resources 映射上傳目錄、enctype="multipart/form-data"。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方式優點缺點建議場景
直接訪問 <a href="/filesOut/..."> 簡單、性能高、支持斷點續傳 暴露目錄結構、無權限控制、無法統計下載 靜態公共資源、CDN 分發
流式下載 Response.getOutputStream() 隱藏真實路徑、可攔截權限、可記錄日志、動態生成文件 佔用服務器 IO/內存、大文件需分塊傳輸 私有文件、需權限控制、需審計日志