現代標準做法,無第三方依賴,支援 @MultipartConfig 註解配置。
@WebServlet("/upload")
@MultipartConfig(
location = "/tmp/uploads", // 暫存目錄
maxFileSize = 2_097_152, // 2MB
maxRequestSize = 4_194_304, // 4MB
fileSizeThreshold = 1024 * 1024 // 1MB 寫入磁碟
)
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
Part filePart = req.getPart("file"); // 取得上傳檔案 Part
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // 防路徑遍歷
String savedPath = "/var/www/uploads/" + UUID.randomUUID() + "_" + fileName;
filePart.write(savedPath); // 直接寫入目的地
resp.getWriter().write("上傳成功:" + savedPath);
}
}
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" accept="image/*" />
<button type="submit">上傳</button>
</form>
早期 Servlet 2.5 時代無標準 API,需手工解析 ServletInputStream 的 boundary 格式。代碼冗長、易出錯、難維護,僅供教學理解 multipart 協議結構。現代專案請勿使用。
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency>
public class UploadAction extends ActionSupport {
private File uploadImage; // 檔案內容
private String uploadImageContentType; // MIME 類型
private String uploadImageFileName; // 原始檔名
private String savePath; // 存儲目錄(struts.xml 注入)
// getter/setter 省略
@Override
public String execute() {
File dest = new File(savePath, uploadImageFileName);
FileUtils.copyFile(uploadImage, dest); // commons-io
return SUCCESS;
}
}
命名約定:表單 <input name="uploadImage"> 對應 Action 三屬性 uploadImage、uploadImageContentType、uploadImageFileName。
<action name="upload" class="com.example.UploadAction">
<param name="savePath">/var/www/uploads</param>
<!-- 使用 fileUpload 攔截器限制類型與大小 -->
<interceptor-ref name="defaultStack">
<param name="fileUpload.allowedTypes">image/jpeg,image/png,image/gif</param>
<param name="fileUpload.maximumSize">2097152</param> <!-- 2MB -->
</interceptor-ref>
<result name="input">/upload.jsp</result> <!-- 驗證失敗返回 -->
<result name="success">/success.jsp</result>
</action>
在 struts.properties 或 messages.properties 覆寫:
struts.messages.error.file.too.large=檔案大小超過限制(最大 {1} 位元組)
struts.messages.error.content.type.not.allowed=不允許的檔案類型:{1}
JSP 顯示:<s:fielderror />。
@GetMapping("/download/{fileName}")
public void download(@PathVariable String fileName, HttpServletResponse resp) throws IOException {
Path path = Paths.get("/var/www/uploads", fileName).normalize(); // 防路徑遍歷
if (!Files.exists(path)) { resp.sendError(404); return; }
resp.setContentType(Files.probeContentType(path));
resp.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
resp.setContentLengthLong(Files.size(path));
Files.copy(path, resp.getOutputStream());
}
<action name="download" class="com.example.FileDownloadAction">
<param name="inputPath">/var/www/uploads/demo.txt</param>
<result name="success" type="stream">
<param name="contentType">application/octet-stream</param>
<param name="inputName">inputStream</param> <!-- 對應 getInputStream() -->
<param name="contentDisposition">attachment;filename="demo.txt"</param>
<param name="bufferSize">4096</param>
</result>
</action>
Action 需提供 public InputStream getInputStream() 回傳檔案流。
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Servlet 3.0 Part API | 零依賴、標準化、效能好、程式碼簡潔 | 需 Servlet 3.0+ 容器 | 現代 Spring Boot、微服務、所有新專案 |
| Struts 2 | 整合驗證、國際化、攔截器豐富 | 依賴 commons-fileupload、配置較繁瑣 | 既有 Struts 2 專案維護 |
| JSP 手工解析 | 理解協議原理 | 極易出錯、無維護性 | 教學、考古 |
Paths.get(...).normalize())。Content-Type 甚至檔案魔數(Magic Bytes)。@MultipartConfig)與業務層雙重限制。