SpringBoot文件上传同时接收复杂参数的过程详解

 更新时间:2022年12月19日 16:02:37   作者:lzhfdxhxm  
这篇文章主要介绍了SpringBoot文件上传同时,接收复杂参数,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

环境信息

        Spring Boot:2.0.8.RELEASE

        Spring Boot内置的tomcat:tomcat-embed-core 8.5.37

问题描述

        收到文件上传的开发工作,要求能适配各种场景,并且各场景的请求参数不一样,因此接收的参数不能是固定的几个字段,要有类似Map的字段来接收动态参数。

        拟使用MultipartFile[] files来接收文件列表,用自定义对象UploadFileDto来接收上传参数(里面包含一个Map)

        UploadFileDto.java

@ApiModel("文件上传dto")
@Data
public class UploadFileDto {
    @ApiModelProperty("处理器,在Spring容器里的名称")
    private String handlerName;
 
    @ApiModelProperty("交易代码")
    private String bizCode;
 
    @ApiModelProperty("交易ID")
    private String bizId;
 
    @ApiModelProperty("上传配置名称,对应配置文件里的配置")
    private String uploadConfigName;
 
    @ApiModelProperty("动态信息")
    private Map<String, Object> dynamicDto;
 
    @ApiModelProperty("上传时间")
    private String uploadDt;
 
    @ApiModelProperty("限定文件大小")
    private long defaultFileSize;
 
    @ApiModelProperty("限定文件格式,多个用逗号隔开")
    private String defaultFileSuffix;
 
    @ApiModelProperty("默认路径之后的文件上级目录")
    private String parentDir;
 
    @ApiModelProperty("备注信息")
    private String remark;
 
    @ApiModelProperty("上传后的文件信息")
    private List<FileInfoDto> fileInfoDtos;
 
}

Controller:

@RestController
@RequestMapping("/fileCommmon")
@Api(tags = "公共-文件操作")
public class FileUpDownLoadController extends BaseController {
 
    @Resource
    private UpDownLoadService upDownLoadService;
 
    @PostMapping(value = "/upload")
    @ApiOperation("文件上传")
    public ResponseDto<UploadFileDto> uploadFile(@RequestParam("files") MultipartFile[] files, @RequestPart() UploadFileDto uploadFileDto) {
        upDownLoadService.uploadFile(uploadFileDto, files);
        return getResponseDto(uploadFileDto);
    }
}

Controller方法,使用@RequestParam("files") MultipartFile[] files来接收文件列表

使用@RequestPart() UploadFileDto uploadFileDto来接收复杂参数。

UpDownLoadService是上传实现类,这里暂时不暂时源码。

可是,在访问该接口的时候,报错了:org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:226)
    at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:134)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:981)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:884)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:858)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)

错误分析

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported,意思是应用不支持'application/octet-stream'这种文件类型

Swagger2里的请求参数:

 分析异常堆栈,是在AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法里抛出的:

         分析这个方法里的代码,发现Spring在尝试使用各个消息转换器(HttpMessageConverter)来处理请求。这里的contentType值是'application/octet-stream',发现找不到可用的消息转换器:

genericConverter.canRead(targetType, contextClass, contentType) ,因此无法解析、转换消息,因此抛出了异常。

         确切的说,上面说的是处理请求参数uploadFileDto,第一个请求参数files已经在下面这个地方处理了(文件类型参数使用MultipartResolutionDelegate.resolveMultipartArgument来处理):

注:

messageConverters的值(这个看各个应用里注册的消息转换器类型、顺序):

经常使用的是MappingJackson2HttpMessageConverter消息转换器,这个是SpringBoot默认用来处理消息的,将原始请求转成json格式,再转成目标类型的格式,是Jackson

targetClass是UploadFileDto对应的Class

解决方法

        明白了问题产生的原因之后,就有了解决思路:增加一个消息转换器,用于支持'application/octet-stream'。

最简单的添加方式,新增一个类,继承AbstractJackson2HttpMessageConverter,这个也是上面说的MappingJackson2HttpMessageConverter的父类

@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
 
    /**
     * Converter for support http request with header Content-Type: multipart/form-data
     */
    public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
    }
 
    @Override
    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
        return false;
    }
 
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return false;
    }
 
    @Override
    protected boolean canWrite(MediaType mediaType) {
        return false;
    }
}

 加了上面的转换器之后:

 读取之后的body类型就是UploadFileDto

 至此,问题解决,后端程序能够正常接收、处理前端发过来的文件列表,以及复杂参数。

简单参数

如果后端程序不需要接收复杂参数,而是只需要固定的几个简单参数,那么就很简单,比如:

Controller:

    @PostMapping(value = "/upload2")
    @ApiOperation("文件上传2")
    public ResponseDto<String> uploadFile(@RequestParam("files") MultipartFile[] files,  @RequestPart() String remark) {
        System.out.println("remark = " + remark);
        upDownLoadService.uploadFile(remark, files);
        return getResponseDto(remark);
    }

此时,contentType值是'application/octet-stream'的String的参数,Spring有提供了默认的消息转换器StringHttpMessageConverter,支持所有的格式,就不需要自定义MappingJackson2HttpMessageConverter消息转换器了。

总结

回顾一下,如果需要复杂参数(自定义对象接收前端参数),那么需要自定义消息转换器(如AbstractJackson2HttpMessageConverter),来支持contentType值是'application/octet-stream'类型的参数,并将其转换成目标格式;

如果不需要复杂参数,只是String等类型,那么不需要自定义消息转换器;

消息转换器是SpringBoot处理前端传输的数据,并转换成接口参数的类型的转换器,转换前、转换后还支持自定义插件处理。详见AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters

@RequestParam和@RequestPart的区别,可以自行查找资料

参考文献:

 rest - @RequestPart with mixed multipart request, Spring MVC 3.2 - Stack Overflow

@RequestPart同时接收文件和json后端报错 - 简书 (jianshu.io)

到此这篇关于SpringBoot文件上传同时,接收复杂参数的文章就介绍到这了,更多相关SpringBoot文件上传接收参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现带缓冲的输入输出流

    Java实现带缓冲的输入输出流

    本文详细讲解了Java实现带缓冲的输入输出流,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • springboot 跨域配置类及跨域请求配置

    springboot 跨域配置类及跨域请求配置

    这篇文章主要介绍了springboot 跨域配置类及跨域请求配置,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • Java线程操作的常见方法【线程名称获取、设置、线程启动判断等】

    Java线程操作的常见方法【线程名称获取、设置、线程启动判断等】

    这篇文章主要介绍了Java线程操作的常见方法,结合实例形式总结分析了java线程的创建、线程名称的获取、设置以及线程启动的判断等相关操作实现技巧,需要的朋友可以参考下
    2019-10-10
  • java 实现DES 加密解密的示例

    java 实现DES 加密解密的示例

    这篇文章主要介绍了java 实现DES 加密解密的示例代码,帮助大家更好的理解和使用Java进行加解密,感兴趣的朋友可以了解下
    2020-12-12
  • SpringBoot前后端json数据交互的全过程记录

    SpringBoot前后端json数据交互的全过程记录

    现在大多数互联网项目都是采用前后端分离的方式开发,下面这篇文章主要给大家介绍了关于SpringBoot前后端json数据交互的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • MyBatis主键生成策略中useGeneratedKeys和<selectKey>的区别

    MyBatis主键生成策略中useGeneratedKeys和<selectKey>的区别

    本文主要介绍了MyBatis主键生成策略中useGeneratedKeys和<selectKey>的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-01-01
  • Java SpringBoot高级用法详解

    Java SpringBoot高级用法详解

    这篇文章主要为大家详细介绍了Java Spring Boot的一些高级用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-09-09
  • myeclipse智能提示设置的实现方法

    myeclipse智能提示设置的实现方法

    本篇文章介绍了,myeclipse智能提示设置的实现方法。需要的朋友参考下
    2013-05-05
  • Java Stream流中的filter()使用方法举例详解

    Java Stream流中的filter()使用方法举例详解

    filter()是Java Stream API中的中间操作,用于根据给定的Predicate条件筛选流中的元素,它通过接收一个返回boolean值的函数(断言)作为参数,筛选出满足条件的元素并收集到新的流中,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-11-11
  • SpringBoot集成PostgreSQL并设置最大连接数

    SpringBoot集成PostgreSQL并设置最大连接数

    本文主要介绍了SpringBoot集成PostgreSQL并设置最大连接数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11

最新评论