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

Java 文件上傳下載完整實現:Servlet 原生、SmartUpload 與 Spring Boot 多方案對比

     編輯:Java 綜合教學

Java 文件上傳下載完整實現指南

文件上傳下載是 Web 開發高頻需求。本文系統對比三種主流方案:Servlet 3.0+ 原生 API(零依賴)、SmartUpload(經典組件)、Spring Boot/MultipartFile(現代標準),涵蓋大文件分片、斷點續傳、安全防範、雲存儲整合等生產級實踐。

一、核心原理回顧

上傳原理

表單設置 enctype="multipart/form-data",瀏覽器以二進制流編碼提交,服務端解析請求體邊界分隔符,提取文件字節流持久化。

下載原理

設置響應頭 Content-Disposition: attachment; filename="xxx" 觸發瀏覽器下載行為,輸出流寫入文件字節。

二、方案一:Servlet 3.0+ 原生 API(推薦零依賴項目)

上傳 Servlet

@WebServlet("/upload")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024,      // 1MB 寫入臨時文件
    maxFileSize = 1024 * 1024 * 50,       // 50MB 單文件限制
    maxRequestSize = 1024 * 1024 * 100    // 100MB 總請求限制
)
public class UploadServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        Part filePart = req.getPart("file");
        String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
        
        // 安全校驗
        if (!isAllowedExtension(fileName)) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "文件類型不允許");
            return;
        }
        
        // 生成唯一文件名防覆蓋
        String savedName = UUID.randomUUID() + "_" + fileName;
        Path savePath = Paths.get(getServletContext().getRealPath("/uploads"), savedName);
        Files.createDirectories(savePath.getParent());
        
        try (InputStream in = filePart.getInputStream()) {
            Files.copy(in, savePath, StandardCopyOption.REPLACE_EXISTING);
        }
        
        resp.getWriter().write("上傳成功:" + savedName);
    }
    
    private boolean isAllowedExtension(String fileName) {
        String ext = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
        return Arrays.asList("jpg", "png", "pdf", "docx").contains(ext);
    }
}

下載 Servlet

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        String fileName = req.getParameter("filename");
        // 防止路徑遍歷攻擊
        fileName = Paths.get(fileName).getFileName().toString();
        
        Path filePath = Paths.get(getServletContext().getRealPath("/uploads"), fileName);
        if (!Files.exists(filePath)) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        resp.setContentType(getServletContext().getMimeType(fileName));
        resp.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        resp.setContentLengthLong(Files.size(filePath));
        
        try (InputStream in = Files.newInputStream(filePath);
             OutputStream out = resp.getOutputStream()) {
            byte[] buffer = new byte[8192];
            int len;
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        }
    }
}

三、方案二:SmartUpload 組件(維護老項目)

注意: SmartUpload 早已停止維護,新項目不建議使用。

上傳核心代碼

SmartUpload su = new SmartUpload();
su.initialize(getServletConfig(), request, response);
su.setMaxFileSize(10 * 1024 * 1024);        // 10MB
su.setTotalMaxFileSize(100 * 1024 * 1024);  // 100MB
su.setAllowedFilesList("txt,jpg,png,gif,pdf");
su.setDeniedFilesList("exe,bat,sh,jsp,php");

try {
    su.upload();
    int count = su.save(uploadPath, SmartUpload.SAVE_PHYSICAL);
    
    // 獲取文件元信息
    for (int i = 0; i < su.getFiles().getCount(); i++) {
        com.jspsmart.upload.File f = su.getFiles().getFile(i);
        System.out.println("字段名:" + f.getFieldName());
        System.out.println("原文件名:" + f.getFileName());
        System.out.println("大小:" + f.getSize());
        System.out.println("擴展名:" + f.getFileExt());
    }
} catch (SmartUploadException e) {
    // 錯誤碼處理:1010/1015 類型錯誤、1105/1110 大小超限
}

批量下載打包 ZIP

response.setContentType("application/x-msdownload");
response.setHeader("Content-Disposition", "attachment; filename=batch.zip");

String[] filenames = request.getParameterValues("filename");
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
    for (String name : filenames) {
        Path file = Paths.get(uploadPath, name);
        if (Files.exists(file)) {
            zos.putNextEntry(new ZipEntry(name));
            Files.copy(file, zos);
            zos.closeEntry();
        }
    }
}

四、方案三:Spring Boot 現代標準(推薦新項目)

配置屬性

spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 50MB
      max-request-size: 100MB
      file-size-threshold: 1MB
      location: ${java.io.tmpdir}/uploads  # 臨時文件目錄

控制器實現

@RestController
@RequestMapping("/api/files")
public class FileController {
    
    @Value("${file.upload-dir:/data/uploads}")
    private String uploadDir;
    
    @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 savePath = Paths.get(uploadDir, savedName);
        try {
            Files.createDirectories(savePath.getParent());
            file.transferTo(savePath);
        } catch (IOException e) {
            log.error("上傳失敗", e);
            return Result.error("存儲異常");
        }
        
        return Result.ok(savedName);
    }
    
    @GetMapping("/download/{filename}")
    public void download(@PathVariable String filename, HttpServletResponse response) {
        filename = Paths.get(filename).getFileName().toString(); // 安全過濾
        Path filePath = Paths.get(uploadDir, filename);
        
        if (!Files.exists(filePath)) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader("Content-Disposition", 
            "attachment; filename=\"" + filename + "\"");
        
        try (InputStream in = Files.newInputStream(filePath)) {
            IOUtils.copy(in, response.getOutputStream());
        } catch (IOException e) {
            log.error("下載失敗", e);
        }
    }
    
    @PostMapping("/chunked-upload")
    public Result chunkedUpload(@RequestParam("file") MultipartFile file,
                                @RequestParam("chunk") int chunk,
                                @RequestParam("chunks") int chunks,
                                @RequestParam("md5") String md5) {
        // 分片上傳邏輯:臨時目錄存分片 → 合併驗證 MD5 → 移動到正式目錄
        // 參考:Resumable.js、Plupload 前端組件配合
    }
}

五、生產級安全與性能清單

維度措施
文件類型雙重校驗:擴展名白名單 + Magic Number(文件頭字節)驗證
文件大小客戶端/網關/應用三層限制,防止 OOM
文件名UUID 重命名,去除路徑遍歷字符(../、..\)
存儲隔離上傳目錄禁用腳本執行權限,獨立域名/存儲桶
惡意掃描集成 ClamAV/ICAP 病毒掃描,異步處理
大文件分片上傳、斷點續傳、秒傳(MD5 秒傳)、異步合併
雲存儲對接 S3/OSS/COS SDK,預簽名 URL 直傳,減輕應用服務器壓力
下載限速Token Bucket 算法限制單用戶帶寬
防盜鏈Referer 檢查、簽名 URL 時效性、防盜鏈 Token

六、對象存儲直傳架構(最佳實踐)

// 後端生成預簽名 URL
@GetMapping("/presign")
public String getPresignedUrl(@RequestParam String filename, @RequestParam String contentType) {
    String key = "uploads/" + UUID.randomUUID() + "_" + filename;
    return ossClient.generatePresignedUrl(bucket, key, Date.from(Instant.now().plusSeconds(3600)), HttpMethod.PUT);
}

// 前端直傳 OSS
fetch(presignedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': contentType },
    body: file
}).then(() => notifyBackendComplete(key));

優勢:應用服務器零流量經手、自動擴容、CDN 加速、成本更低。

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