RestTemplate上传文件解决方案

简介: 当对接文件上传模块时,需要对接上传文件的接口,而我们模块的数据是以字节数组存在的(已经操作过了的字节数组,存在于内存中)接口是以form-data的形式上传的,其中需要上传MultipartFIle,如果使用MultipartFile放入到请求的 fromMap中,然后再上传这个文件,会报(ByteArrayInputStream no serialized)的错误,也就是没有注入对应的bean的错误。。

背景:由于Hutool中的HttpUtil没有对应的连接池,所以使用Spring自带的RestTemplate来进行其他系统的Http信息的对接。

问题出现:当对接文件模组时,需要对接上传文件的接口,而我们模块的数据是以字节数组存在的(已经操作过了的字节数组,存在于内存中)接口是以form-data的形式上传的,其中需要上传MultipartFIle,如果使用MultipartFile放入到请求的 from map中,然后再上传这个文件,会报(ByteArrayInputStream no  serialized)的错误,也就是没有注入对应的bean的错误。

问题代码:

Mapheaders=newHashMap<String, String>(1);
headers.put("Content-Type", "multipart/form-data");
//使用字节数据进行创建文件【方法入参为字节数组】MultipartFilefile=newMockMultiPartFile(filebyte);
MultiValueMap<String, Object>form=newLinkedMultiValueMap<>();
form.add("appId", fileDealConstants.getFileDealAppId());
form.add("file", file);
form.add("filename", fileName);
form.add("fileInvalidDay", "0");


问题报错【截取主要报错】:

org.springframework.http.converter.HttpMessageConversionException: Typedefinitionerror: [simpletype, classjava.io.ByteArrayInputStream]; nestedexceptioniscom.fasterxml.jackson.databind.exc.InvalidDefinitionException: Noserializerfoundforclassjava.io.ByteArrayInputStreamandnopropertiesdiscoveredtocreateBeanSerializer (toavoidexception, disableSerializationFeature.FAIL_ON_EMPTY_BEANS) (throughreferencechain: org.springframework.mock.web.MockMultipartFile["inputStream"])
Causedby: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Noserializerfoundforclassjava.io.ByteArrayInputStreamandnopropertiesdiscoveredtocreateBeanSerializer (toavoidexception, disableSerializationFeature.FAIL_ON_EMPTY_BEANS) (throughreferencechain: org.springframework.mock.web.MockMultipartFile["inputStream"])


百度RestTemplate上传文件的方法,有两种方法解决这种情况。

方法1

使用FileSystemResource进行上传,但是FileSystemResource的创建只能通过 File  或者 文件的路径来生成,也就是说文件已经真实存在磁盘了,所以解决方案是先存到本地然后再进行读取,然后再删除【这种方法看着貌似有点麻烦】,如果需要存到本地的话,还需要判断本地是否有重复的文件,如果删除失败的话,服务器上也会存有许多的垃圾文件,需要定时清除,所以最终采用方法2

此处FileSysteResource的构造函数,只能读取对应的真实存在的文件

publicFileSystemResource(Stringpath)
publicFileSystemResource(Filefile)
publicFileSystemResource(PathfilePath)
publicFileSystemResource(FileSystemfileSystem, Stringpth)


方法2

继承InputStreamResource重写一个构造方法,然后重写getFileName和contentLength即可,可以直接代替MultipartFIle来上传。

参考博客【里面会有一些源码的解析】:https://www.cnblogs.com/shineman-zhang/articles/13070118.html

【写代码过程中的发现】:如果出现InputStream 已经关闭的错误,查看对应的使用方法是不是把InputStream 用完了就关了


CommonInputStreamResource

publicclassCommonInputStreamResourceextendsInputStreamResource {
/**** 文件長度*/privateintlength;
/**** 文件名稱*/privateStringfileName;
publicCommonInputStreamResource(InputStreaminputStream) {
super(inputStream);
    }
publicCommonInputStreamResource(InputStreaminputStream, intlength,StringfileName) {
super(inputStream);
this.length=length;
this.fileName=fileName;
    }
/*** 覆写父类方法* 如果不重写这个方法,并且文件有一定大小,那么服务端会出现异常* {@code The multi-part request contained parameter data (excluding uploaded files) that exceeded}** @return*/@OverridepublicStringgetFilename() {
returnthis.fileName;
    }
/*** 覆写父类 contentLength 方法* 因为 {@link org.springframework.core.io.AbstractResource#contentLength()}方法会重新读取一遍文件,* 而上传文件时,restTemplate 会通过这个方法获取大小。然后当真正需要读取内容的时候,发现已经读完,会报如下错误。* <code>* java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times* at org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:96)* </code>* <p>* ref:com.amazonaws.services.s3.model.S3ObjectInputStream#available()** @return*/@OverridepubliclongcontentLength() {
intestimate=length;
returnestimate==0?1 : estimate;
    }
}



无问题的代码:

//设置headerMapheaders=newHashMap<String, String>(1);
headers.put("Content-Type", "multipart/form-data");
//根据文件字节数组转对应的 CommonInputStreamResource,然后再上传ByteArrayInputStreaminputStream=newByteArrayInputStream(fileBytes);
CommonInputStreamResourcecommonInputStreamResource=newCommonInputStreamResource(inputStream,fileBytes.length,fileName);
//必须使用LinkedMultiValueMap传参MultiValueMap<String, Object>form=newLinkedMultiValueMap<>();
form.add("appId", fileDealConstants.getFileDealAppId());
//将文件数据放入到map中form.add("file", commonInputStreamResource);
form.add("filename", fileName);
form.add("fileInvalidDay", "0");
StringrequestUrl=fileDealConstants.getFileDealUrl() +fileDealConstants.getUploadPath();
ResponseEntity<String>responseEntity=restTemplateUtil.postResponseEntity(headers, fileDealConstants.getFileDealUrl() +fileDealConstants.getUploadPath(), form, newParameterizedTypeReference<String>(){});


RestTemplateUtil部分代码

@Slf4j@Component@Configuration@AllArgsConstructorpublicclassRestTemplateUtil {
@AutowiredprivateRestTemplaterestTemplate;
public<T>ResponseEntity<T>postResponseEntity(Map<String, String>headers, Stringurl, Objectbody, ParameterizedTypeReference<T>reference, Object... uriVariables) {
MultiValueMap<String, String>map=newHttpHeaders();
if (!ObjectUtils.isEmpty(headers)) {
for (Map.Entry<String, String>entry : headers.entrySet()) {
map.add(entry.getKey(), entry.getValue());
            }
        }
returnrestTemplate.exchange(url, HttpMethod.POST, newHttpEntity<>(body, map), reference, uriVariables);
    }
}



目录
相关文章
SpringMVC上传文件的三种方式
SpringMVC上传文件的三种方式
|
4月前
|
存储 前端开发 Java
SpringBoot使用云端资源url下载文件的接口写法
在Spring Boot中实现从云端资源URL下载文件的功能可通过定义REST接口完成。示例代码展示了一个`FileDownloadController`,它包含使用`@GetMapping`注解的方法`downloadFile`,此方法接收URL参数,利用`RestTemplate`下载文件,并将文件字节数组封装为`ByteArrayResource`返回给客户端。此外,通过设置HTTP响应头,确保文件以附件形式下载。这种方法适用于从AWS S3或Google Cloud Storage等云服务下载文件。
464 7
|
4月前
|
XML JSON Java
通过 Feign 进行文件上传
通过 Feign 进行文件上传
195 7
|
4月前
|
JSON 数据格式
postman 实用教程(含带 token 访问需登录权限的接口、测试文件上传接口、测试文件下载接口)
postman 实用教程(含带 token 访问需登录权限的接口、测试文件上传接口、测试文件下载接口)
629 0
|
JSON Java Maven
接口调用神器RestTemplate(四)
接口调用神器RestTemplate
141 1
|
6月前
|
JSON 中间件 数据格式
在自定义服务器框架中处理 POST 请求
在自定义服务器框架中处理 POST 请求
|
缓存
RestTemplate请求访问简单使用
RestTemplate请求访问简单使用
91 1
|
Java 测试技术 Apache
接口调用神器RestTemplate(一)
接口调用神器RestTemplate
100 0
|
JSON 数据格式
接口调用神器RestTemplate(三)
接口调用神器RestTemplate
155 0