Commons FileUpload 文件上傳完整實戰:原理、配置、亂碼、限流、臨時文件管理
Apache Commons FileUpload 是 Servlet 時代處理 multipart/form-data 請求的標準組件。本文從原理到生產級代碼,全方位講解文件上傳的核心知識點。
一、文件上傳必要條件
| 條件 | 設置 | 說明 |
| Form Method | POST | GET 無請求體,無法承載文件數據 |
| Enctype | multipart/form-data | 默認 application/x-www-form-urlencoded 不支持文件 |
二、核心原理
- 請求到達 Servlet,
ServletFileUpload.isMultipartContent(request) 判斷類型
DiskFileItemFactory 創建 FileItem 工廠(控制臨時文件閾值、目錄)
ServletFileUpload 解析請求 → List
- 遍歷
FileItem:
isFormField() 區分普通字段 vs 文件
- 普通字段:
getString(encoding)
- 文件字段:
getName()、getInputStream()、write(File)
三、Maven 依賴
<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.6</version>
</dependency>
四、完整 Servlet 實現
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
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. 校驗 Content-Type
if (!ServletFileUpload.isMultipartContent(request)) {
out.write("請設置 enctype='multipart/form-data'");
return;
}
// 2. 配置工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File("/tmp/upload")); // 臨時文件目錄
factory.setSizeThreshold(1024 * 1024); // 1MB 內存閾值,超過寫磁盤
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(3 * 1024 * 1024); // 單文件 3MB
upload.setSizeMax(6 * 1024 * 1024); // 總大小 6MB
upload.setHeaderEncoding("UTF-8"); // 解決文件名亂碼
// 3. 解析請求
List items;
try {
items = upload.parseRequest(request);
} catch (FileUploadBase.FileSizeLimitExceededException e) {
out.write("單文件超過 3MB"); return;
} catch (FileUploadBase.SizeLimitExceededException e) {
out.write("總大小超過 6MB"); return;
} catch (FileUploadException e) {
throw new RuntimeException("解析失敗", e);
}
// 4. 處理每個部分
for (FileItem item : items) {
if (item.isFormField()) {
processFormField(item, out);
} else {
processUploadField(item, out);
}
}
out.write("上傳成功!");
}
private void processFormField(FileItem item, PrintWriter out) {
String fieldName = item.getFieldName();
String fieldValue;
try {
fieldValue = item.getString("UTF-8"); // 指定編碼
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("不支持 UTF-8", e);
}
System.out.println(fieldName + "=" + fieldValue);
}
private void processUploadField(FileItem item, PrintWriter out) {
String fileName = item.getName();
if (fileName == null || fileName.isEmpty()) return; // 未選文件
// 1. 生成唯一文件名:UUID + 原文件名
fileName = UUID.randomUUID() + "_" + FilenameUtils.getName(fileName);
String extension = FilenameUtils.getExtension(fileName);
// 2. 內容類型校驗(只允許圖片)
String contentType = item.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
out.write("只允許上傳圖片"); return;
}
// 3. 分目錄存儲:按文件名 hashCode 兩級目錄
String baseDir = getServletContext().getRealPath("/WEB-INF/files");
String subDir = makeSubDir(baseDir, fileName);
File storeDir = new File(baseDir, subDir);
if (!storeDir.exists()) storeDir.mkdirs();
// 4. 寫入文件(自動清理臨時文件)
try {
File dest = new File(storeDir, fileName);
item.write(dest); // 關鍵:write() 會自動刪除臨時文件
System.out.println("存儲至: " + dest.getAbsolutePath());
} catch (Exception e) {
throw new RuntimeException("寫入失敗", e);
}
}
private String makeSubDir(String baseDir, String fileName) {
int hash = fileName.hashCode();
int d1 = hash & 0xF; // 低 4 位
int d2 = (hash & 0xF0) >> 4; // 次低 4 位
String dir = d1 + File.separator + d2;
new File(baseDir, dir).mkdirs();
return dir;
}
}
五、關鍵問題專題
1. 亂碼雙重保障
- 普通字段:
item.getString("UTF-8") 指定編碼
- 文件名:
upload.setHeaderEncoding("UTF-8") + 確保頁面 <meta charset="UTF-8">
2. 臨時文件管理
| 方法 | 行為 | 建議 |
item.write(File) | 寫入目標文件,**自動刪除臨時文件** | 首選 |
item.getInputStream() | 手動流拷貝,**需手動調用 item.delete() 清理臨時文件** | 特殊需求時使用 |
factory.setSizeThreshold(1MB) | 小文件內存中,大文件寫磁盤臨時文件 | 平衡性能與內存 |
factory.setRepository(dir) | 指定臨時文件目錄 | 避免系統盤寫滿 |
3. 文件大小限制
upload.setFileSizeMax(3MB):單文件上限
upload.setSizeMax(6MB):總請求上限
- 超過自動拋出
FileSizeLimitExceededException / SizeLimitExceededException
4. 安全加固清單
- 文件類型驗證:
contentType.startsWith("image/") + 擴展名白名單
- 文件名重命名:UUID + 原名,防止路徑遍歷、覆蓋、特殊字符
- 存儲目錄隔離:
/WEB-INF/files 不可直接 Web 訪問
- 病毒掃描:集成 ClamAV 等殺毒引擎(生產環境)
六、Spring MVC 整合(補充)
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="6291456"/> <!-- 6MB -->
<property name="maxUploadSizePerFile" value="3145728"/> <!-- 3MB -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
@Controller
public class FileController {
@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file) {
// Spring 自動封裝,內部同樣用 Commons FileUpload
// file.transferTo(destFile); // 自動清理臨時文件
}
}
Commons FileUpload 雖然歷史悠久,但原理清晰、功能完備,掌握其核心 API 與最佳實踐,能應對絕大多數 Servlet 環境下的文件上傳場景。
- 上一篇文章: 《編寫高質量代碼》第 8 章異常處理精華:建議 114-117 深度解讀與實踐
- 下一篇文章: Hibernate 核心查詢方式:HQL 與 QBC 詳解
Copyright ©
程式碼之家 All Rights Reserved