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

Commons FileUpload 文件上傳完整實戰:原理、配置、亂碼、限流、臨時文件管理

     編輯:Java 綜合教學

Commons FileUpload 文件上傳完整實戰:原理、配置、亂碼、限流、臨時文件管理

Apache Commons FileUpload 是 Servlet 時代處理 multipart/form-data 請求的標準組件。本文從原理到生產級代碼,全方位講解文件上傳的核心知識點。

一、文件上傳必要條件

條件設置說明
Form MethodPOSTGET 無請求體,無法承載文件數據
Enctypemultipart/form-data默認 application/x-www-form-urlencoded 不支持文件

二、核心原理

  1. 請求到達 Servlet,ServletFileUpload.isMultipartContent(request) 判斷類型
  2. DiskFileItemFactory 創建 FileItem 工廠(控制臨時文件閾值、目錄)
  3. ServletFileUpload 解析請求 → List
  4. 遍歷 FileItem:
    • isFormField() 區分普通字段 vs 文件
    • 普通字段:getString(encoding)
    • 文件字段:getName()、getInputStream()、write(File)

三、Maven 依賴

<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.6</version>
</dependency>

四、完整 Servlet 實現

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 1. 校驗 Content-Type
        if (!ServletFileUpload.isMultipartContent(request)) {
            out.write("請設置 enctype='multipart/form-data'");
            return;
        }

        // 2. 配置工廠
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setRepository(new File("/tmp/upload"));  // 臨時文件目錄
        factory.setSizeThreshold(1024 * 1024);           // 1MB 內存閾值,超過寫磁盤

        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(3 * 1024 * 1024);          // 單文件 3MB
        upload.setSizeMax(6 * 1024 * 1024);              // 總大小 6MB
        upload.setHeaderEncoding("UTF-8");               // 解決文件名亂碼

        // 3. 解析請求
        List items;
        try {
            items = upload.parseRequest(request);
        } catch (FileUploadBase.FileSizeLimitExceededException e) {
            out.write("單文件超過 3MB"); return;
        } catch (FileUploadBase.SizeLimitExceededException e) {
            out.write("總大小超過 6MB"); return;
        } catch (FileUploadException e) {
            throw new RuntimeException("解析失敗", e);
        }

        // 4. 處理每個部分
        for (FileItem item : items) {
            if (item.isFormField()) {
                processFormField(item, out);
            } else {
                processUploadField(item, out);
            }
        }
        out.write("上傳成功!");
    }

    private void processFormField(FileItem item, PrintWriter out) {
        String fieldName = item.getFieldName();
        String fieldValue;
        try {
            fieldValue = item.getString("UTF-8");  // 指定編碼
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("不支持 UTF-8", e);
        }
        System.out.println(fieldName + "=" + fieldValue);
    }

    private void processUploadField(FileItem item, PrintWriter out) {
        String fileName = item.getName();
        if (fileName == null || fileName.isEmpty()) return; // 未選文件

        // 1. 生成唯一文件名:UUID + 原文件名
        fileName = UUID.randomUUID() + "_" + FilenameUtils.getName(fileName);
        String extension = FilenameUtils.getExtension(fileName);

        // 2. 內容類型校驗(只允許圖片)
        String contentType = item.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            out.write("只允許上傳圖片"); return;
        }

        // 3. 分目錄存儲:按文件名 hashCode 兩級目錄
        String baseDir = getServletContext().getRealPath("/WEB-INF/files");
        String subDir = makeSubDir(baseDir, fileName);
        File storeDir = new File(baseDir, subDir);
        if (!storeDir.exists()) storeDir.mkdirs();

        // 4. 寫入文件(自動清理臨時文件)
        try {
            File dest = new File(storeDir, fileName);
            item.write(dest);  // 關鍵:write() 會自動刪除臨時文件
            System.out.println("存儲至: " + dest.getAbsolutePath());
        } catch (Exception e) {
            throw new RuntimeException("寫入失敗", e);
        }
    }

    private String makeSubDir(String baseDir, String fileName) {
        int hash = fileName.hashCode();
        int d1 = hash & 0xF;        // 低 4 位
        int d2 = (hash & 0xF0) >> 4; // 次低 4 位
        String dir = d1 + File.separator + d2;
        new File(baseDir, dir).mkdirs();
        return dir;
    }
}

五、關鍵問題專題

1. 亂碼雙重保障

  • 普通字段:item.getString("UTF-8") 指定編碼
  • 文件名:upload.setHeaderEncoding("UTF-8") + 確保頁面 <meta charset="UTF-8">

2. 臨時文件管理

3. 文件大小限制

  • upload.setFileSizeMax(3MB):單文件上限
  • upload.setSizeMax(6MB):總請求上限
  • 超過自動拋出 FileSizeLimitExceededException / SizeLimitExceededException

4. 安全加固清單

  • 文件類型驗證:contentType.startsWith("image/") + 擴展名白名單
  • 文件名重命名:UUID + 原名,防止路徑遍歷、覆蓋、特殊字符
  • 存儲目錄隔離:/WEB-INF/files 不可直接 Web 訪問
  • 病毒掃描:集成 ClamAV 等殺毒引擎(生產環境)

六、Spring MVC 整合(補充)

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="6291456"/> <!-- 6MB -->
    <property name="maxUploadSizePerFile" value="3145728"/> <!-- 3MB -->
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

@Controller
public class FileController {
    @PostMapping("/upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file) {
        // Spring 自動封裝,內部同樣用 Commons FileUpload
        // file.transferTo(destFile); // 自動清理臨時文件
    }
}

Commons FileUpload 雖然歷史悠久,但原理清晰、功能完備,掌握其核心 API 與最佳實踐,能應對絕大多數 Servlet 環境下的文件上傳場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方法行為建議
item.write(File)寫入目標文件,**自動刪除臨時文件**首選
item.getInputStream()手動流拷貝,**需手動調用 item.delete() 清理臨時文件**特殊需求時使用
factory.setSizeThreshold(1MB)小文件內存中,大文件寫磁盤臨時文件平衡性能與內存
factory.setRepository(dir)指定臨時文件目錄避免系統盤寫滿