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

Apache Commons FileUpload 文件上傳完整實作指南

     編輯:Java 相關知識

技術背景

Apache Commons FileUpload 是 Java Web 開發中處理 multipart/form-data 表單上傳的標準元件,廣泛應用於 Servlet/JSP、Spring MVC 等框架中。本文詳細說明從原理分析到完整實作的各個環節。

核心原理分析

表單上傳必要條件

  1. method="POST":GET 請求無法承載文件二進位資料
  2. enctype="multipart/form-data":告知伺服器此為混合表單資料 (含文字欄位與二進位文件)

預設 application/x-www-form-urlencoded 僅支援鍵值對文字資料,無法處理二進位文件流。

處理流程

  1. 請求到達 Servlet
  2. ServletFileUpload.isMultipartContent(request) 驗證內容類型
  3. DiskFileItemFactory 建立工廠 (可設定暫存目錄、記憶體緩衝閾值)
  4. ServletFileUpload 解析請求,產生 List<FileItem>
  5. 遍歷 FileItem:isFormField() 區分一般欄位與文件欄位
  6. 文件欄位呼叫 item.write(file) 持久化 (自動清理暫存檔)

完整實作範例

前端頁面 (upload.jsp)

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <title>文件上傳示例</title>
    <meta charset="UTF-8">
</head>
<body>
    <h2>多文件上傳測試</h2>
    <form action="${pageContext.request.contextPath}/servlet/UploadServlet" 
          method="post" enctype="multipart/form-data">
        姓名:<input type="text" name="username" /><br/>
        文件1:<input type="file" name="file1" /><br/>
        文件2:<input type="file" name="file2" /><br/>
        <input type="submit" value="上傳" />
    </form>
</body>
</html>

後端 Servlet 完整實作

package com.example.upload;

import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    // 配置常數
    private static final int MAX_FILE_SIZE = 3 * 1024 * 1024;      // 3MB 單檔上限
    private static final int MAX_TOTAL_SIZE = 6 * 1024 * 1024;     // 6MB 總上限
    private static final int MEMORY_THRESHOLD = 5 * 1024;          // 5KB 記憶體緩衝
    private static final String UPLOAD_DIR = "uploads";            // 相對儲存路徑
    private static final String TEMP_DIR = "upload_temp";          // 暫存目錄

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 1. 驗證 multipart 請求
        if (!ServletFileUpload.isMultipartContent(request)) {
            out.write("錯誤:表單必須設定 enctype='multipart/form-data'");
            return;
        }

        // 2. 建立工廠與解析器
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        factory.setRepository(new File(getServletContext().getRealPath("/") + TEMP_DIR));
        
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(MAX_FILE_SIZE);
        upload.setSizeMax(MAX_TOTAL_SIZE);
        upload.setHeaderEncoding("UTF-8"); // 解決中文檔名亂碼

        String realUploadPath = getServletContext().getRealPath("/") + UPLOAD_DIR;
        File uploadDir = new File(realUploadPath);
        if (!uploadDir.exists()) uploadDir.mkdirs();

        try {
            // 3. 解析請求
            List<FileItem> items = upload.parseRequest(request);
            
            // 4. 處理各欄位
            for (FileItem item : items) {
                if (item.isFormField()) {
                    // 一般表單欄位
                    String fieldName = item.getFieldName();
                    String fieldValue = item.getString("UTF-8");
                    System.out.println("表單欄位: " + fieldName + " = " + fieldValue);
                } else {
                    // 文件欄位處理
                    processUploadedFile(item, realUploadPath, out);
                }
            }
            out.write("<script>alert('上傳成功!'); window.history.back();</script>");
            
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            out.write("<script>alert('單一檔案超過 3MB 限制!'); window.history.back();</script>");
        } catch (FileUploadBase.SizeLimitExceededException e) {
            out.write("<script>alert('總檔案大小超過 6MB 限制!'); window.history.back();</script>");
        } catch (Exception e) {
            e.printStackTrace();
            out.write("<script>alert('上傳失敗:" + e.getMessage() + "'); window.history.back();</script>");
        }
    }

    /** 處理單一上傳文件 */
    private void processUploadedFile(FileItem item, String basePath, PrintWriter out) throws Exception {
        String originalName = item.getName();
        if (originalName == null || originalName.isEmpty()) {
            out.write("<script>alert('未選擇檔案!'); window.history.back();</script>");
            return;
        }

        // 安全檔名處理:UUID + 原始副檔名
        String ext = FilenameUtils.getExtension(originalName);
        String safeName = UUID.randomUUID().toString() + "." + ext;
        
        // 依日期建立子目錄 (避免單一目錄檔案過多)
        String dateDir = new java.text.SimpleDateFormat("yyyyMMdd").format(new Date());
        File dateDirFile = new File(basePath, dateDir);
        if (!dateDirFile.exists()) dateDirFile.mkdirs();

        File destFile = new File(dateDirFile, safeName);
        item.write(destFile); // 自動清理暫存檔
        
        System.out.println("檔案已儲存: " + destFile.getAbsolutePath());
        // 此處可記錄資料庫:原始檔名、儲存路徑、上傳者、時間、MIME類型等
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

web.xml 註冊 (Servlet 3.0+ 可改用 @WebServlet 註解)

<servlet>
    <servlet-name>UploadServlet</servlet-name>
    <servlet-class>com.example.upload.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadServlet</servlet-name>
    <url-pattern>/servlet/UploadServlet</url-pattern>
</servlet-mapping>

關鍵技術點深度解析

1. 暫存檔案管理

  • DiskFileItemFactory.setRepository(File):指定暫存目錄 (預設系統暫存目錄)
  • setSizeThreshold(int):記憶體緩衝區大小,超過則寫入暫存檔 (預設 10KB)
  • 最佳實踐:使用 item.write(File) 而非手動 IO 流複製,前者會自動刪除暫存檔,避免磁碟洩漏

2. 中文檔名亂碼解決

  • Servlet 容器預設 ISO-8859-1 編碼解析 Header
  • 解決方案:upload.setHeaderEncoding("UTF-8") (Commons FileUpload 1.3.1+ 支援)
  • 舊版本需手動轉碼:new String(name.getBytes("ISO-8859-1"), "UTF-8")

3. 檔案大小限制

方法作用範圍拋出異常
upload.setFileSizeMax()單一檔案FileSizeLimitExceededException
upload.setSizeMax()整個請求總和SizeLimitExceededException

需在 parseRequest() 前設定,捕獲異常時請求流已讀取完畢,可直接回應錯誤訊息。

4. 檔案類型驗證 (安全性關鍵)

// 僅允許圖片類型
String contentType = item.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
    throw new IllegalArgumentException("僅允許上傳圖片檔案");
}

// 進階:檢查 Magic Number (檔案頭特徵碼) 防止偽裝副檔名
byte[] header = new byte[8];
try (InputStream in = item.getInputStream()) {
    in.read(header);
}
// JPEG: FF D8 FF, PNG: 89 50 4E 47 0D 0A 1A 0A, PDF: 25 50 44 46...

5. 分散式儲存策略 (生產環境建議)

  • 雲端儲存:整合 AWS S3、阿里云 OSS、Google Cloud Storage
  • 分散式檔案系統:FastDFS、MinIO、Ceph
  • 資料庫記錄:僅存檔案元資料 (路徑、MD5、大小、業務關聯 ID),實體檔案存儲於對象儲存

Spring Boot 整合範例 (現代化開發)

@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
    
    @Value("${file.upload.dir:/data/uploads}")
    private String uploadDir;
    
    @PostMapping
    public Result upload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) return Result.error("檔案為空");
        
        // 檔案類型驗證
        String contentType = file.getContentType();
        if (!Arrays.asList("image/jpeg", "image/png", "application/pdf").contains(contentType)) {
            return Result.error("不支援的檔案類型");
        }
        
        // 儲存邏輯...
        String fileName = UUID.randomUUID() + "." + FilenameUtils.getExtension(file.getOriginalFilename());
        Path dest = Paths.get(uploadDir, fileName);
        Files.copy(file.getInputStream(), dest);
        
        return Result.success("/files/" + fileName);
    }
}

application.yml 配置:

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
      file-size-threshold: 2KB

總結

Commons FileUpload 提供了穩健的文件上傳處理能力,核心在於正確配置工廠參數、妥善處理暫存檔案、實施嚴格的安全驗證。現代專案建議優先考慮 Spring Boot 整合的 MultipartFile 抽象層,或直接對接雲端對象儲存服務,將檔案儲存邏輯從應用伺服器剝離,提升擴展性與可維護性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved