Spring MVC 单文件上传实现
单文件上传是文件处理的基础功能,掌握其实现方式是理解多文件上传的前提。
基本单文件上传
Controller实现
Java
@RestController
@RequestMapping("/api/upload")
public class UploadController {
@Value("${upload.path}")
private String uploadPath;
@PostMapping("/single")
public String upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
throw new IllegalArgumentException("上传文件为空");
}
String fileName = generateFileName(file);
File destFile = new File(uploadPath + fileName);
try {
file.transferTo(destFile);
return fileName;
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
private String generateFileName(MultipartFile file) {
return UUID.randomUUID().toString() + getExtension(file);
}
private String getExtension(MultipartFile file) {
String originalName = file.getOriginalFilename();
return originalName != null && originalName.contains(".")
? originalName.substring(originalName.lastIndexOf("."))
: "";
}
}
配置上传路径
YAML
upload:
path: /data/upload/
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
文件上传表单
HTML表单
HTML
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<form action="/api/upload/single" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
</body>
</html>
enctype必须设置为multipart/form-data。
AJAX文件上传
JavaScript
function uploadFile() {
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
const formData = new FormData();
formData.append('file', file);
fetch('/api/upload/single', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(fileName => {
console.log('上传成功: ' + fileName);
})
.catch(error => {
console.error('上传失败: ' + error);
});
}
文件名策略
UUID唯一命名
Java
private String generateUUIDFileName(MultipartFile file) {
return UUID.randomUUID().toString() + getExtension(file);
}
时间戳命名
Java
private String generateTimestampFileName(MultipartFile file) {
String timestamp = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
return timestamp + "_" + UUID.randomUUID().toString().substring(0, 8)
+ getExtension(file);
}
保留原始文件名(加UUID防重名)
Java
private String generateOriginalFileName(MultipartFile file) {
String originalName = file.getOriginalFilename();
String uuid = UUID.randomUUID().toString().substring(0, 8);
return uuid + "_" + originalName;
}
按日期分目录存储
Java
@PostMapping("/with-date-path")
public String uploadWithDatePath(@RequestParam("file") MultipartFile file) {
String datePath = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String fullPath = uploadPath + datePath;
File directory = new File(fullPath);
if (!directory.exists()) {
directory.mkdirs();
}
String fileName = UUID.randomUUID() + getExtension(file);
File destFile = new File(fullPath + "/" + fileName);
try {
file.transferTo(destFile);
return datePath + "/" + fileName;
} catch (IOException e) {
throw new RuntimeException("上传失败", e);
}
}
InputStream方式处理
Java
@PostMapping("/stream")
public String uploadStream(@RequestParam("file") MultipartFile file) {
String fileName = UUID.randomUUID() + getExtension(file);
String filePath = uploadPath + fileName;
try (InputStream inputStream = file.getInputStream();
FileOutputStream outputStream = new FileOutputStream(filePath)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return fileName;
} catch (IOException e) {
throw new RuntimeException("上传失败", e);
}
}
文件信息响应
Java
@Data
@AllArgsConstructor
class UploadResponse {
private String fileName;
private String originalName;
private Long size;
private String url;
}
@PostMapping("/info")
public UploadResponse uploadWithInfo(@RequestParam("file") MultipartFile file) {
String fileName = UUID.randomUUID() + getExtension(file);
String filePath = uploadPath + fileName;
try {
file.transferTo(new File(filePath));
} catch (IOException e) {
throw new RuntimeException("上传失败", e);
}
return new UploadResponse(
fileName,
file.getOriginalFilename(),
file.getSize(),
"/download/" + fileName
);
}
文件类型校验
Java
@PostMapping("/validate")
public String uploadWithValidate(@RequestParam("file") MultipartFile file) {
// 校验文件为空
if (file.isEmpty()) {
throw new ValidationException("文件为空");
}
// 校验文件大小
if (file.getSize() > 10 * 1024 * 1024) {
throw new ValidationException("文件大小超过10MB限制");
}
// 校验文件类型
String contentType = file.getContentType();
if (!isAllowedType(contentType)) {
throw new ValidationException("不允许的文件类型: " + contentType);
}
// 校验文件扩展名
String extension = getExtension(file);
if (!isAllowedExtension(extension)) {
throw new ValidationException("不允许的文件扩展名: " + extension);
}
return saveFile(file);
}
private boolean isAllowedType(String contentType) {
Set<String> allowedTypes = Set.of(
"image/jpeg", "image/png", "image/gif",
"application/pdf"
);
return allowedTypes.contains(contentType);
}
private boolean isAllowedExtension(String extension) {
Set<String> allowedExts = Set.of(".jpg", ".jpeg", ".png", ".gif", ".pdf");
return allowedExts.contains(extension.toLowerCase());
}
异常处理
Java
@RestControllerAdvice
public class UploadExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<Map<String, Object>> handleMaxSize(
MaxUploadSizeExceededException e) {
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", "文件大小超过限制");
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleIllegalArgument(
IllegalArgumentException e) {
Map<String, Object> response = new HashMap<>();
response.put("code", 400);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
文件存储到云服务
Java
@Service
public class CloudStorageService {
@Autowired
private OSSClient ossClient;
@Value("${oss.bucket}")
private String bucket;
public String uploadToOSS(MultipartFile file) {
String fileName = UUID.randomUUID() + getExtension(file);
try {
ossClient.putObject(bucket, fileName, file.getInputStream());
return ossClient.generatePresignedUrl(bucket, fileName,
new Date(System.currentTimeMillis() + 3600 * 1000)).toString();
} catch (IOException e) {
throw new RuntimeException("OSS上传失败", e);
}
}
}
RESTful上传接口
Java
@RestController
@RequestMapping("/api/files")
public class FileController {
@PostMapping
public UploadResponse upload(@RequestParam("file") MultipartFile file) {
return fileService.upload(file);
}
@GetMapping("/{id}")
public ResponseEntity<Resource> download(@PathVariable Long id) {
return fileService.download(id);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
fileService.delete(id);
}
}
上传进度回调
Java
@PostMapping("/progress")
public String uploadWithProgress(
@RequestParam("file") MultipartFile file,
@RequestParam("uploadId") String uploadId) {
long totalSize = file.getSize();
long processed = 0;
try (InputStream inputStream = file.getInputStream();
FileOutputStream outputStream = new FileOutputStream(uploadPath + UUID.randomUUID())) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
processed += bytesRead;
// 更新进度(存储到Redis等)
updateProgress(uploadId, processed, totalSize);
}
return "上传完成";
} catch (IOException e) {
throw new RuntimeException("上传失败", e);
}
}
要点总结
- @RequestParam接收MultipartFile
- enctype="multipart/form-data"是必须的
- transferTo()方法直接保存文件
- UUID命名避免文件名冲突
- 按日期分目录便于管理
- 校验文件大小、类型、扩展名
📝 发现内容有误?点击此处直接编辑