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

JavaWeb 文件上傳下載綜合解析:從原理到框架整合的完整指南

     編輯:Java 相關知識

文件上傳核心原理

HTTP 請求結構 (multipart/form-data)

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

zhangsan
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file1"; filename="photo.jpg"
Content-Type: image/jpeg

<二進制數據...>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
  • 邊界字符串分隔各 Part
  • 每 Part 含 Content-Disposition 標識字段名/文件名
  • 文件 Part 含 Content-Type、空行、二進制體
  • 結束邊界以 -- 結尾

手工解析流程 (教學用)

InputStream is = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String boundary = br.readLine(); // 讀取邊界
// 循環解析各 Part...
// 注意:首行邊界、文件頭、空行、二進制體、結束邊界

Commons FileUpload 框架封裝

核心類

  • DiskFileItemFactory:臨時文件/內存閾值、存儲目錄
  • ServletFileUpload:解析入口、大小限制、進度監聽、編碼設置
  • FileItem:單個表單項,isFormField() 區分類型

標準上傳流程

DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024 * 1024); // 1MB 閾值
factory.setRepository(new File("/tmp"));

ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(10 * 1024 * 1024);
upload.setSizeMax(50 * 1024 * 1024);

List items = upload.parseRequest(request);
for (FileItem item : items) {
    if (item.isFormField()) {
        String value = item.getString("UTF-8");
    } else {
        String fileName = item.getName().substring(item.getName().lastIndexOf("\\") + 1);
        try (InputStream in = item.getInputStream();
             FileOutputStream out = new FileOutputStream(savePath + fileName)) {
            IOUtils.copy(in, out);
        }
        item.delete(); // 清理臨時文件
    }
}

文件下載核心原理

HTTP 響應關鍵頭

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="download.jpg"
Content-Length: 102400

<二進制數據流>
  • Content-Disposition: attachment 強制瀏覽器下載而非預覽
  • filename 指定下載文件名,需編碼處理中文
  • Content-Length 支持進度條、斷點續傳

標準下載流程

File file = new File(realPath, fileName);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", 
    "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentLengthLong(file.length());

try (FileInputStream fis = new FileInputStream(file);
     BufferedInputStream bis = new BufferedInputStream(fis);
     OutputStream os = response.getOutputStream()) {
    byte[] buffer = new byte[8192];
    int len;
    while ((len = bis.read(buffer)) > 0) {
        os.write(buffer, 0, len);
    }
}

主流框架整合對比

框架上傳核心關鍵配置Controller 寫法
Struts 2 Commons FileUpload 內建 struts.multipart.maxSize
struts.multipart.saveDir
private File file; String fileFileName;
自動注入暫存文件
Spring MVC CommonsMultipartResolver / StandardServletMultipartResolver spring.servlet.multipart.max-file-size
max-request-size
@RequestParam MultipartFile file
Spring Boot 自動配置 StandardServletMultipartResolver (Tomcat) / MultipartResolver (Jetty/Undertow) application.yml 同 Spring MVC 同 Spring MVC,零配置啟用

Struts 2 完整示例

Action

public class FileUploadAction extends ActionSupport {
    private File file;           // 暫存文件
    private String fileFileName; // 原始文件名
    private String fileContentType;
    private String username;
    
    @Override
    public String execute() {
        String saveDir = ServletActionContext.getServletContext()
            .getRealPath("/upload");
        File dest = new File(saveDir, fileFileName);
        Files.copy(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
        return SUCCESS;
    }
    // getter/setter...
}

struts.xml

<action name="fileUpload" class="com.example.FileUploadAction">
    <result>/success.jsp</result>
    <interceptor-ref name="fileUpload">
        <param name="maximumSize">10485760</param> <!-- 10MB -->
        <param name="allowedTypes">image/jpeg,image/png,application/pdf</param>
    </interceptor-ref>
    <interceptor-ref name="defaultStack"/>
</action>

Spring MVC / Boot 完整示例

配置 (application.yml)

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 50MB
      enabled: true
      file-size-threshold: 1MB  # 閾值,超過寫磁盤
      location: /tmp            # 臨時目錄

上傳 Controller

@RestController
@RequestMapping("/api/file")
public class FileController {
    
    @Value("${file.upload-dir:./uploads}")
    private String uploadDir;
    
    @PostMapping("/upload")
    public Result upload(@RequestParam("file") MultipartFile file,
                         @RequestParam(value = "username", required = false) String username) {
        if (file.isEmpty()) return Result.error("文件為空");
        
        // 安全文件名
        String original = file.getOriginalFilename();
        String ext = original.substring(original.lastIndexOf("."));
        String savedName = UUID.randomUUID() + ext;
        
        Path path = Paths.get(uploadDir, savedName);
        try {
            Files.createDirectories(path.getParent());
            Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            return Result.error("保存失敗: " + e.getMessage());
        }
        
        return Result.ok(Map.of("url", "/files/" + savedName, "name", original));
    }
    
    @GetMapping("/download/{fileName}")
    public void download(@PathVariable String fileName, 
                         HttpServletRequest request, HttpServletResponse response) {
        // 安全校驗
        String safeName = Paths.get(fileName).getFileName().toString();
        File file = new File(uploadDir, safeName);
        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        // 編碼兼容
        String ua = request.getHeader("User-Agent");
        String encoded = ua.contains("MSIE") || ua.contains("Trident") ?
            URLEncoder.encode(safeName, StandardCharsets.UTF_8) :
            new String(safeName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        
        response.reset();
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + encoded + "\"");
        response.setContentLengthLong(file.length());
        
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = fis.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
        } catch (IOException e) {
            log.error("下載失敗", e);
        }
    }
}

安全與最佳實踐清單

  • 🔒 **文件名安全**:UUID 重命名、路徑遍歷防護 (`Paths.get(fileName).getFileName()`)
  • 🔒 **類型驗證**:副檔名白名單 + Magic Number 檢測 (如 Files.probeContentType())
  • 🔒 **大小限制**:框架層 + 業務層雙重限制
  • 🔒 **存儲隔離**:上傳目錄置於 Web 根目錄外、或對象存儲 (OSS/S3/MinIO)
  • 🔒 **病毒掃描**:生產環境集成 ClamAV、或上傳至對象存儲觸發掃描
  • 🔒 **權限控制**:下載介面校驗用戶權限、防止越權訪問
  • ⚡ **效能優化**:大文件分片上傳、斷點續傳、NIO 零拷貝下載、CDN 加速
  • 📝 **審計日誌**:記錄上傳/下載用戶、文件、時間、IP、結果

常見問題速查

現象原因解決
Spring Boot MultipartFile 為 null缺少 MultipartResolver / 表單 enctype 錯誤 檢查依賴、配置、form enctype=multipart/form-data
Struts 2 文件名亂碼未設置編碼 / Tomcat URIEncoding struts.multipart.encoding=UTF-8 + Tomcat URIEncoding="UTF-8"
上傳大文件報 413/連接重置超過框架/服務器/代理限制 調整 Spring max-request-size、Tomcat maxPostSize、Nginx client_max_body_size
下載文件名亂碼User-Agent 編碼分支缺失 雙重 filename/filename* 或 UA 判斷
文件無法下載/損壞響應流被 Filter/Interceptor 污染 返回 void、response.reset()、確保無其他輸出
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved