Apache Commons FileUpload 是 Java Web 專案處理 multipart/form-data 檔案上傳的經典元件。本文提供基於 Tomcat 6 的 Servlet 完整實作,涵蓋單/多檔上傳、表單欄位處理、記憶體閾值控制、大小限制、暫存目錄配置與資源清理等生產級關鍵細節。
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="myFile" /> <!-- 必須有 name 屬性 -->
<input type="text" name="description" />
<input type="submit" value="上傳" />
</form>
method="post"、enctype="multipart/form-data" 為必要條件。input 標籤必須設定 name 屬性,否則無法取得內容。package org.xxm;
import java.io.*;
import java.util.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
public class UploadFileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 僅支援 POST
return;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1. 驗證是否為 multipart 請求(同時檢查 POST)
if (!ServletFileUpload.isMultipartContent(req)) {
System.out.println("enctype error! 請使用 multipart/form-data");
return;
}
req.setCharacterEncoding("UTF-8");
// 2. 配置 DiskFileItemFactory
DiskFileItemFactory factory = new DiskFileItemFactory();
// 暫存目錄:必須真實存在且可寫入
File repoDir = new File(getServletContext().getRealPath("/WEB-INF/tmp/upload"));
if (!repoDir.exists()) repoDir.mkdirs();
factory.setRepository(repoDir);
// 記憶體閾值:超過此大小改寫入暫存檔(位元組)
factory.setSizeThreshold(1024 * 1000); // 1MB
// 3. 建立 ServletFileUpload 並設定大小限制
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(100 * 1024 * 1024); // 單檔 100MB
upload.setSizeMax(200 * 1024 * 1024); // 總計 200MB
// 可選:設定標頭編碼處理中文檔名
upload.setHeaderEncoding("UTF-8");
List<FileItem> items = null;
try {
items = upload.parseRequest(req);
} catch (FileUploadBase.SizeLimitExceededException e) {
System.out.println("上傳總大小超過限制!");
return;
} catch (Exception e) {
e.printStackTrace();
return;
}
// 4. 遍歷處理每個表單項目
if (items != null) {
for (FileItem item : items) {
if (item.isFormField()) {
// 一般表單欄位
String fieldName = item.getFieldName();
String value = item.getString("UTF-8");
System.out.println("Form field: " + fieldName + " = " + value);
} else {
// 檔案欄位
String originalName = item.getName();
if (originalName == null || originalName.isEmpty()) continue;
// 處理不同瀏覽器傳送的完整路徑,僅保留檔名
String fileName = originalName.substring(originalName.lastIndexOf("\\") + 1);
// 防禦性處理 Unix 路徑分隔符
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
// 目標儲存目錄:專案外部絕對路徑,避免戰爭重新部署遺失
File uploadDir = new File(getServletContext().getRealPath("/WEB-INF/upload"));
if (!uploadDir.exists()) uploadDir.mkdirs();
File destFile = new File(uploadDir, fileName);
try (InputStream in = item.getInputStream();
OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile))) {
Streams.copy(in, out, true); // true = 關閉輸入流
System.out.println("檔案上傳成功:" + destFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
| 參數 | 設定方法 | 建議值/說明 |
|---|---|---|
| 暫存目錄 | factory.setRepository(File) | 專案外 /tmp/upload,確保磁碟空間充足 |
| 記憶體閾值 | factory.setSizeThreshold(int) | 1MB (1024*1000);小檔留記憶體,大檔落地 |
| 單檔上限 | upload.setFileSizeMax(long) | 依業務需求,例 100MB |
| 總上傳上限 | upload.setSizeMax(long) | 略大於單檔上限,防單一請求耗盡資源 |
| 標頭編碼 | upload.setHeaderEncoding(String) | UTF-8,解決中文檔名亂碼 |
DiskFileItemFactory 不會自動刪除暫存檔,需排程清理 repository 目錄,或實作 FileCleaningTracker。UUID + 原始副檔名,避免覆蓋與路徑遍歷攻擊。Content-Type、副檔名、魔數;掃描病毒;儲存於非 Web 可直接存取目錄。Streams.copy 是否正確關閉流、暫存檔是否清理。Commons FileUpload 配置得當可穩定支援中小型檔案上傳。核心在於:正確設定 DiskFileItemFactory 暫存策略、合理限制大小、妥善處理編碼與資源關閉。大檔、高併發、斷點續傳場景建議引入專業上傳元件或雲端儲存服務(OSS/S3)。