SpringBoot文件上傳同時(shí)接收復(fù)雜參數(shù)的過(guò)程詳解
環(huán)境信息
Spring Boot:2.0.8.RELEASE
Spring Boot內(nèi)置的tomcat:tomcat-embed-core 8.5.37
問(wèn)題描述
收到文件上傳的開(kāi)發(fā)工作,要求能適配各種場(chǎng)景,并且各場(chǎng)景的請(qǐng)求參數(shù)不一樣,因此接收的參數(shù)不能是固定的幾個(gè)字段,要有類(lèi)似Map的字段來(lái)接收動(dòng)態(tài)參數(shù)。
擬使用MultipartFile[] files來(lái)接收文件列表,用自定義對(duì)象UploadFileDto來(lái)接收上傳參數(shù)(里面包含一個(gè)Map)
UploadFileDto.java
@ApiModel("文件上傳dto") @Data public class UploadFileDto { @ApiModelProperty("處理器,在Spring容器里的名稱") private String handlerName; @ApiModelProperty("交易代碼") private String bizCode; @ApiModelProperty("交易ID") private String bizId; @ApiModelProperty("上傳配置名稱,對(duì)應(yīng)配置文件里的配置") private String uploadConfigName; @ApiModelProperty("動(dòng)態(tài)信息") private Map<String, Object> dynamicDto; @ApiModelProperty("上傳時(shí)間") private String uploadDt; @ApiModelProperty("限定文件大小") private long defaultFileSize; @ApiModelProperty("限定文件格式,多個(gè)用逗號(hào)隔開(kāi)") private String defaultFileSuffix; @ApiModelProperty("默認(rèn)路徑之后的文件上級(jí)目錄") 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來(lái)接收文件列表
使用@RequestPart() UploadFileDto uploadFileDto來(lái)接收復(fù)雜參數(shù)。
UpDownLoadService是上傳實(shí)現(xiàn)類(lèi),這里暫時(shí)不暫時(shí)源碼。
可是,在訪問(wèn)該接口的時(shí)候,報(bào)錯(cuò)了: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)
錯(cuò)誤分析
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported,意思是應(yīng)用不支持'application/octet-stream'這種文件類(lèi)型
Swagger2里的請(qǐng)求參數(shù):
分析異常堆棧,是在AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法里拋出的:
分析這個(gè)方法里的代碼,發(fā)現(xiàn)Spring在嘗試使用各個(gè)消息轉(zhuǎn)換器(HttpMessageConverter)來(lái)處理請(qǐng)求。這里的contentType值是'application/octet-stream',發(fā)現(xiàn)找不到可用的消息轉(zhuǎn)換器:
genericConverter.canRead(targetType, contextClass, contentType) ,因此無(wú)法解析、轉(zhuǎn)換消息,因此拋出了異常。
確切的說(shuō),上面說(shuō)的是處理請(qǐng)求參數(shù)uploadFileDto,第一個(gè)請(qǐng)求參數(shù)files已經(jīng)在下面這個(gè)地方處理了(文件類(lèi)型參數(shù)使用MultipartResolutionDelegate.resolveMultipartArgument來(lái)處理):
注:
messageConverters的值(這個(gè)看各個(gè)應(yīng)用里注冊(cè)的消息轉(zhuǎn)換器類(lèi)型、順序):
經(jīng)常使用的是MappingJackson2HttpMessageConverter消息轉(zhuǎn)換器,這個(gè)是SpringBoot默認(rèn)用來(lái)處理消息的,將原始請(qǐng)求轉(zhuǎn)成json格式,再轉(zhuǎn)成目標(biāo)類(lèi)型的格式,是Jackson
targetClass是UploadFileDto對(duì)應(yīng)的Class
解決方法
明白了問(wèn)題產(chǎn)生的原因之后,就有了解決思路:增加一個(gè)消息轉(zhuǎn)換器,用于支持'application/octet-stream'。
最簡(jiǎn)單的添加方式,新增一個(gè)類(lèi),繼承AbstractJackson2HttpMessageConverter,這個(gè)也是上面說(shuō)的MappingJackson2HttpMessageConverter的父類(lèi)
@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; } }
加了上面的轉(zhuǎn)換器之后:
讀取之后的body類(lèi)型就是UploadFileDto
至此,問(wèn)題解決,后端程序能夠正常接收、處理前端發(fā)過(guò)來(lái)的文件列表,以及復(fù)雜參數(shù)。
簡(jiǎn)單參數(shù)
如果后端程序不需要接收復(fù)雜參數(shù),而是只需要固定的幾個(gè)簡(jiǎn)單參數(shù),那么就很簡(jiǎn)單,比如:
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); }
此時(shí),contentType值是'application/octet-stream'的String的參數(shù),Spring有提供了默認(rèn)的消息轉(zhuǎn)換器StringHttpMessageConverter,支持所有的格式,就不需要自定義MappingJackson2HttpMessageConverter消息轉(zhuǎn)換器了。
總結(jié)
回顧一下,如果需要復(fù)雜參數(shù)(自定義對(duì)象接收前端參數(shù)),那么需要自定義消息轉(zhuǎn)換器(如AbstractJackson2HttpMessageConverter),來(lái)支持contentType值是'application/octet-stream'類(lèi)型的參數(shù),并將其轉(zhuǎn)換成目標(biāo)格式;
如果不需要復(fù)雜參數(shù),只是String等類(lèi)型,那么不需要自定義消息轉(zhuǎn)換器;
消息轉(zhuǎn)換器是SpringBoot處理前端傳輸?shù)臄?shù)據(jù),并轉(zhuǎn)換成接口參數(shù)的類(lèi)型的轉(zhuǎn)換器,轉(zhuǎn)換前、轉(zhuǎn)換后還支持自定義插件處理。詳見(jiàn)AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters
@RequestParam和@RequestPart的區(qū)別,可以自行查找資料
參考文獻(xiàn):
rest - @RequestPart with mixed multipart request, Spring MVC 3.2 - Stack Overflow
@RequestPart同時(shí)接收文件和json后端報(bào)錯(cuò) - 簡(jiǎn)書(shū) (jianshu.io)
到此這篇關(guān)于SpringBoot文件上傳同時(shí),接收復(fù)雜參數(shù)的文章就介紹到這了,更多相關(guān)SpringBoot文件上傳接收參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用@PostConstruct注解導(dǎo)入配置方式
這篇文章主要介紹了SpringBoot使用@PostConstruct注解導(dǎo)入配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Spring boot 實(shí)現(xiàn)單個(gè)或批量文件上傳功能
這篇文章主要介紹了Spring boot 實(shí)現(xiàn)單個(gè)或批量文件上傳功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-08-08使用Java?Socket實(shí)現(xiàn)GPS定位數(shù)據(jù)處理
在許多應(yīng)用場(chǎng)景中,如車(chē)輛追蹤、移動(dòng)設(shè)備定位等,GPS定位數(shù)據(jù)的實(shí)時(shí)獲取和處理至關(guān)重要,本文將介紹如何使用Java?Socket編程來(lái)接收GPS設(shè)備發(fā)送的數(shù)據(jù)并進(jìn)行處理,需要的朋友可以參考下2024-07-07Java中ArrayList和LinkedList的遍歷與性能分析
這篇文章主要給大家介紹了ArrayList和LinkedList這兩種list的五種循環(huán)遍歷方式,各種方式的性能測(cè)試對(duì)比,根據(jù)ArrayList和LinkedList的源碼實(shí)現(xiàn)分析性能結(jié)果,總結(jié)結(jié)論。相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考價(jià)值,有需要的朋友們下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。2016-12-12Mybatis源碼分析之存儲(chǔ)過(guò)程調(diào)用和運(yùn)行流程
這一篇我們學(xué)習(xí)一下Mybatis調(diào)用存儲(chǔ)過(guò)程的使用和運(yùn)行流程,首先我們創(chuàng)建一個(gè)簡(jiǎn)單的存儲(chǔ)過(guò)程,具體創(chuàng)建過(guò)程大家可以通過(guò)本文學(xué)習(xí)下2016-11-11Spring Cloud引入Eureka組件,完善服務(wù)治理
這篇文章主要介紹了Spring Cloud引入Eureka組件,完善服務(wù)治理的過(guò)程詳解,幫助大家更好的理解和使用spring cloud,感興趣的朋友可以了解下2021-02-02SpringBoot3集成Thymeleaf的過(guò)程詳解
在現(xiàn)代的Web開(kāi)發(fā)中,構(gòu)建靈活、動(dòng)態(tài)的用戶界面是至關(guān)重要的,Spring Boot和Thymeleaf的結(jié)合為開(kāi)發(fā)者提供了一種簡(jiǎn)單而強(qiáng)大的方式來(lái)創(chuàng)建動(dòng)態(tài)的Web應(yīng)用,本文將介紹如何在Spring Boot項(xiàng)目中集成Thymeleaf,并展示一些基本的使用方法,需要的朋友可以參考下2024-01-01