方案一:Commons FileUpload (通用、Servlet 2.5+)
依賴
commons-fileupload-1.3.3.jar
commons-io-2.6.jar
Servlet 實現
@WebServlet("/upload")
@MultipartConfig // Servlet 3.0+ 原生支持,見方案二
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 檢查 Content-Type
if (!ServletFileUpload.isMultipartContent(req)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "非 multipart 請求");
return;
}
// 2. 配置工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(getServletContext().getRealPath("/tmp"))); // 臨時文件目錄
factory.setSizeThreshold(1024 * 1024); // 1MB 以下內存,以上磁盤
// 3. 解析請求
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(10 * 1024 * 1024); // 單文件 10MB
upload.setSizeMax(50 * 1024 * 1024); // 總請求 50MB
try {
List items = upload.parseRequest(req);
for (FileItem item : items) {
if (item.isFormField()) {
// 普通表單字段
String fieldName = item.getFieldName();
String value = item.getString("UTF-8");
System.out.println("Field: " + fieldName + " = " + value);
} else {
// 文件字段
String fileName = new File(item.getName()).getName(); // 防路徑遍歷
String ext = fileName.substring(fileName.lastIndexOf('.'));
String savedName = UUID.randomUUID().toString() + ext;
File saveDir = new File(getServletContext().getRealPath("/upload"));
if (!saveDir.exists()) saveDir.mkdirs();
File savedFile = new File(saveDir, savedName);
item.write(savedFile); // 寫入最終位置 (自動處理臨時文件清理)
System.out.println("Saved: " + savedName);
}
}
resp.sendRedirect("ok.jsp");
} catch (FileUploadException e) {
throw new ServletException("解析失敗", e);
} catch (Exception e) {
throw new ServletException("保存失敗", e);
}
}
}
方案二:Servlet 3.0 原生 @MultipartConfig (無第三方依賴)
@WebServlet("/upload")
@MultipartConfig(
location = "/tmp", // 臨時文件目錄
fileSizeThreshold = 1024 * 1024, // 1MB
maxFileSize = 10 * 1024 * 1024, // 10MB
maxRequestSize = 50 * 1024 * 1024 // 50MB
)
public class UploadServlet3 extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
for (Part part : req.getParts()) {
if (part.getSize() == 0) continue; // 空字段
String cd = part.getHeader("Content-Disposition");
boolean isFile = cd != null && cd.contains("filename=");
if (isFile) {
String fileName = Paths.get(part.getSubmittedFileName()).getFileName().toString();
String ext = fileName.substring(fileName.lastIndexOf('.'));
String savedName = UUID.randomUUID() + ext;
part.write(getServletContext().getRealPath("/upload") + File.separator + savedName);
} else {
String value = new String(part.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
System.out.println(part.getName() + " = " + value);
}
}
resp.sendRedirect("ok.jsp");
}
}
前端表單 (通用)
關鍵安全點
- 文件名清洗:
new File(item.getName()).getName() 或 Paths.get(...).getFileName() 防止 ../../etc/passwd。
- 類型驗證:勿信任
Content-Type,需檢查 Magic Number (文件頭) 或擴展名白名單。
- 大小限制:工廠/解析器/註解雙重限制,防止 DoS。
- 存儲隔離:上傳目錄置於 Web 根目錄外,或配置 Nginx 禁止腳本執行。