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

Java Web 文件上傳下載完整實現:Commons FileUpload + Servlet 規範實戰

     編輯:Java 相關知識

依賴準備

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

上傳 Servlet 實現

@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
    private static final long MAX_SIZE = 20 * 1024 * 1024; // 20MB
    private static final String ALLOWED_EXTS = ".jpg|.jpeg|.gif|.bmp|.png|";

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        boolean success = processUpload(req);
        req.getRequestDispatcher("/upload.jsp").forward(req, resp);
    }

    private boolean processUpload(HttpServletRequest req) {
        String savePath = getServletContext().getRealPath("/upload");
        File dir = new File(savePath);
        if (!dir.exists()) dir.mkdirs();

        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setRepository(dir);
        factory.setSizeThreshold(1024 * 1024); // 1MB 內存閾值

        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setSizeMax(MAX_SIZE);
        upload.setHeaderEncoding("UTF-8");

        try {
            List<FileItem> items = upload.parseRequest(req);
            for (FileItem item : items) {
                String fieldName = item.getFieldName();
                if (item.isFormField()) {
                    // 普通表單字段
                    String value = item.getString("UTF-8");
                    req.setAttribute(fieldName, value);
                } else {
                    // 文件字段
                    String fileName = item.getName();
                    if (fileName == null || fileName.trim().isEmpty()) continue;
                    
                    // 安全處理:僅取文件名部分
                    fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
                    String ext = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
                    if (!ALLOWED_EXTS.contains(ext + "|")) {
                        req.setAttribute("message", "禁止上傳類型: " + ext);
                        return false;
                    }

                    // 生成唯一文件名防覆蓋
                    String newName = System.currentTimeMillis() + ext;
                    req.setAttribute(fieldName, fileName);
                    req.setAttribute(fieldName + "Server", newName);

                    // 流式寫入
                    try (InputStream in = item.getInputStream();
                         OutputStream out = new FileOutputStream(new File(dir, newName))) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len);
                    }
                }
            }
            req.setAttribute("message", "上傳成功");
            return true;
        } catch (FileUploadException e) {
            req.setAttribute("message", "文件過大或解析失敗");
            return false;
        } catch (Exception e) {
            req.setAttribute("message", "上傳異常: " + e.getMessage());
            return false;
        }
    }
}

下載 Servlet 實現

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        String fileName = req.getParameter("fileName");   // 存儲名
        String origName = req.getParameter("origName");   // 原始名
        if (fileName == null || origName == null) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }
        processDownload(fileName, origName, req, resp);
    }

    private void processDownload(String fileName, String origName, 
                                 HttpServletRequest req, HttpServletResponse resp) {
        String path = getServletContext().getRealPath("/upload");
        File file = new File(path, fileName);
        if (!file.exists()) {
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        resp.reset();
        resp.setContentType("application/octet-stream");
        resp.setContentLengthLong(file.length());
        // RFC 5987 編碼兼容中文文件名
        String encodedName = StandardCharsets.UTF_8.encode(origName).toString()
            .replaceAll("[^\\p{ASCII}]", "");
        resp.setHeader("Content-Disposition", 
            "attachment; filename*=UTF-8''" + encodedName);

        try (FileInputStream in = new FileInputStream(file);
             OutputStream out = resp.getOutputStream()) {
            byte[] buffer = new byte[4096];
            int len;
            while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len);
            out.flush();
        } catch (IOException e) {
            log.error("下載失敗", e);
        }
    }
}

前端 JSP 頁面

<%@ page contentType="text/html;charset=UTF-8" %>
<form action="upload" method="post" enctype="multipart/form-data">
    賬號: <input type="text" name="account"><br/>
    文件: <input type="file" name="upfile"><br/>
    <input type="submit" value="上傳">
</form>

<c:if test="${not empty message}">
    <div style="color:red">${message}</div>
</c:if>

<h3>下載示例</h3>
<a href="download?fileName=12345.jpg&origName=風景.jpg">下載風景.jpg</a>

關鍵最佳實踐

場景做法
大文件內存溢出DiskFileItemFactory 設閾值 + 臨時目錄,超閾值落盤
文件名衝突時間戳/UUID + 原後綴重命名
中文文件名下載亂碼Content-Disposition: filename*=UTF-8''encoded
惡意文件上傳白名單後綴 + 內容檢測 + 存儲目錄禁執行權限
路徑遍歷攻擊文件名僅保留 baseName,拼接安全路徑
併發上傳性能異步處理 + 對象存儲 (OSS/S3) 直傳

現代化替代方案

  • Spring Boot:MultipartFile + @RequestParam 自動綁定,無需手動解析
  • Servlet 3.0+:@MultipartConfig + request.getPart() 原生 API
  • 大文件直傳:前端簽名直傳 OSS,後端僅驗證回調
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved