全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 6 分钟 ✍️ juanwangdev

Spring MVC 多文件上传实现

多文件上传支持批量处理多个文件,提高上传效率并简化前端交互。

基本多文件上传

Controller实现

Java
@RestController
@RequestMapping("/api/upload")
public class UploadController {

    @PostMapping("/multiple")
    public List<String> uploadMultiple(@RequestParam("files") MultipartFile[] files) {
        List<String> fileNames = new ArrayList<>();

        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                String fileName = saveFile(file);
                fileNames.add(fileName);
            }
        }

        return fileNames;
    }

    private String saveFile(MultipartFile file) {
        String fileName = UUID.randomUUID().toString() + getExtension(file);
        String filePath = "/upload/" + fileName;

        try {
            file.transferTo(new File(filePath));
            return fileName;
        } catch (IOException e) {
            throw new RuntimeException("文件保存失败", e);
        }
    }

    private String getExtension(MultipartFile file) {
        String originalName = file.getOriginalFilename();
        return originalName != null && originalName.contains(".")
            ? originalName.substring(originalName.lastIndexOf("."))
            : "";
    }
}

使用List接收

Java
@PostMapping("/list")
public List<String> uploadList(@RequestParam("files") List<MultipartFile> files) {
    return files.stream()
        .filter(file -> !file.isEmpty())
        .map(this::saveFile)
        .collect(Collectors.toList());
}

多文件上传表单

HTML
<form action="/api/upload/multiple" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple>
    <button type="submit">上传</button>
</form>

AJAX多文件上传

JavaScript
function uploadFiles(files) {
    const formData = new FormData();

    for (let i = 0; i < files.length; i++) {
        formData.append('files', files[i]);
    }

    fetch('/api/upload/multiple', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => console.log('上传成功:', data))
    .catch(error => console.error('上传失败:', error));
}

// 文件选择触发
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
    uploadFiles(e.target.files);
});

上传结果响应封装

Java
@Data
@AllArgsConstructor
public class UploadResult {
    private String fileName;
    private String originalName;
    private Long size;
    private String url;
    private boolean success;
    private String message;
}

@PostMapping("/batch")
public List<UploadResult> uploadBatch(@RequestParam("files") MultipartFile[] files) {
    List<UploadResult> results = new ArrayList<>();

    for (MultipartFile file : files) {
        try {
            if (file.isEmpty()) {
                results.add(new UploadResult(null, file.getOriginalFilename(),
                    0L, null, false, "文件为空"));
                continue;
            }

            String savedName = saveFile(file);
            String url = "/download/" + savedName;

            results.add(new UploadResult(savedName, file.getOriginalFilename(),
                file.getSize(), url, true, "上传成功"));

        } catch (Exception e) {
            results.add(new UploadResult(null, file.getOriginalFilename(),
                0L, null, false, "上传失败: " + e.getMessage()));
        }
    }

    return results;
}

唯一文件名策略

Java
@Component
public class FileNameGenerator {

    public String generate(MultipartFile file) {
        // UUID + 原始扩展名
        return UUID.randomUUID().toString() + getExtension(file);
    }

    public String generateWithTimestamp(MultipartFile file) {
        // 时间戳 + UUID + 扩展名
        String timestamp = LocalDateTime.now().format(
            DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return timestamp + "_" + UUID.randomUUID().toString().substring(0, 8)
            + getExtension(file);
    }

    public String generateWithOriginal(MultipartFile file) {
        // UUID + 原始文件名(避免重名)
        String original = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().substring(0, 8);
        return uuid + "_" + original;
    }

    public String generateWithPath(MultipartFile file) {
        // 按日期分目录存储
        String datePath = LocalDateTime.now().format(
            DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        return datePath + "/" + UUID.randomUUID() + getExtension(file);
    }
}

按日期分目录存储

Java
@PostMapping("/with-path")
public List<String> uploadWithPath(@RequestParam("files") MultipartFile[] files) {
    String basePath = "/upload/";
    String datePath = LocalDateTime.now().format(
        DateTimeFormatter.ofPattern("yyyy/MM/dd"));

    List<String> urls = new ArrayList<>();

    for (MultipartFile file : files) {
        if (!file.isEmpty()) {
            String fileName = UUID.randomUUID() + getExtension(file);
            String fullPath = basePath + datePath + "/" + fileName;

            File directory = new File(basePath + datePath);
            if (!directory.exists()) {
                directory.mkdirs();
            }

            try {
                file.transferTo(new File(fullPath));
                urls.add(datePath + "/" + fileName);
            } catch (IOException e) {
                throw new RuntimeException("文件保存失败", e);
            }
        }
    }

    return urls;
}

异步多文件上传

Java
@PostMapping("/async")
public CompletableFuture<List<String>> uploadAsync(
        @RequestParam("files") MultipartFile[] files) {

    return CompletableFuture.supplyAsync(() -> {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        List<CompletableFuture<String>> futures = new ArrayList<>();

        for (MultipartFile file : files) {
            futures.add(CompletableFuture.supplyAsync(() -> saveFile(file), executor));
        }

        return futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());
    });
}

上传进度追踪

Java
@RestController
@RequestMapping("/api/upload")
public class UploadProgressController {

    private final Map<String, UploadProgress> progressMap = new ConcurrentHashMap<>();

    @PostMapping("/with-progress")
    public String uploadWithProgress(
            @RequestParam("files") MultipartFile[] files,
            @RequestParam("uploadId") String uploadId) {

        UploadProgress progress = new UploadProgress(files.length);
        progressMap.put(uploadId, progress);

        List<String> results = new ArrayList<>();
        for (int i = 0; i < files.length; i++) {
            try {
                String fileName = saveFile(files[i]);
                results.add(fileName);
                progress.completed++;
            } catch (Exception e) {
                progress.failed++;
            }
            progressMap.put(uploadId, progress);
        }

        return String.join(",", results);
    }

    @GetMapping("/progress/{uploadId}")
    public UploadProgress getProgress(@PathVariable String uploadId) {
        return progressMap.getOrDefault(uploadId, new UploadProgress(0));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UploadProgress {
    private int total;
    private int completed = 0;
    private int failed = 0;

    public int getPercentage() {
        return total > 0 ? (completed + failed) * 100 / total : 0;
    }
}

文件大小统计

Java
@PostMapping("/size-check")
public Map<String, Object> uploadWithSizeCheck(
        @RequestParam("files") MultipartFile[] files) {

    long totalSize = 0;
    List<Map<String, Object>> fileInfos = new ArrayList<>();

    for (MultipartFile file : files) {
        if (!file.isEmpty()) {
            long size = file.getSize();
            totalSize += size;

            Map<String, Object> info = new HashMap<>();
            info.put("name", file.getOriginalFilename());
            info.put("size", size);
            info.put("sizeKB", size / 1024);
            info.put("sizeMB", size / 1024 / 1024);
            fileInfos.add(info);
        }
    }

    Map<String, Object> result = new HashMap<>();
    result.put("totalFiles", files.length);
    result.put("totalSize", totalSize);
    result.put("totalSizeMB", totalSize / 1024 / 1024);
    result.put("files", fileInfos);

    return result;
}

配置文件上传

YAML
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
      enabled: true

要点总结

  • 使用MultipartFile[]或List接收多文件
  • 前端使用multiple属性或FormData批量添加文件
  • 唯一文件名策略避免重名覆盖
  • 按日期分目录便于管理和清理
  • 异步上传提高并发处理效率
  • 进度追踪提升用户体验

📝 发现内容有误?点击此处直接编辑

← 上一篇 Spring MVC 单文件上传实现
下一篇 → Spring MVC 异常处理与国际化
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库