SpringMVC multipart 文件上传

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("."));
    }
}

注意事项:

  1. enctype 必须设置 :前端 form 必须设置 enctype="multipart/form-data"

  2. 字段名一致:后端 @RequestParam 的 value 必须与前端的 name 属性一致

  3. 大小限制:根据需求合理配置文件大小限制

  4. JSON 处理:复杂对象可以转为 JSON 字符串传递,后端再解析

  5. 验证顺序:建议先验证文件,再处理业务逻辑

  6. 资源清理:文件流会自动关闭,但建议使用 try-with-resources

推荐方式 :使用 @RequestParam + @ModelAttribute 组合,清晰分离文件数据和表单数据,便于维护。

相关推荐
得一录1 小时前
ModuleNotFoundError: No module named ‘llama_index.llms
开发语言·人工智能
j7~1 小时前
【C++】类和对象(下)--详解之再探构造函数,友元,static成员,类型转换等
开发语言·c++·类型转换·友元·匿名对象·内部类·编译器优化
西凉的悲伤1 小时前
Spring Security + JWT 登录认证完整实践指南
java·后端·spring·spring security·jwt
稷下元歌1 小时前
7天学会plc加机器视觉关于运动控制部份,配套视频在bib
开发语言·c++·git·vscode·python·docker·pip
薇茗1 小时前
【C++】 类与对象 基础篇
开发语言·c++·基础语法·类与对象
晚笙coding1 小时前
从零讲透 LangChain 输出格式化:让模型真的“能用”
java·开发语言·langchain
奋斗的小方1 小时前
Java进阶篇1-1:异常
java·开发语言·python
码语智行1 小时前
行政区划 ZIP 导入(importZip)
java
sycmancia1 小时前
Qt——多页面切换组件
开发语言·qt