文件上傳下載是 Web 開發核心功能。本文提供基於 Spring MVC 的完整 XML 配置、Controller 實現、大文件處理、中文文件名編碼解決方案及進階優化建議。
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<!-- 位元組流轉換器,支援文件下載 -->
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<!-- 字符串轉換器,指定 UTF-8 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json; charset=UTF-8"/>
</bean>
</list>
</property>
</bean>
@RequestMapping("/file/{fileName:.+}")
public ResponseEntity<byte[]> download(@PathVariable String fileName,
HttpServletRequest request, HttpServletResponse response) {
ResponseEntity<byte[]> result = null;
try {
// 1. 安全處理文件名(防止路徑遍歷)
String safeName = sanitizeFileName(fileName);
// 2. 讀取文件(示例路徑,實際應配置外部存儲路徑)
String basePath = "C:\\tempDirectory\\";
File file = new File(basePath + safeName);
if (!file.exists()) throw new FileNotFoundException();
// 3. 讀取位元組
byte[] content = FileUtils.readFileToByteArray(file); // Apache Commons IO
// 4. 構建響應頭
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(content.length);
// 5. 解決中文文件名亂碼(RFC 5987 標準)
String encodedName = URLEncoder.encode(safeName, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");
headers.setContentDispositionFormData("attachment", encodedName);
result = new ResponseEntity<>(content, headers, HttpStatus.OK);
} catch (Exception e) {
log.error("文件下載失敗", e);
// 可轉發到錯誤頁或返回 404
}
return result;
}
/** 文件名安全過濾 */
private String sanitizeFileName(String name) {
return name.replaceAll("[\\\\/:*?\"<>|]", "_"); // 移除非法字符
}
<!-- 文件上傳解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<!-- 最大上傳 1GB -->
<property name="maxUploadSize" value="1073741824"/>
<!-- 內存閾值 40KB,超過寫入臨時文件 -->
<property name="maxInMemorySize" value="40960"/>
</bean>
需依賴:commons-fileupload、commons-io
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public JsonResult upload(Doc doc, @RequestParam("uploadFile") MultipartFile file) {
JsonResult result = new JsonResult();
try {
// 1. 獲取上傳目錄(建議配置在屬性文件)
String uploadDir = servletContext.getRealPath("/upload");
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
// 2. 原始文件名處理
String originalName = file.getOriginalFilename();
String safeName = originalName.replaceAll("\\s+", ""); // 去空格
String ext = safeName.substring(safeName.lastIndexOf("."));
// 3. 生成唯一文件名(防覆蓋):UUID + 原名 或 時間戳 + 原名
String storedName = UUID.randomUUID().toString() + ext;
File target = new File(dir, storedName);
// 4. 保存文件
file.transferTo(target); // 自動處理內存/臨時文件
// 5. 業務邏輯:保存元數據到數據庫
docService.upload(doc, storedName, originalName);
result.setSuccess(true);
result.setMsg("上傳成功");
} catch (Exception e) {
log.error("文件上傳失敗", e);
result.setSuccess(false);
result.setMsg("上傳失敗:" + e.getMessage());
}
return result;
}
StandardServletMultipartResolver (Servlet 3.0+) 避免全量讀入內存CompletableFuture 或線程池處理耗時合併/掃毒<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
web.xml 或 @MultipartConfig 配置:
<multipart-config>
<max-file-size>1073741824</max-file-size>
<max-request-size>2147483648</max-request-size>
<file-size-threshold>40960</file-size-threshold>
</multipart-config>
| 問題 | 原因 | 解決 |
|---|---|---|
| 中文文件名下載亂碼 | 瀏覽器編碼不一致 | RFC 5987: filename*=UTF-8''encodedName |
| 上傳大文件 OOM | 全量讀入 byte[] | 流式處理 / 分片上傳 / 增加 heap |
| 臨時文件未清理 | Commons FileUpload 默認行為 | 配置 FileCleaningTracker 或定時清理任務 |
| Spring Boot 2.x 無需配置 | 自動配置 | application.yml 配置 spring.servlet.multipart.* |
original_name、stored_name、path、size、mime_type