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

Apache Commons FileUpload 文件上傳完整教學與實戰代碼

     編輯:Java 相關知識

技術選型對比

上傳組件特點現狀
Apache Commons FileUpload標準、輕量、社群活躍、Spring MVC 底層實作推薦使用
SmartUpload早期商業組件、功能豐富已停止維護
Servlet 3.0 Part API標準 API、無第三方依賴功能較簡單
COS (com.oreilly.servlet)早期經典已停止維護

核心依賴

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

前端表單關鍵點

<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile" />
    <input type="submit" value="上傳" />
</form>

三個必要條件:

  1. method="POST"
  2. enctype="multipart/form-data"
  3. <input type="file">

核心處理流程

  1. DiskFileUpload diskFileUpload = new DiskFileUpload(); (舊版) 或 new ServletFileUpload(factory) (新版)
  2. List<FileItem> list = diskFileUpload.parseRequest(request); 解析請求
  3. 遍歷 List,用 item.isFormField() 區分一般欄位與檔案
  4. 檔案處理:item.getInputStream() 讀取流 → FileOutputStream 寫入

完整 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 javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class FileUploadServlet extends HttpServlet {
    // 配置常數
    private static final String UPLOAD_DIR = "uploads";      // 相對路徑
    private static final String TEMP_DIR = "uploads/temp";   // 暫存目錄
    private static final long MAX_FILE_SIZE = 10 * 1024 * 1024;   // 10MB
    private static final long MAX_REQUEST_SIZE = 50 * 1024 * 1024; // 50MB
    private static final int MEMORY_THRESHOLD = 4 * 1024;         // 4KB
    
    private String realBasePath;

    @Override
    public void init() throws ServletException {
        realBasePath = getServletContext().getRealPath("/");
        // 初始化目錄
        new File(realBasePath, UPLOAD_DIR).mkdirs();
        new File(realBasePath, TEMP_DIR).mkdirs();
    }

    @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.println("錯誤:請使用 multipart/form-data 編碼");
            return;
        }

        // 2. 建立工廠
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        factory.setRepository(new File(realBasePath, TEMP_DIR));
        
        // 3. 建立解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(MAX_FILE_SIZE);
        upload.setSizeMax(MAX_REQUEST_SIZE);
        upload.setHeaderEncoding("UTF-8"); // 解決中文檔名

        try {
            // 4. 解析請求
            List<FileItem> items = upload.parseRequest(request);
            
            String savedFilePath = null;
            String description = "";
            
            for (FileItem item : items) {
                if (item.isFormField()) {
                    // 一般表單欄位
                    if ("description".equals(item.getFieldName())) {
                        description = item.getString("UTF-8");
                    }
                } else {
                    // 檔案欄位
                    String fileName = item.getName();
                    if (fileName == null || fileName.isEmpty()) continue;
                    
                    // 安全檔名處理
                    String originalName = new File(fileName).getName();
                    String ext = "";
                    int dotIdx = originalName.lastIndexOf('.');
                    if (dotIdx > 0) ext = originalName.substring(dotIdx);
                    
                    String savedName = UUID.randomUUID().toString() + ext;
                    File savedFile = new File(new File(realBasePath, UPLOAD_DIR), savedName);
                    
                    // 5. 寫入檔案 (自動清理暫存)
                    item.write(savedFile);
                    savedFilePath = UPLOAD_DIR + "/" + savedName;
                    
                    out.println("檔案已儲存: " + savedFile.getAbsolutePath() + "<br/>");
                }
            }
            
            // 顯示結果
            out.println("<h3>上傳成功</h3>");
            if (savedFilePath != null) {
                out.println("<a href='" + request.getContextPath() + "/" + savedFilePath + "' target='_blank'>檢視檔案</a><br/>");
            }
            out.println("說明: " + description);
            
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            out.println("錯誤:單一檔案超過 " + (MAX_FILE_SIZE/1024/1024) + "MB 限制");
        } catch (FileUploadBase.SizeLimitExceededException e) {
            out.println("錯誤:總上傳大小超過 " + (MAX_REQUEST_SIZE/1024/1024) + "MB 限制");
        } catch (Exception e) {
            e.printStackTrace();
            out.println("錯誤:" + e.getMessage());
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println("請使用 POST 方法上傳檔案");
    }
}

前端頁面 (upload.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>檔案上傳</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 2rem auto; }
        .form-group { margin-bottom: 1rem; }
        label { display: block; margin-bottom: 0.5rem; font-weight: bold; }
        input[type="file"] { border: 1px solid #ccc; padding: 0.5rem; width: 100%; }
        button { background: #007bff; color: white; border: none; padding: 0.75rem 1.5rem; cursor: pointer; }
        button:hover { background: #0056b3; }
        .result { margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; }
    </style>
</head>
<body>
    <h2>檔案上傳測試</h2>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label>檔案說明</label>
            <input type="text" name="description" placeholder="選填" />
        </div>
        <div class="form-group">
            <label>選擇檔案 (最大 10MB)</label>
            <input type="file" name="myfile" required />
        </div>
        <button type="submit">上傳檔案</button>
    </form>
    <div class="result" id="result" style="display:none;"></div>
</body>
</html>

web.xml 配置

<web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee" version="3.1">
    <servlet>
        <servlet-name>FileUploadServlet</servlet-name>
        <servlet-class>com.example.upload.FileUploadServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>FileUploadServlet</servlet-name>
        <url-pattern>/upload</url-pattern>
    </servlet-mapping>
</web-app>

關鍵技術點深度解析

1. 暫存檔案機制

  • 上傳檔案超過 sizeThreshold (預設 10KB) 時,寫入暫存目錄
  • DiskFileItemFactory.setRepository(File) 指定暫存目錄
  • 關鍵:使用 item.write(File) 會自動刪除暫存檔,手動用 getInputStream() 則需自行清理

2. 中文檔名亂碼解決

// 方案一:Commons FileUpload 1.3.1+ 內建支援
upload.setHeaderEncoding("UTF-8");

// 方案二:手動轉碼 (舊版本)
String fileName = item.getName();
String decodedName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");

3. 檔案大小限制雙重保護

設定範圍異常類型
upload.setFileSizeMax()單一檔案FileSizeLimitExceededException
upload.setSizeMax()整個請求SizeLimitExceededException

捕獲異常時請求流已讀完,可直接回應錯誤頁面。

4. 安全性強化清單

  1. 檔案類型驗證:副檔名 + MIME Type + Magic Number 三重檢查
  2. 檔名安全化:移除路徑資訊,使用 UUID/MD5 重命名
  3. 儲存路徑隔離:儲存於 WebRoot 外或配置 Nginx 禁止腳本執行
  4. 大小限制:前端 JS + 後端雙重驗證
  5. 防毒掃描:整合 ClamAV 或雲端安全服務
  6. 權限控制:驗證用戶身份、權限後才允許上傳

Spring Boot 整合 (現代標準做法)

配置類

@Configuration
public class MultipartConfig {
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(50 * 1024 * 1024); // 50MB
        resolver.setMaxUploadSizePerFile(10 * 1024 * 1024); // 10MB
        resolver.setDefaultEncoding("UTF-8");
        return resolver;
    }
}

Controller

application.yml

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
      file-size-threshold: 2KB
      location: /tmp/uploads # 暫存目錄

總結

Apache Commons FileUpload 是 Java 檔案上傳的基石組件,掌握其核心流程 (工廠→解析器→FileItem→寫入) 與關鍵配置 (暫存、大小、編碼) 至關重要。現代 Spring Boot 專案建議直接使用 MultipartFile 抽象層,配合 CommonsMultipartResolver 或 StandardServletMultipartResolver,享受聲明式配置與自動綁定便利。無論何種架構,安全驗證、暫存管理、檔名處理永遠是不可省略的核心環節。

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