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

Java EE 文件上傳下載實戰:Servlet 3.0+ 標準 API 與 Apache Commons FileUpload

     編輯:Java 相關知識

文件上傳:Servlet 3.0+ 原生 API (推薦,無第三方依賴)

前端表單關鍵點

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="text" name="description" />
    <button type="submit">上傳</button>
</form>
  • enctype="multipart/form-data" 必須設置
  • method="post" 必須
  • name 屬性用於後端獲取

後端 Servlet 實現

@WebServlet("/upload")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024,        // 1MB 閾值,超過寫入臨時文件
    maxFileSize = 1024 * 1024 * 10,         // 10MB 單文件限制
    maxRequestSize = 1024 * 1024 * 50       // 50MB 總請求限制
)
public class UploadServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");
        
        // 獲取普通表單字段
        String description = req.getParameter("description");
        
        // 獲取上傳文件 Part
        Part filePart = req.getPart("file");
        if (filePart == null || filePart.getSize() == 0) {
            resp.getWriter().write("{\"code\":400,\"msg\":\"文件為空\"}");
            return;
        }
        
        // 解析原始文件名
        String originalName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
        // 安全文件名:UUID + 原後綴
        String ext = originalName.substring(originalName.lastIndexOf("."));
        String savedName = UUID.randomUUID().toString().replace("-", "") + ext;
        
        // 保存路徑
        String uploadDir = getServletContext().getRealPath("/uploads");
        File dir = new File(uploadDir);
        if (!dir.exists()) dir.mkdirs();
        
        File dest = new File(uploadDir, savedName);
        
        try (InputStream in = filePart.getInputStream();
             OutputStream out = new FileOutputStream(dest)) {
            
            byte[] buffer = new byte[8192];
            int len;
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        }
        
        // 記錄映射關係 (可存 DB/Redis)
        // fileMap.put(savedName, originalName);
        
        resp.getWriter().write(
            String.format("{\"code\":200,\"msg\":\"成功\",\"fileName\":\"%s\",\"originalName\":\"%s\"}", 
                savedName, originalName));
    }
}

文件上傳:Apache Commons FileUpload (舊版項目/精細控制)

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
@WebServlet("/upload-legacy")
public class LegacyUploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 檢查是否 multipart
        if (!ServletFileUpload.isMultipartContent(req)) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "非文件上傳請求");
            return;
        }
        
        // 2. 磁盤工廠 (臨時文件目錄、大小閾值)
        File tempDir = new File(System.getProperty("java.io.tmpdir"), "upload");
        DiskFileItemFactory factory = new DiskFileItemFactory(1024 * 1024, tempDir);
        
        // 3. 解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(10 * 1024 * 1024); // 10MB
        upload.setSizeMax(50 * 1024 * 1024);     // 50MB
        upload.setHeaderEncoding("UTF-8");
        
        try {
            List items = upload.parseRequest(req);
            for (FileItem item : items) {
                if (item.isFormField()) {
                    // 普通字段
                    String fieldName = item.getFieldName();
                    String value = item.getString("UTF-8");
                } else {
                    // 文件字段
                    String fileName = item.getName();
                    if (fileName != null && !fileName.isEmpty()) {
                        String ext = fileName.substring(fileName.lastIndexOf("."));
                        String savedName = UUID.randomUUID() + ext;
                        File dest = new File(uploadDir, savedName);
                        FileUtils.copyInputStreamToFile(item.getInputStream(), dest);
                    }
                }
            }
        } catch (FileUploadException e) {
            throw new ServletException("解析失敗", e);
        }
    }
}

文件下載實現

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        String fileName = req.getParameter("fileName"); // 存儲名
        String originalName = req.getParameter("originalName"); // 原始名
        
        if (fileName == null || fileName.isEmpty()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "參數缺失");
            return;
        }
        
        // 安全校驗:防止路徑遍歷攻擊
        fileName = Paths.get(fileName).getFileName().toString();
        String uploadDir = getServletContext().getRealPath("/uploads");
        File file = new File(uploadDir, fileName);
        
        if (!file.exists() || !file.isFile()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
            return;
        }
        
        // 設置響應頭
        resp.setContentType("application/octet-stream"); // 強制下載
        resp.setContentLengthLong(file.length());
        
        // 文件名編碼 (RFC 5987 / RFC 6266)
        String encodedName = URLEncoder.encode(originalName != null ? originalName : fileName, 
            StandardCharsets.UTF_8.toString()).replace("+", "%20");
        resp.setHeader("Content-Disposition", 
            "attachment; filename=\"" + encodedName + "\"; filename*=UTF-8''" + encodedName);
        
        // 傳輸文件
        try (InputStream in = new BufferedInputStream(new FileInputStream(file));
             OutputStream out = resp.getOutputStream()) {
            
            byte[] buffer = new byte[8192];
            int len;
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
            out.flush();
        }
    }
}

大文件/斷點續傳進階方案

  • 分片上傳:前端切片 (如 5MB/片),後端合併,支持暫停續傳
  • 秒傳:計算文件 MD5,秒傳已存在文件
  • Range 請求下載:支持 Range: bytes=0-1023 斷點續傳
  • 對象存儲:生產環境建議直傳 OSS/S3/MinIO,後端只簽名授權

安全防護清單

風險防護措施
路徑遍歷Paths.get(fileName).getFileName() 截取純文件名
惡意文件類型校驗 Content-Type、魔數、後綴白名單
文件名注入存儲用 UUID,原名存 DB 映射
大文件 DoS限制 maxFileSize/maxRequestSize、流式處理
腳本執行上傳目錄配置不可執行、隔離域名、CDN 防盜鏈
併發衝突UUID/雪花算法生成唯一文件名

Spring Boot 整合簡化

@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) return Result.error("文件為空");
    
    String originalName = file.getOriginalFilename();
    String ext = originalName.substring(originalName.lastIndexOf("."));
    String savedName = UUID.randomUUID() + ext;
    
    Path dest = Paths.get(uploadDir, savedName);
    Files.createDirectories(dest.getParent());
    file.transferTo(dest); // 一行搞定
    
    return Result.ok(savedName, originalName);
}

// application.yml
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
      file-size-threshold: 1MB

掌握標準 API 與工具庫兩種上傳方式、下載響應頭規範、安全防護體系,構建企業級文件服務能力。

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