1. 配置 MultipartResolver
Java Config:
java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public MultipartResolver multipartResolver() {
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
return resolver;
}
}
application.properties :
bash
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.location=/tmp/uploads
2. Controller 接收文件
方式一:使用 @RequestParam
java
@PostMapping("/upload")
public String handleUpload(@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
// 保存文件
file.transferTo(new File("/uploads/" + file.getOriginalFilename()));
return "上传成功: " + file.getOriginalFilename();
}
return "上传失败";
}
方式二:使用 @ModelAttribute
java
@PostMapping("/upload")
public String handleUpload(@ModelAttribute UploadForm form) {
MultipartFile file = form.getFile();
// 处理文件
return "success";
}
// UploadForm 类
public class UploadForm {
private MultipartFile file;
// getter/setter
}
方式三:多文件上传
java
@PostMapping("/upload/multiple")
public String handleMultipleUpload(@RequestParam("files") MultipartFile[] files) {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 处理每个文件
}
}
return "上传成功";
}
3. 常用 MultipartFile 方法
java
// 获取文件基本信息
String originalFilename = file.getOriginalFilename();
String contentType = file.getContentType();
long size = file.getSize();
boolean isEmpty = file.isEmpty();
// 获取文件内容
byte[] bytes = file.getBytes();
InputStream inputStream = file.getInputStream();
// 保存文件(推荐)
file.transferTo(new File("/path/to/save/" + originalFilename));
4. 完整示例:带参数的文件上传
java
@PostMapping("/uploadWithParams")
public ResponseEntity<?> uploadWithParams(
@RequestParam("file") MultipartFile file,
@RequestParam("description") String description,
@RequestParam(value = "userId", required = false) Long userId) {
try {
// 验证文件
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("文件为空");
}
// 验证文件类型
String contentType = file.getContentType();
if (!contentType.startsWith("image/")) {
return ResponseEntity.badRequest().body("只支持图片文件");
}
// 生成唯一文件名
String newFileName = UUID.randomUUID().toString() +
getFileExtension(file.getOriginalFilename());
// 保存文件
Path path = Paths.get("/uploads/" + newFileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
// 保存到数据库(示例)
FileRecord record = new FileRecord();
record.setFileName(newFileName);
record.setOriginalName(file.getOriginalFilename());
record.setDescription(description);
record.setUserId(userId);
// fileRecordService.save(record);
return ResponseEntity.ok("上传成功: " + newFileName);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("上传失败: " + e.getMessage());
}
}
private String getFileExtension(String filename) {
return filename.substring(filename.lastIndexOf("."));
}
5. 异常处理
java
@ControllerAdvice
public class MultipartExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<?> handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
.body("文件大小超出限制,最大 10MB");
}
@ExceptionHandler(MultipartException.class)
public ResponseEntity<?> handleMultipartException(MultipartException e) {
return ResponseEntity.badRequest().body("文件上传错误: " + e.getMessage());
}
}
注意事项:
-
必须在 form 标签添加
enctype="multipart/form-data" -
根据应用场景合理配置文件大小限制
-
注意文件保存路径的权限问题
-
生产环境建议将文件保存到专门的存储服务(如 OSS、云存储)
在 Spring MVC 中同时接收表单数据和文件数据,有以下几种常用方式:
1. 使用 @RequestParam 分别接收
java
@PostMapping("/submit")
public ResponseEntity<?> submit(
// 文件
@RequestParam("file") MultipartFile file,
// 普通表单字段
@RequestParam("username") String username,
@RequestParam("age") Integer age,
@RequestParam("email") String email,
// 可选字段
@RequestParam(value = "description", required = false) String description) {
// 处理文件
String fileName = file.getOriginalFilename();
// 处理表单数据
UserDto user = new UserDto(username, age, email, description);
return ResponseEntity.ok("用户 " + username + " 上传文件: " + fileName);
}
2. 使用 @ModelAttribute 封装表单数据
java
// 表单数据对象
@Data
public class UserForm {
private String username;
private Integer age;
private String email;
private String description;
// 注意:不要在这里放 MultipartFile
}
@PostMapping("/submit")
public ResponseEntity<?> submit(
@RequestParam("file") MultipartFile file,
@ModelAttribute UserForm userForm) {
// 处理文件
saveFile(file);
// 处理表单数据
System.out.println(userForm.getUsername());
System.out.println(userForm.getAge());
return ResponseEntity.ok("success");
}
3. 使用 @RequestPart(推荐)
java
@PostMapping("/submit")
public ResponseEntity<?> submit(
@RequestPart("file") MultipartFile file,
@RequestPart("user") UserDto userDto) {
// @RequestPart 可以自动将 JSON 或表单数据转换为对象
// 前端需要以 multipart/form-data 格式发送
return ResponseEntity.ok("用户: " + userDto.getUsername() +
" 文件: " + file.getOriginalFilename());
}
// 前端示例 (使用 fetch)
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('user', JSON.stringify({
username: '张三',
age: 25,
email: 'zhangsan@example.com'
}));
fetch('/submit', {
method: 'POST',
body: formData
});
4. 使用包装类同时包含文件和表单数据
java
// 包装类
@Data
public class UserWithFile {
// 表单数据
private String username;
private Integer age;
private String email;
// 文件数据
private MultipartFile avatar;
private List<MultipartFile> attachments;
}
@PostMapping("/submit")
public ResponseEntity<?> submit(@ModelAttribute UserWithFile userWithFile) {
// 获取表单数据
String username = userWithFile.getUsername();
// 获取文件
MultipartFile avatar = userWithFile.getAvatar();
List<MultipartFile> attachments = userWithFile.getAttachments();
// 处理业务逻辑
return ResponseEntity.ok("success");
}
5. 使用 HttpServletRequest 手动获取
java
@PostMapping("/submit")
public ResponseEntity<?> submit(HttpServletRequest request) {
// 需要先判断是否为 multipart
if (request instanceof MultipartHttpServletRequest) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获取文件
MultipartFile file = multipartRequest.getFile("file");
// 获取表单数据
String username = multipartRequest.getParameter("username");
String age = multipartRequest.getParameter("age");
// 获取所有参数
Map<String, String[]> parameterMap = multipartRequest.getParameterMap();
return ResponseEntity.ok("success");
}
return ResponseEntity.badRequest().body("不是 multipart 请求");
}
6. 完整实战示例:用户注册 + 头像上传
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/register")
public ResponseEntity<?> register(
// 文件
@RequestParam("avatar") MultipartFile avatar,
// 表单数据 - 方式1:分别接收
@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("nickname") String nickname,
@RequestParam("phone") String phone,
// 复杂对象可以用 @RequestParam 接收后转换
@RequestParam("address") String addressJson) throws JsonProcessingException {
// 1. 验证文件
if (avatar.isEmpty()) {
return ResponseEntity.badRequest().body("头像不能为空");
}
// 2. 验证文件类型
String contentType = avatar.getContentType();
if (!contentType.startsWith("image/")) {
return ResponseEntity.badRequest().body("只能上传图片文件");
}
// 3. 保存头像
String avatarUrl = saveAvatar(avatar, username);
// 4. 解析地址 JSON
ObjectMapper mapper = new ObjectMapper();
Address address = mapper.readValue(addressJson, Address.class);
// 5. 构建用户对象
User user = User.builder()
.username(username)
.password(password)
.nickname(nickname)
.phone(phone)
.address(address)
.avatarUrl(avatarUrl)
.build();
// 6. 保存用户信息
userService.register(user);
return ResponseEntity.ok("注册成功");
}
private String saveAvatar(MultipartFile file, String username) throws IOException {
// 生成文件名
String extension = getFileExtension(file.getOriginalFilename());
String newFileName = username + "_" + System.currentTimeMillis() + extension;
// 保存路径
Path path = Paths.get("/uploads/avatars/" + newFileName);
Files.createDirectories(path.getParent());
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return "/uploads/avatars/" + newFileName;
}
private String getFileExtension(String filename) {
return filename.substring(filename.lastIndexOf("."));
}
}
注意事项:
-
enctype 必须设置 :前端 form 必须设置
enctype="multipart/form-data" -
字段名一致:后端 @RequestParam 的 value 必须与前端的 name 属性一致
-
大小限制:根据需求合理配置文件大小限制
-
JSON 处理:复杂对象可以转为 JSON 字符串传递,后端再解析
-
验证顺序:建议先验证文件,再处理业务逻辑
-
资源清理:文件流会自动关闭,但建议使用 try-with-resources
推荐方式 :使用 @RequestParam + @ModelAttribute 组合,清晰分离文件数据和表单数据,便于维护。