文件上傳是 Web 開發高頻需求。Spring MVC 提供 MultipartFile 封裝簡化開發,但理解底層 Commons FileUpload 手動解析流程,有助於處理大文件、進度監聽、安全校驗等進階場景。本文對比兩種實現方式並給出生產級最佳實踐。
<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>
DiskFileItemFactory:配置內存閾值(超過寫臨時文件)、臨時目錄。ServletFileUpload:設置編碼、單文件/總大小限制、進度監聽器。upload.parseRequest(request) → List。FileItem:isFormField() 區分普通字段與文件。UUID + 原文件名 防衝突,hash 分目錄 防單目錄過多,流式寫入 FileOutputStream。item.delete() 刪除臨時文件。DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(100 * 1024); // 100KB 閾值
factory.setRepository(tmpDir);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(20 * 1024 * 1024); // 20MB
upload.setSizeMax(40 * 1024 * 1024); // 40MB
List items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) { /* 普通字段 */ }
else { /* 文件字段 */
String filename = item.getName().substring(item.getName().lastIndexOf("\\")+1);
String ext = filename.substring(filename.lastIndexOf(".")+1);
if ("rar".equals(ext) || "zip".equals(ext)) throw new BizException("禁止上傳壓縮包");
try (InputStream in = item.getInputStream();
FileOutputStream out = new FileOutputStream(targetFile)) {
IOUtils.copy(in, out); // 或手動 byte[] buffer 循環
}
item.delete();
}
}
MultipartFile —— 開箱即用<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="41943040"/> <!-- 40MB -->
<property name="maxInMemorySize" value="102400"/> <!-- 100KB -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
或 Spring Boot application.yml:
spring:
servlet:
multipart:
max-file-size: 20MB
max-request-size: 40MB
file-size-threshold: 100KB
@PostMapping("/upload/resume")
public String uploadResume(@RequestParam("file") MultipartFile file,
@RequestParam(value = "oldUrl", required = false) String oldUrl,
HttpServletRequest request) {
// 1. 刪除舊文件
if (oldUrl != null) deleteDir(new File(uploadRoot + oldUrl));
// 2. 校驗
if (file.isEmpty()) return "4"; // 文件未獲取
String original = StringUtils.deleteWhitespace(file.getOriginalFilename());
String ext = original.substring(original.lastIndexOf(".")).toLowerCase();
if (!Set.of(".doc",".docx",".xls",".xlsx",".ppt",".pptx",".pdf").contains(ext))
return "6"; // 類型不合法
// 3. 存儲:時間戳目錄 + 原文件名
String dir = uploadRoot + File.separator + System.currentTimeMillis();
File target = new File(dir, original);
target.getParentFile().mkdirs();
try { file.transferTo(target); }
catch (IOException e) { return "3"; }
return File.separator + "files" + File.separator + ...; // 返回相對路徑
}
transferTo() 底層優化:若是臨時文件直接 renameTo 避免拷貝。| 維度 | 措施 |
|---|---|
| 安全 | 1. 白名單校驗擴展名/MIME 類型 2. 文件內容掃毒 3. 存儲目錄不可直接 Web 訪問(放 WEB-INF 或對象存儲) 4. 文件名去特殊字符、UUID 重命名 |
| 性能 | 1. 大文件分片上傳 + 合併 2. 異步處理(消息隊列)3. 對象存儲(S3/OSS)直傳,後端只簽名 |
| 可靠性 | 1. 斷點續傳(記錄已上傳分片)2. 上傳完成異步校驗 MD5 3. 失敗自動重試/告警 |
| 監控 | 1. 上傳耗時/大小/成功率指標 2. 進度回調(WebSocket/SSE) |
| 維度 | 手動解析 | MultipartFile |
|---|---|---|
| 代碼量 | 大(~80 行) | 小(~30 行) |
| 靈活度 | 極高(流程每步可控) | 中(受限於封裝行為) |
| 學習成本 | 高(需懂 FileItem/Factory) | 低 |
| 推薦場景 | 進度條、掃毒、非 Spring、特殊協議 | 常規業務、快速開發、Spring Boot |
建議:常規業務首選 MultipartFile;進階需求在 MultipartFile 基礎上擴展(如繼承 MultipartResolver 自定義解析),避免重複造輪子。