Spring MVC 文件上传

Spring MVC文件上传

在应用系统中,文件上传是非常常用的系统功能,Spring MVC为上传文件提供了良好的支持。首先Spring MVC的文件上传是通过MultipartResolver(Multipart解析器)处理的,MultipartResolver是一个接口,它有两个实现类,具体说明如下:

实现类 依赖要求 备注
CommonsMultipartResolver 依赖Apache下的fileupload项目 需要导入第三方依赖包
StandardServletMultipartResolver 依赖Servlet3.0及以上版本 无需第三方依赖,Spring 3.1后支持

StandardServletMultipartResolver上传实现

核心配置类(ServletConfig)

@Configuration
@ComponentScan(basePackages = "com.controller")
@EnableWebMvc // 以注解的方式驱动 Spring MVC(包含注解映射的HandlerMapping及HandlerAdapter等相关组件)
public class ServletConfig implements WebMvcConfigurer {
    private final MessageSource messageSource;

    public ServletConfig(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    /**
     * 配置静态资源不被拦截
     * 静态资源:css, js, 图片, 视频, 音频, 文档
     * @param registry 资源处理器
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**") // 静态资源的url:根路径下
                .addResourceLocations("/public/"); // 静态资源存放的位置(访问链接无需包含public)
    }

    /**
     * 处理跨域问题(全局),局部跨域使用@CrossOrigin注解
     * @param registry 跨域处理器
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 允许所有的请求跨域
                .allowedOrigins("http://localhost:5173") // 跨域来源(携带cookie时不可设为*)
                .allowedHeaders("*") // 允许所有的请求头
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD") // 允许的请求方式
                .allowCredentials(true) // 允许携带cookie
                .maxAge(3600); // 跨域访问有效期:1小时
    }

    /**
     * 配置视图解析器
     * @param registry 视图解析器
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(
                new InternalResourceViewResolver("/WEB-INF/pages/", ".jsp")
        );
    }

    @Bean
    public MultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }
}

关键说明:

  • MultipartResolver的Bean名称必须为multipartResolver(Spring约定),若方法名不一致需显式指定:@Bean(name = "multipartResolver")
  • 该配置类通过@EnableWebMvc启用注解驱动的Spring MVC,同时配置了静态资源、跨域、视图解析器等基础功能

Servlet容器配置(WebConfig)

StandardServletMultipartResolver需要通过Servlet容器配置启用Servlet3.0的Multipart解析,需继承AbstractAnnotationConfigDispatcherServletInitializer类:

package com.config;

import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * Root WebApplicationContext 配置类
     */
    @Override
    protected Class<?> @Nullable [] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * Servlet WebApplicationContext 配置类
     */
    @Override
    protected Class<?> @Nullable [] getServletConfigClasses() {
        return new Class[]{ServletConfig.class};
    }

    /**
     * 前端控制器 DispatcherServlet 拦截的请求
     * /: 所有请求(包含jsp)
     * /*: 所有请求(不包含jsp)
     * *.suffix:以指定后缀结尾的请求
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 设置文件上传的配置
     * @param registration Servlet 动态加载配置
     */
    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        // 设置允许上传的单个文件大小:5M(-1表示不限制)
        var singleMax = 5 * 1024 * 1024;
        // 设置允许上传的总文件大小:20M(-1表示不限制)
        var totalMax = 20 * 1024 * 1024;
        registration.setMultipartConfig(
                new MultipartConfigElement(
                        null, // location:临时文件存放路径(null使用Servlet容器默认目录)
                        singleMax, // maxFileSize:单个文件最大大小
                        totalMax, // maxRequestSize:总文件最大大小
                        0 // fileSizeThreshold:内存阈值(0表示所有文件直接写入临时文件)
                )
        );
    }
}

MultipartConfigElement参数说明:

参数 说明
location 临时文件存放路径(未完成上传/超出内存阈值的文件),null使用容器默认目录(需保证目录有读写权限)
maxFileSize 单个文件最大大小(字节),-1表示无限制
maxRequestSize 单次请求总文件大小(字节),-1表示无限制
fileSizeThreshold 内存阈值,超过该值的文件写入临时目录,0表示所有文件直接写入临时文件

控制器实现(FileController)

package com.controller;

import com.response.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

@RestController
public class FileController {
    
    @PostMapping("/file/upload")
    public ResponseResult upload(MultipartFile[] avatar) throws IOException {
        if(avatar != null) {
            var paths = new ArrayList<String>();
            for(var file : avatar) {
                if(!file.isEmpty()) {
                    // 替换为实际的文件存储路径
                    var target = new File("你自己的路径" + file.getOriginalFilename());
                    file.transferTo(target); // 保存文件到指定路径
                    // 构造文件访问URL(需与静态资源配置对应)
                    paths.add("http://localhost:8080/avatar/" + file.getOriginalFilename());
                }
            }
            return ResponseResult.ok(paths);
        }
        return ResponseResult.fail("上传文件为空");
    }
}

说明:

  • 接收前端传递的多文件(参数名avatar需与前端FormData的key一致)
  • 通过file.transferTo(target)方法将文件保存到指定路径
  • 返回文件访问URL列表或错误信息

前端实现(Vue3)

<template>
    <div>
        <input type="file" name="avatar"/> <br/>
        <input type="file" name="avatar"/> <br/>
        <input type="file" name="avatar"/> <br/>
        <button @click="handleClick()">上传</button>
    </div>
</template>

<script setup>
import request from '@/utils/request';

async function handleClick() {
    // 获取所有文件输入框
    let nodes = document.getElementsByName("avatar")
    let files = []
    // 收集非空文件
    for (let element of nodes) {
        let file = element.files[0]
        if (file) files.push(file)
    }
    console.log(files)
    
    // 构造FormData(文件上传必须使用FormData格式)
    const params = new FormData()
    for(let file of files) {
        console.log(file)
        params.append("avatar", file) // key需与后端接口参数名一致
    }
    console.log(params)

    // 发送文件上传请求
    const {data: result} = await request.post("/file/upload", params, {
        headers: {
            'Content-Type': 'multipart/form-data' // 显式指定内容类型
        }
    })
    console.log(result.data)
}
</script>

关键注意事项(request.js配置)

请求拦截器中禁止手动设置Content-Type,需由浏览器自动处理(FormData格式会自动生成正确的Content-Type并包含边界标识):

// request.js 正确配置示例
request.interceptors.request.use(
  (config) => {
    // 错误示例(若存在需删除):
    // config.headers['Content-Type'] = 'application/json' 
    // config.headers['Content-Type'] = 'multipart/form-data'

    // 正确做法:只添加token等通用请求头,不修改Content-Type
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

使用StandardServletMultipartResolver实现Spring MVC文件上传的核心步骤:

  1. 配置MultipartResolver的Bean(名称必须为multipartResolver
  2. 通过WebConfig配置文件上传的大小限制、临时目录等参数
  3. 编写控制器接收并处理上传文件
  4. 前端使用FormData格式提交文件,且不手动设置Content-Type
  5. 确保跨域、静态资源访问等基础配置正确
posted @ 2025-12-25 16:22  Jing61  阅读(23)  评论(0)    收藏  举报