commons-fileupload-1.3.1.jar、commons-io-2.x.jarMaven 坐標:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<form action="${pageContext.request.contextPath}/servlet/UploadServlet"
method="post" enctype="multipart/form-data">
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="text" name="description1" />
<input type="text" name="description2" />
<button type="submit">上傳</button>
</form>
三大鐵律:method="post"、enctype="multipart/form-data"、name 屬性唯一且與後端對應。
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.example.upload.UploadServlet</servlet-class>
<init-param>
<param-name>uploadPath</param-name>
<param-value>/data/uploads/</param-value> <!-- 生產環境建議配置在外部屬性文件 -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
@WebServlet("/upload")
@MultipartConfig(
maxFileSize = 10 * 1024 * 1024, // 10MB
maxRequestSize = 50 * 1024 * 1024, // 50MB
fileSizeThreshold = 1024 * 1024 // 1MB 臨界值,超過寫入臨時文件
)
public class UploadServlet extends HttpServlet {
private static final Logger log = LoggerFactory.getLogger(UploadServlet.class);
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json;charset=UTF-8");
String uploadBase = getServletConfig().getInitParameter("uploadPath");
File baseDir = new File(uploadBase);
if (!baseDir.exists()) baseDir.mkdirs();
Map<String, Object> result = new HashMap<>();
List<Map<String, String>> uploadedFiles = new ArrayList<>();
try {
// Servlet 3.0+ 原生 API,無需 Commons FileUpload
Collection<Part> parts = req.getParts();
for (Part part : parts) {
String fieldName = part.getName();
if (part.getSize() == 0) continue; // 空字段跳過
if (part.getSubmittedFileName() != null) { // 文件類型
String originalName = Paths.get(part.getSubmittedFileName()).getFileName().toString();
String storedName = UUID.randomUUID() + "_" + originalName;
File dest = new File(baseDir, storedName);
part.write(dest.getAbsolutePath());
part.delete(); // 刪除臨時文件
Map<String, String> fileInfo = new HashMap<>();
fileInfo.put("field", fieldName);
fileInfo.put("originalName", originalName);
fileInfo.put("storedName", storedName);
fileInfo.put("path", dest.getAbsolutePath());
fileInfo.put("size", String.valueOf(part.getSize()));
uploadedFiles.add(fileInfo);
log.info("文件上傳成功: {} -> {}", originalName, storedName);
} else { // 普通表單字段
String value = req.getParameter(fieldName);
result.put(fieldName, value);
}
}
result.put("success", true);
result.put("files", uploadedFiles);
result.put("message", "上傳完成,共 " + uploadedFiles.size() + " 個文件");
} catch (Exception e) {
log.error("上傳異常", e);
result.put("success", false);
result.put("message", "上傳失敗: " + e.getMessage());
}
new ObjectMapper().writeValue(resp.getWriter(), result);
}
}
@MultipartConfig 或 DiskFileItemFactory.setSizeThreshold 限制單文件/總請求大小。part.getContentType() 與文件頭魔數(Magic Number)雙重驗證,防止偽裝文件。| 現象 | 原因 | 解決 |
|---|---|---|
| request.getParameter 為 null | enctype 缺失或 Servlet 未解析 multipart | 檢查 form enctype、確認 @MultipartConfig 或 Commons FileUpload 配置 |
| 中文文件名亂碼 | Tomcat 8+ 默認 ISO-8859-1 | server.xml Connector 加 URIEncoding="UTF-8";或手動 new String(name.getBytes("ISO-8859-1"), "UTF-8") |
| 大文件上傳失敗 | 超過默認大小限制 | 調整 maxFileSize/maxRequestSize;檢查 Nginx client_max_body_size |
從傳統的 Commons FileUpload 到 Servlet 3.0 原生 @MultipartConfig,文件上傳技術已趨成熟。核心在於:嚴格校驗、安全存儲、異常兜底、日誌可追溯。願此實戰指南助你快速構建生產級上傳服務。