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批量添加文件
- 唯一文件名策略避免重名覆盖
- 按日期分目录便于管理和清理
- 异步上传提高并发处理效率
- 进度追踪提升用户体验
📝 发现内容有误?点击此处直接编辑