文件上傳下載是 Web 開發中最基礎也最常見的功能之一。本文將從原理分析、核心組件使用、常見問題處理到完整代碼實現,系統講解 JavaWeb 中文件上傳下載的完整方案。
實現文件上傳,HTML 表單必須滿足三個條件:
| 編碼類型 | 適用場景 | 服務端獲取方式 |
|---|---|---|
| application/x-www-form-urlencoded (默認) | 普通表單數據 | request.getParameter("name") |
| multipart/form-data | 包含文件上傳 | 需解析請求輸入流 |
當使用 multipart/form-data 時,請求體不再是簡單的鍵值對,而是包含邊界分隔符的多部分數據,原生的 getParameter 方法將失效,必須解析 request.getInputStream()。
commons-fileupload.jar - 核心包commons-io.jar - 依賴包// 1. 創建工廠對象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2. 創建解析器
ServletFileUpload fileUpload = new ServletFileUpload(factory);
// 3. 解析請求
List<FileItem> list = fileUpload.parseRequest(request);
// 4. 遍歷處理
for (FileItem item : list) {
if (item.isFormField()) {
// 處理普通字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
} else {
// 處理文件字段
String fileName = item.getName();
InputStream is = item.getInputStream();
item.write(new File("保存路徑"));
}
}
| 方法 | 說明 |
|---|---|
| isFormField() | 判斷是否為普通表單字段 |
| getFieldName() | 獲取字段名稱 |
| getString() | 獲取字段內容 |
| getName() | 獲取上傳文件名 |
| getContentType() | 獲取文件 MIME 類型 |
| getSize() | 獲取文件大小(字節) |
| getInputStream() | 獲取文件輸入流 |
| write(File) | 保存文件到指定路徑 |
| delete() | 刪除臨時文件 |
將上傳文件保存目錄設置在 Web 根目錄之外(如 WEB-INF 下),防止用戶直接通過 URL 訪問上傳的文件。
使用 UUID + 原文件名的方式生成唯一文件名:
String filename = UUID.randomUUID().toString() + "_" + FilenameUtils.getName(originalName);
方案一:按日期分目錄
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateDir = sdf.format(new Date());
方案二:按文件名 HashCode 分級目錄
private String makeChildDirectory(String filename) {
int hashcode = filename.hashCode();
String hex = Integer.toHexString(hashcode);
// 取前兩位作為二級目錄
return hex.charAt(0) + File.separator + hex.charAt(1);
}
// 單個文件最大 3MB
fileUpload.setFileSizeMax(1024 * 1024 * 3);
// 總大小最大 6MB
fileUpload.setSizeMax(1024 * 1024 * 6);
DiskFileItemFactory 默認使用系統臨時目錄,文件超過 10KB 時寫入磁盤。處理完成後必須調用 fileItem.delete() 清理臨時文件。
// 設置請求頭編碼,解決文件名中文亂碼
fileUpload.setHeaderEncoding("UTF-8");
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 判斷是否支持文件上傳
if (!ServletFileUpload.isMultipartContent(request)) {
throw new RuntimeException("表單必須設置 enctype=multipart/form-data");
}
// 2. 配置工廠與解析器
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File("f:\\temp")); // 指定臨時目錄
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(1024 * 1024 * 3); // 3MB
upload.setSizeMax(1024 * 1024 * 10); // 10MB
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
processFormField(item);
} else {
processUploadField(item);
}
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
response.getWriter().write("單個文件不能超過 3MB");
} catch (FileUploadBase.SizeLimitExceededException e) {
response.getWriter().write("總文件大小不能超過 10MB");
} catch (Exception e) {
e.printStackTrace();
}
}
private void processUploadField(FileItem item) throws Exception {
String filename = FilenameUtils.getName(item.getName());
if (filename == null || filename.isEmpty()) return;
// 生成唯一文件名
filename = UUID.randomUUID() + "_" + filename;
// 目錄分散
String childDir = makeChildDirectory(uploadDir, filename);
// 保存文件(自動清理臨時文件)
File saveFile = new File(uploadDir, childDir + File.separator + filename);
item.write(saveFile);
item.delete();
}
private void processFormField(FileItem item) throws Exception {
String name = item.getFieldName();
String value = item.getString("UTF-8");
System.out.println(name + " = " + value);
}
}
通過 JavaScript 動態添加/刪除文件輸入框:
<script>
function addFile() {
var div = document.getElementById("fileContainer");
div.innerHTML += "<div><input type='file' name='photo' />" +
"<input type='button' value='刪除' onclick='delFile(this)'/></div>";
}
function delFile(btn) {
btn.parentNode.parentNode.removeChild(btn.parentNode);
}
</script>
<form enctype="multipart/form-data" action="/upload" method="post">
<div id="fileContainer">
<div><input type="file" name="photo" />
<input type="button" value="添加" onclick="addFile()" /></div>
</div>
<input type="submit" value="上傳" />
</form>
核心要點:設置正確的響應頭,處理中文文件名編碼兼容性。
public class DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String filename = "銷售報表.csv";
String userAgent = request.getHeader("User-Agent").toLowerCase();
// 處理中文文件名編碼(兼容 IE 和現代瀏覽器)
if (userAgent.contains("msie") || userAgent.contains("trident")) {
filename = URLEncoder.encode(filename, "UTF-8");
} else {
filename = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
}
// 關鍵響應頭
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
response.setContentType(getServletContext().getMimeType(filename));
response.setCharacterEncoding("UTF-8");
// 輸出文件內容
try (PrintWriter out = response.getWriter()) {
out.write("商品,銷量\n");
out.write("電視機,20\n");
out.write("洗衣機,10\n");
out.write("冰箱,8\n");
}
}
}
文件上傳下載看似簡單,但生產環境中需考慮:安全性、性能、編碼兼容、異常處理、臨時文件清理等多個維度。掌握 Commons FileUpload 核心原理,配合合理的文件命名、目錄分散、大小限制策略,即可構建健壯的文件處理模塊。