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

Apache Commons FileUpload 多檔案上傳完整範例集錦

     編輯:Java 相關知識

專案簡介

本文提供四個層層遞進的 Commons FileUpload 實作範例:從最基本的單檔解析、多檔上傳、大小限制,到完整的 Servlet 封裝與 web.xml 配置,涵蓋實務開發最常見的需求場景。

環境準備

  • commons-fileupload-1.2.jar (或更新版 1.4+)
  • commons-io-1.3.2.jar (FileUpload 依賴)
  • 將 JAR 放入 WEB-INF/lib 目錄

範例一:最基礎 JSP 內嵌解析 (demo1)

前端表單 (demo1.html)

<form action="demo1.jsp" method="post" enctype="multipart/form-data">
    您的姓名: <input type="text" name="username"><br>
    上傳檔案: <input type="file" name="myfile"><br>
    <input type="submit" value="送出">
</form>

JSP 處理邏輯 (demo1.jsp)

<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*" %>
<%
    boolean isMultipart = ServletFileUpload.isMultipartContent(request);
    if (isMultipart) {
        FileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        List<FileItem> items = upload.parseRequest(request);
        
        for (FileItem item : items) {
            if (item.isFormField()) {
                // 一般欄位
                if ("username".equals(item.getFieldName())) {
                    out.print("姓名: " + item.getString() + "<br>");
                }
            } else {
                // 檔案欄位
                out.print("上傳檔名: " + item.getName() + "<br>");
            }
        }
    } else {
        out.print("請設定 enctype='multipart/form-data'");
    }
%>

範例二:多檔案上傳至指定目錄 (demo2)

前端 (demo2.html) - 兩個同名 file 欄位

<form action="demo2.jsp" method="post" enctype="multipart/form-data">
    檔案1: <input type="file" name="myfile"><br>
    檔案2: <input type="file" name="myfile"><br>
    <input type="submit" value="上傳">
</form>

JSP 儲存邏輯 (demo2.jsp)

<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*, java.io.*" %>
<%
    String uploadPath = "D:/uploads";
    File dir = new File(uploadPath);
    if (!dir.exists()) dir.mkdirs();
    
    if (ServletFileUpload.isMultipartContent(request)) {
        try {
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            List<FileItem> items = upload.parseRequest(request);
            
            for (FileItem item : items) {
                if (!item.isFormField()) {
                    String fileName = item.getName();
                    if (fileName != null) {
                        File fullFile = new File(fileName);
                        File savedFile = new File(uploadPath, fullFile.getName());
                        item.write(savedFile);
                    }
                }
            }
            out.print("上傳成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
%>

範例三:單檔上傳 + 大小限制 + 暫存目錄配置 (demo3)

<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*, java.io.*" %>
<%
    File uploadDir = new File("D:/uploads");
    File tempDir = new File("D:/uploads/temp");
    uploadDir.mkdirs(); tempDir.mkdirs();
    
    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setSizeThreshold(4096);        // 4KB 記憶體緩衝
    factory.setRepository(tempDir);         // 暫存目錄
    
    ServletFileUpload upload = new ServletFileUpload(factory);
    upload.setSizeMax(4 * 1024 * 1024);    // 4MB 總大小限制
    
    List<FileItem> items = upload.parseRequest(request);
    for (FileItem item : items) {
        if (!item.isFormField() && item.getName() != null) {
            File saved = new File(uploadDir, new File(item.getName()).getName());
            item.write(saved);
        }
    }
    out.print("上傳成功");
%>

範例四:標準 Servlet 封裝 + web.xml 配置 (最佳實踐)

UploadServlet.java

package com.example.upload;

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

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

public class UploadServlet extends HttpServlet {
    private String uploadPath = "D:/uploads";
    private String tempPath = "D:/uploads/temp";
    private File tempDir;

    @Override
    public void init() throws ServletException {
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) uploadDir.mkdirs();
        tempDir = new File(tempPath);
        if (!tempDir.exists()) tempDir.mkdirs();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(4096);
        factory.setRepository(tempDir);
        
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setSizeMax(4 * 1024 * 1024); // 4MB
        
        try {
            List<FileItem> items = upload.parseRequest(req);
            for (FileItem item : items) {
                if (!item.isFormField()) {
                    String fileName = item.getName();
                    if (fileName != null) {
                        File saved = new File(uploadPath, new File(fileName).getName());
                        item.write(saved);
                    }
                }
            }
            System.out.println("上傳成功");
        } catch (Exception e) {
            e.printStackTrace();
            // 生產環境應導向錯誤頁面
        }
    }
}

前端表單 (demo4.html)

<form action="fileupload" method="post" enctype="multipart/form-data">
    檔案: <input type="file" name="myfile"><br>
    <input type="submit" value="上傳">
</form>

web.xml 配置

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

核心 API 深度解析

類別/方法用途關鍵參數
DiskFileItemFactory建立 FileItem 工廠setSizeThreshold(記憶體閾值), setRepository(暫存目錄)
ServletFileUpload解析 multipart 請求parseRequest(request) → List<FileItem>
FileItem代表單一表單項目isFormField(), getName(), getString(), write(File), getInputStream()
upload.setFileSizeMax()單檔大小限制bytes, 拋出 FileSizeLimitExceededException
upload.setSizeMax()請求總大小限制bytes, 拋出 SizeLimitExceededException
upload.setHeaderEncoding()解決中文檔名亂碼"UTF-8" (1.3.1+ 版本支援)

進階實務技巧

1. 檔案類型驗證 (雙重檢查)

// 副檔名檢查
String fileName = item.getName().toLowerCase();
if (!fileName.matches(".*\\.(jpg|jpeg|png|gif|pdf)$")) {
    throw new IllegalArgumentException("不允許的檔案類型");
}

// MIME 類型檢查
String contentType = item.getContentType();
if (!Arrays.asList("image/jpeg", "image/png", "application/pdf").contains(contentType)) {
    throw new IllegalArgumentException("MIME 類型不符");
}

// Magic Number 檢查 (最可靠)
byte[] header = new byte[8];
try (InputStream in = item.getInputStream()) { in.read(header); }
boolean isJpeg = header[0]==(byte)0xFF && header[1]==(byte)0xD8;
boolean isPng = header[0]==(byte)0x89 && header[1]==(byte)0x50;
if (!isJpeg && !isPng) throw new IllegalArgumentException("檔案頭不符");

2. 唯一檔名生成策略

// 策略一:UUID + 原始副檔名
String savedName = UUID.randomUUID() + "." + FilenameUtils.getExtension(fileName);

// 策略二:時間戳 + 隨機數
String savedName = System.currentTimeMillis() + "_" + new Random().nextInt(10000) + "." + ext;

// 策略三:MD5 雜湊 + 副檔名 (去重)
String md5 = DigestUtils.md5Hex(item.getInputStream());
String savedName = md5 + "." + ext;

3. 分散式儲存路徑設計 (避免單目錄檔案過多)

// 方案一:日期分層
String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
// 儲存路徑: /uploads/2024/01/15/uuid.jpg

// 方案二:檔名雜湊分桶
int hash = fileName.hashCode();
int dir1 = hash & 0xF;        // 0-15
int dir2 = (hash >> 4) & 0xF; // 0-15
String hashPath = dir1 + "/" + dir2;
// 儲存路徑: /uploads/3/A/uuid.jpg (256 個子目錄)

4. 異常處理最佳實踐

try {
    List<FileItem> items = upload.parseRequest(request);
    // 處理邏輯...
} catch (FileUploadBase.FileSizeLimitExceededException e) {
    // 單檔超過限制
    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "檔案過大: " + e.getMessage());
} catch (FileUploadBase.SizeLimitExceededException e) {
    // 總大小超過限制
    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "總上傳量過大: " + e.getMessage());
} catch (FileUploadException e) {
    // 解析錯誤 (非 multipart、連線中斷等)
    log.error("上傳解析失敗", e);
    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "上傳處理異常");
} catch (Exception e) {
    log.error("未預期錯誤", e);
    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "系統錯誤");
}

從 JSP 遷移到 Servlet/Controller 的理由

  1. 關注點分離:視圖邏輯與業務邏輯分離,便於測試與維護
  2. 異常處理:Servlet 可集中處理異常、導向錯誤頁、記錄日誌
  3. 安全性:避免 JSP 直接暴露伺服器路徑、資料庫操作等敏感代碼
  4. 擴展性:易於加入攔截器、過濾器、AOP 切面 (驗證、日誌、限流)
  5. 團隊協作:前端工程師專注模板,後端工程師專注 Controller

現代化替代方案對比

方案優點缺點建議場景
Commons FileUpload + Servlet零依賴框架、完全控制、效能好代碼較繁瑣、需手動管理暫存純 Servlet 專案、微服務輕量節點
Spring MVC MultipartFile自動綁定、聲明式配置、整合驗證依賴 Spring 生態Spring Boot/Spring MVC 專案 (首選)
Servlet 3.0 @MultipartConfig標準 API、無第三方依賴功能較簡單、大檔案處理較弱Java EE 標準相容、不想引入額外 JAR
雲端直傳 (OSS/S3 Presigned URL)減輕應用伺服器壓力、高併發架構複雜、需前端配合大檔案、高併發、雲原生架構

總結

Commons FileUpload 是 Java 檔案上傳的經典解決方案,四個範例涵蓋了從原型驗證到生產部署的完整演進路徑。實務開發中建議:

  1. 採用 Servlet/Controller 架構而非 JSP 內嵌代碼
  2. 實施三層驗證:副檔名 + MIME + Magic Number
  3. 配置暫存目錄、大小限制、編碼設定
  4. 採用分層儲存路徑 + UUID 命名避免衝突
  5. Spring 專案直接使用 MultipartFile 簡化開發
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved