首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Servlet 實現文件上傳功能完整解析與進度監聽

     編輯:Java 相關知識

Java Servlet 文件上傳完整實現:核心功能、進度監聽與定時清理

本文詳細解析基於 Apache Commons FileUpload 的 Servlet 文件上傳完整方案,包含檔案類型驗證、大小限制、日期分目錄儲存、AJAX 進度即時回饋、暫存檔定時清理等生產級功能。

核心功能特性

  • 支援多檔案上傳 (HTML5 multiple 屬性)
  • 檔案類型白名單驗證 (圖片/文檔/壓縮包等)
  • 單檔 100MB 大小限制
  • UUID 重命名避免衝突
  • 按日期自動建立子目錄 (yyyyMMdd)
  • AJAX 輪詢即時顯示上傳進度
  • 暫存檔定時清理機制 (ServletContextListener)

後端核心實現

1. 上傳 Servlet - UploadServlet.java

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());
        }
    }
}

2. 進度監聽器 - UploadListener.java

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 方法...
}

3. 進度查詢 Servlet - GetProgressServlet.java

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);
    }
}

前端實現 (HTML5 + AJAX)

1. HTML 頁面

<input type="file" id="file" name="file" multiple style="display:none" />
<a href="javascript:addOne()">添加檔案</a>
<div id="target"></div>

2. CSS 進度條樣式

.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; }

3. JavaScript 核心邏輯

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、更新狀態
}

暫存檔定時清理機制

1. 監聽器 - TempFileListener.java

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();
    }
}

2. 清理任務 - TempFileManager.java

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;
    }
}

3. web.xml 註冊監聽器

<listener>
    <listener-class>util.upload.TempFileListener</listener-class>
</listener>

依賴庫

  • commons-fileupload-1.3.1.jar
  • commons-io-2.4.jar

部署與測試建議

  • 確保 Servlet 容器支援 Servlet 3.0+ (Tomcat 7+)
  • 上傳目錄需有寫入權限
  • 生產環境建議配合 Nginx 反向代理處理大檔案上傳
  • 進度輪詢間隔 50ms 可根據網路情況調整
  • 清理間隔建議設為 1 小時 (3600000ms),保留時間 1-7 天視磁碟空間而定

總結

本方案提供了生產可用的文件上傳完整實現,涵蓋從前端選檔、後端驗證儲存、即時進度回饋、到暫存檔自動清理的全生命週期管理。核心技術點包括 Commons FileUpload 解析、ProgressListener 監聽、AJAX 輪詢、ServletContextListener 定時任務,架構清晰、擴展性強。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved