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

Spring MVC 文件上傳下載完整配置與 Controller 實戰

     編輯:Java 相關知識

項目依賴 (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.11.0</version>
</dependency>

Spring MVC 配置類 (Java Config 推薦)

@Configuration
@EnableWebMvc
@ComponentScan("com.example")
public class WebConfig implements WebMvcConfigurer {

    // 1. 文件上傳解析器 (支援 100MB 以內)
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("UTF-8");
        resolver.setMaxUploadSize(100 * 1024 * 1024); // 100MB
        resolver.setMaxInMemorySize(40960); // 40KB 閾值,超過寫臨時文件
        return resolver;
    }

    // 2. 靜態資源映射 (可選)
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/files/**")
                .addResourceLocations("file:/data/upload/");
    }
}

文件下載 Controller

@RestController
@RequestMapping("/api/file")
public class FileController {

    private static final String BASE_DIR = "C:/tempDirectory/";

    @GetMapping("/download/{filename:.+}")
    public ResponseEntity<Resource> download(@PathVariable String filename, HttpServletRequest request) {
        try {
            // 1. 安全校驗:防止路徑遍歷攻擊
            Path filePath = Paths.get(BASE_DIR).resolve(filename).normalize();
            if (!filePath.startsWith(Paths.get(BASE_DIR))) {
                return ResponseEntity.badRequest().build();
            }
            Resource resource = new UrlResource(filePath.toUri());
            if (!resource.exists() || !resource.isReadable()) {
                return ResponseEntity.notFound().build();
            }

            // 2. 內容類型推斷
            String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
            if (contentType == null) contentType = "application/octet-stream";

            // 3. 中文文件名編碼 (RFC 5987)
            String encodedName = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
            String contentDisposition = "attachment; filename*=UTF-8''" + encodedName;

            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
                    .contentLength(resource.contentLength())
                    .body(resource);

        } catch (Exception e) {
            log.error("下載失敗: {}", filename, e);
            return ResponseEntity.internalServerError().build();
        }
    }
}

文件上傳 Controller

@PostMapping("/upload")
public ResponseEntity<ApiResponse> upload(@RequestParam("file") MultipartFile file,
                                           @RequestParam(required = false) String bizType) {
    // 1. 基礎校驗
    if (file.isEmpty()) return ResponseEntity.badRequest().body(ApiResponse.fail("文件為空"));
    
    // 2. 文件類型白名單
    String contentType = file.getContentType();
    if (!ALLOWED_TYPES.contains(contentType)) {
        return ResponseEntity.badRequest().body(ApiResponse.fail("不支持的文件類型"));
    }

    // 3. 生成唯一文件名 (UUID + 原始後綴)
    String originalName = file.getOriginalFilename();
    String suffix = originalName.substring(originalName.lastIndexOf("."));
    String saveName = UUID.randomUUID().toString().replace("-", "") + suffix;

    // 4. 保存路徑:按日期分目錄避免單目錄文件過多
    String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    Path saveDir = Paths.get(BASE_DIR, dateDir);
    Files.createDirectories(saveDir);
    Path target = saveDir.resolve(saveName);

    try {
        file.transferTo(target); // 底層用 NIO 複製,高效
    } catch (IOException e) {
        log.error("文件保存失敗", e);
        return ResponseEntity.internalServerError().body(ApiResponse.fail("保存失敗"));
    }

    // 5. 業務入庫 (略)
    // fileService.save(FileRecord.builder().originalName(originalName).savePath(target.toString()).build());

    return ResponseEntity.ok(ApiResponse.success(Map.of("url", "/api/file/download/" + saveName)));
}

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

  • 分片上傳:前端按 5MB 分片,後端提供 /upload/chunk 合併接口
  • 秒傳:上傳前計算文件 MD5,後端查詢是否已存在,存在則直接返回
  • 斷點續傳:記錄已上傳分片標識,續傳時跳過
  • 對象存儲:生產環境建議直傳 OSS (MinIO/Aliyun OSS/AWS S3),後端只簽名授權

安全加固清單

風險防護措施
路徑遍歷用 Path.normalize() + startsWith(baseDir) 校驗
惡意腳本上傳白名單校驗 Content-Type + 後綴;存儲路徑與 Web 根目錄隔離
文件名注入不使用原始文件名存儲,一律 UUID 重命名
DoS 攻擊限制單文件/總大小、限制並發上傳數、啟用防毒掃描

總結

Spring MVC 文件上傳下載的核心在於:MultipartResolver 配置、Resource 抽象統一文件訪問、安全校驗全鏈路覆蓋。生產環境建議遷移至對象存儲 + 直傳模式,後端僅做元數據管理與簽名授權。

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