文件上傳下載是 Web 開發高頻需求。本文基於 Spring MVC 框架,系統講解:五種上傳實現方式、多文件上傳、兩種下載模式、工具類封裝、Spring 配置、前端測試頁面,並分析各方式優缺點與適用場景。
統一文件保存邏輯:按日期分目錄、UUID 重命名防衝突、返回相對路徑供 DB 存儲。
public final class Files_Helper_DG {
private Files_Helper_DG() { throw new Error("工具類不可實例化"); }
// Spring MultipartFile.transferTo() 方式保存
public static String FilesUpload_transferTo_spring(HttpServletRequest request,
MultipartFile multipartFile,
String filePath) {
// 1. 構建保存路徑:項目實際路徑 + 相對路徑 + 日期目錄
String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date());
String savePath = request.getSession().getServletContext().getRealPath(filePath)
+ File.separator + datePath;
File dir = new File(savePath);
if (!dir.exists()) dir.mkdirs();
// 2. 生成唯一文件名:UUID + 原始後綴
String suffix = multipartFile.getOriginalFilename()
.substring(multipartFile.getOriginalFilename().lastIndexOf("."));
String uuidName = UUID.randomUUID().toString() + suffix;
String fullPath = savePath + File.separator + uuidName;
String relativePath = filePath + File.separator + datePath + File.separator + uuidName;
// 3. 保存文件
try {
multipartFile.transferTo(new File(fullPath));
return relativePath;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 流式下載:設置響應頭、輸出流寫入
public static void FilesDownload_servlet(HttpServletRequest request,
HttpServletResponse response,
String filePath) {
String realPath = request.getSession().getServletContext().getRealPath(filePath);
File file = new File(realPath);
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition",
"attachment;filename=" + new String(file.getName().getBytes("utf-8"), "iso8859-1"));
response.addHeader("Content-Length", String.valueOf(file.length()));
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@RequestMapping("fileUpload_multipartFile")
@ResponseBody
public String fileUpload_multipartFile(HttpServletRequest request,
@RequestParam("file_upload") MultipartFile file) {
String path = Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\",\"filePath\":\"" + path + "\"}";
}
@RequestMapping("fileUpload_multipartRequest")
@ResponseBody
public String fileUpload_multipartRequest(HttpServletRequest request) {
MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) request;
MultipartFile file = mpRequest.getFile("file_upload");
String path = Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\",\"filePath\":\"" + path + "\"}";
}
@RequestMapping("fileUpload_CommonsMultipartResolver")
@ResponseBody
public String fileUpload_CommonsMultipartResolver(HttpServletRequest request) {
CommonsMultipartResolver resolver = new CommonsMultipartResolver(
request.getSession().getServletContext());
if (resolver.isMultipart(request)) {
MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest) request;
Iterator<String> it = mpRequest.getFileNames();
while (it.hasNext()) {
MultipartFile file = mpRequest.getFile(it.next());
Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
}
}
return "upload success!";
}
@RequestMapping("fileUpload_stream")
@ResponseBody
public String fileUpload_stream(HttpServletRequest request) {
String savePath = request.getSession().getServletContext().getRealPath("/filesOut/Upload");
File dir = new File(savePath);
if (!dir.exists()) dir.mkdirs();
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if (!ServletFileUpload.isMultipartContent(request)) {
return "非表單上傳數據";
}
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
// 普通表單字段
System.out.println(item.getFieldName() + "=" + item.getString("UTF-8"));
} else {
// 文件字段
String fileName = item.getName();
if (fileName == null || fileName.trim().isEmpty()) continue;
String suffix = fileName.substring(fileName.lastIndexOf("."));
try (InputStream in = item.getInputStream();
FileOutputStream out = new FileOutputStream(savePath + "/stream_" + suffix)) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
item.delete(); // 清理臨時文件
}
}
return "upload success!";
} catch (Exception e) {
e.printStackTrace();
return "upload fail";
}
}
@RequestMapping("fileUpload_spring_list")
@ResponseBody
public String fileUpload_spring_list(HttpServletRequest request,
@RequestParam("file_upload") MultipartFile[] files) {
if (files == null || files.length == 0) {
return "{\"TFMark\":\"false\",\"Msg\":\"參數錯誤\"}";
}
try {
for (MultipartFile file : files) {
Files_Helper_DG.FilesUpload_transferTo_spring(request, file, "/filesOut/Upload");
}
return "{\"TFMark\":\"true\",\"Msg\":\"upload success!\"}";
} catch (Exception e) {
return "{\"TFMark\":\"false\",\"Msg\":\"參數傳遞有誤\"}";
}
}
<mvc:annotation-driven />
<context:component-scan base-package="HelloSpringMVC.controller" />
<!-- 靜態資源映射:讓上傳文件可直接訪問 -->
<mvc:resources location="/filesOut/" mapping="/filesOut/**" />
<!-- 多部分解析器:必須配置 id=multipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600" /> <!-- 100MB -->
<property name="maxInMemorySize" value="4096" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
包含 5 個表單分別測試 5 種上傳方式,2 個下載鏈接測試直接訪問與流式下載。
<form action="/FileUpload/fileUpload_multipartFile" method="post" enctype="multipart/form-data">
<input type="file" name="file_upload"> <input type="submit" value="upload">
</form>
<!-- 其他 4 個 form 結構類似,action 指向對應 mapping -->
<!-- 下載測試 -->
<a href="<%=basePath%>filesOut/Download/mst.txt">直接訪問下載</a>
<a href="/FileUpload/fileDownload_servlet">流式下載(安全)</a>
@RequestMapping("fileDownload_servlet")
public void fileDownload_servlet(HttpServletRequest request, HttpServletResponse response) {
Files_Helper_DG.FilesDownload_servlet(request, response, "/filesOut/Download/mst.txt");
}
| 方式 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|
直接訪問 <a href="/filesOut/..."> |
簡單、性能高、支持斷點續傳 | 暴露目錄結構、無權限控制、無法統計下載 | 靜態公共資源、CDN 分發 |
流式下載 Response.getOutputStream() |
隱藏真實路徑、可攔截權限、可記錄日志、動態生成文件 | 佔用服務器 IO/內存、大文件需分塊傳輸 | 私有文件、需權限控制、需審計日志 |