本文詳細解析基於 Apache Commons FileUpload 的 Servlet 文件上傳完整方案,包含檔案類型驗證、大小限制、日期分目錄儲存、AJAX 進度即時回饋、暫存檔定時清理等生產級功能。
package util.upload;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.http.*;
import java.io.File;
import java.util.*;
public class UploadServlet extends HttpServlet {
// 允許的副檔名白名單
private String[] allowedExtName = {
"zip","rar", "txt","doc","wps","docx","java",
"xls","xlsx", "ppt","pptx", "pdf",
"jpg","jpeg","bmp","gif","png"
};
private static final int MAX_SIZE = 100 * 1024 * 1024; // 100MB
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
HttpSession session = request.getSession();
session.setAttribute("result", "nok");
session.setAttribute("error", "");
// 進度監聽器
UploadListener listener = new UploadListener();
session.setAttribute("LISTENER", listener);
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setProgressListener(listener);
// 儲存路徑:webapp/upload/yyyyMMdd/
String basePath = request.getSession().getServletContext().getRealPath("/upload");
String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date());
File saveDir = new File(basePath, dateDir);
if (!saveDir.exists()) saveDir.mkdirs();
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField() && item.getName() != null && !item.getName().isEmpty()) {
// 檔案類型驗證
String extName = item.getName().substring(item.getName().lastIndexOf('.') + 1);
boolean allowed = Arrays.stream(allowedExtName)
.anyMatch(e -> e.equalsIgnoreCase(extName));
if (!allowed) throw new Exception("上傳檔案格式不合法!");
// 大小驗證
if (item.getSize() > MAX_SIZE)
throw new Exception("檔案過大,請選擇不超過 100MB 的檔案!");
// UUID 重命名
String fileName = UUID.randomUUID() + "_" + item.getName();
File destFile = new File(saveDir, fileName);
item.write(destFile);
// 記錄可訪問 URL
String contextPath = request.getSession().getServletContext().getContextPath();
String fileUrl = contextPath + "/upload/" + dateDir + "/" + fileName;
session.setAttribute("filename", fileUrl);
session.setAttribute("result", "OK");
}
}
} catch (Exception e) {
session.setAttribute("error", "上傳失敗: " + e.getMessage());
}
}
}
package util.upload;
import org.apache.commons.fileupload.ProgressListener;
public class UploadListener implements ProgressListener {
private volatile long bytesRead = 0;
private volatile long contentLength = 0;
private volatile long items = 0;
@Override
public void update(long bytesRead, long contentLength, int items) {
this.bytesRead = bytesRead;
this.contentLength = contentLength;
this.items = items;
}
// Getter 方法...
}
package util.upload;
import javax.servlet.http.*;
import java.io.*;
public class GetProgressServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
UploadListener listener = (UploadListener) session.getAttribute("LISTENER");
if (listener == null) return;
String error = (String) session.getAttribute("error");
String result = (String) session.getAttribute("result");
String fileName = (String) session.getAttribute("filename");
// 格式:已讀位元組,總位元組,錯誤訊息,結果狀態,檔案URL
String progressInfo = listener.getBytesRead() + ","
+ listener.getContentLength() + ","
+ (error == null ? "" : error) + ","
+ (result == null ? "" : result) + ","
+ (fileName == null ? "" : fileName);
response.getWriter().print(progressInfo);
}
}
<input type="file" id="file" name="file" multiple style="display:none" />
<a href="javascript:addOne()">添加檔案</a>
<div id="target"></div>
.pborder { width: 500px; border: 1px solid #B1D632; padding: 1px; position: relative; }
.drawpro { width: 0%; height: 20px; background: #B1D632; line-height: 20px; }
.pspan { position: absolute; width: 100%; text-align: center; font-weight: bold; }
var uplist = []; // 上傳隊列
var flag = true; // 是否可上傳下一個
function addfile(evt) {
var file = evt.target.files[0];
var uf = { id: ID++, file: file, state: 0, path: "" };
uplist.push(uf);
// 建立進度條 DOM
var div = document.createElement("div");
div.id = "pro" + uf.id;
div.className = "pro";
div.innerHTML = progressBarHTML;
document.getElementById("target").appendChild(div);
waittimer = setInterval(upload, 1000);
}
function upload() {
if (flag && uplist.some(f => f.state === 0)) {
var uf = uplist.find(f => f.state === 0);
uf.state = 1; // 上傳中
flag = false;
var formData = new FormData();
formData.append("file", uf.file);
httpUP = new XMLHttpRequest();
httpUP.upload.addEventListener('progress', uploadProgress, false);
httpUP.open("POST", "Upload", true);
httpUP.send(formData);
nowID = uf.id;
timer = setInterval(getProgress, 50);
}
}
function getProgress() {
// AJAX 輪詢 GetProgressServlet 獲取進度
// 解析回應更新進度條寬度與百分比
// 狀態 OK 時清除定時器、標記完成、釋放 flag
}
function abortUpload(btn) {
// 取消上傳:abort XMLHttpRequest、移除 DOM、更新狀態
}
package util.upload;
import javax.servlet.*;
import java.util.Timer;
public class TempFileListener implements ServletContextListener {
private Timer timer;
public void contextInitialized(ServletContextEvent sce) {
timer = new Timer();
String realPath = sce.getServletContext().getRealPath("/");
long interval = Long.parseLong(getConfig("every_time_run")) * 1000;
// 10秒後首次執行,之後每 interval 執行一次
timer.schedule(new SystemTaskTest(realPath, sce.getServletContext()), 10000, interval);
}
public void contextDestroyed(ServletContextEvent sce) {
timer.cancel();
}
}
package util.upload;
import java.io.File;
import java.util.Date;
public class TempFileManager implements Runnable {
private String rootPath;
private static final int RETENTION_MINUTES = 1440; // 1天
public TempFileManager(String rootPath) {
this.rootPath = rootPath + "upload";
}
public void run() {
File uploadDir = new File(rootPath);
deleteExpiredFiles(uploadDir);
}
private void deleteExpiredFiles(File folder) {
if (folder.isDirectory()) {
File[] files = folder.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
deleteExpiredFiles(f); // 遞歸處理子目錄
// 子目錄清空後若過期則刪除目錄本身
if (canDelete(f) && f.listFiles().length == 0) f.delete();
} else {
if (canDelete(f)) f.delete();
}
}
}
}
}
private boolean canDelete(File file) {
long ageMinutes = (System.currentTimeMillis() - file.lastModified()) / 60000;
return ageMinutes > RETENTION_MINUTES;
}
}
<listener>
<listener-class>util.upload.TempFileListener</listener-class>
</listener>
本方案提供了生產可用的文件上傳完整實現,涵蓋從前端選檔、後端驗證儲存、即時進度回饋、到暫存檔自動清理的全生命週期管理。核心技術點包括 Commons FileUpload 解析、ProgressListener 監聽、AJAX 輪詢、ServletContextListener 定時任務,架構清晰、擴展性強。