Spring Cloud Feign上传文件
缘起
最近有个需求就是把图片格式化成300*240的大小并且作为小程序分享时的封面,A服务负责处理图片,B服务是公共服务,负责上传图片到OSS并且生成访问链接。
Feign的Server端
这个就是正常的Controller
@PostMapping("/uploadImg")
public Result<FileUploadDTO> uploadImg(@RequestParam("fileData") MultipartFile file,String title)
Feign的Client端
这个有点特殊,首先要有一个Config提供SpringFormEncoder
public class FeignConfig {
@Bean
@Scope("prototype")
public Encoder multipartFormEncoder() {
return new SpringFormEncoder();
}
}
然后Feign Client如下:
@FeignClient(name= "FileUploadClient",url="${url}",configuration = FeignConfig.class)
public interface FileUploadService {
@PostMapping(value = "/uploadImg",produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<FileUploadDTO> uploadImg(@RequestPart("fileData") MultipartFile file, @RequestParam(value = "title",required = false)String title);
}
注意:
@FeignClient
注解里面configuration
引用上面的FeignConfig
配置@PostMapping
注解里面consumes
要配置MediaType.MULTIPART_FORM_DATA_VALUE
,代表Content-Type
是multipart/form-data
,默认情况下是application/json
- 文件要用
@RequestPart
,不能用@RequestParam
- 这个类中不要再写其它的接口了,因为其它接口一律使用给定的
SpringFormEncoder
,而非默认的Encoder
实现类 - 配置类
FeignConfig
不要加@Configuration
或者@Compoent
注解,会替代掉默认的配置
调用
//tiltle是传入的文件名 originalFilename是原始文件名 bytes是文件字节码
MockMultipartFile mockMultipartFile = new MockMultipartFile(title,originalFilename, MediaType.MULTIPART_FORM_DATA_VALUE,bytes);
Result<FileUploadDTO> fileUploadDTOResult = fileUploadService .uploadImg(mockMultipartFile, title);
注意:
- 普通的File转
MultipartFile
可以使用org.springframework.mock.web.MockMultipartFile
,是spring-test
包里的。MultipartFile
是个接口,spring中的实现类是
//org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile
private static class StandardMultipartFile implements MultipartFile, Serializable {
private final Part part;
private final String filename;
public StandardMultipartFile(Part part, String filename) {
this.part = part;
this.filename = filename;
}
//... 省略getter 和 setter
public void transferTo(File dest) throws IOException, IllegalStateException {
this.part.write(dest.getPath());
if (dest.isAbsolute() && !dest.exists()) {
FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
}
}
public void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest));
}
}
这玩意儿是个private
的静态内部类,外面没法用。
遇到报错
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:189)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.getMultiPartStream(FileItemIteratorImpl.java:205)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:224)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:276)
at org.apache.catalina.connector.Request.parseParts(Request.java:2784)
at org.apache.catalina.connector.Request.getParts(Request.java:2685)
at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:773)
at jakarta.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:315)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:93)
... 47 common frames omitted
原因是单纯的 Content-Type
头设置为multipart/form-data
是不行,组装request body的时候缺少文件内容开头,正确的格式如下:
------WebKitFormBoundaryAMsovNKq5B8TpBPy
Content-Disposition: form-data; name="fileData"; filename="stream报错.PNG"
Content-Type: image/png
------WebKitFormBoundaryAMsovNKq5B8TpBPy--
所以除了要加上consumes = MediaType.MULTIPART_FORM_DATA_VALUE
这个头,还要配置上SpringFormEncoder
和@RequestPart
注解。
附图片处理逻辑
/**
图片处理使用的是javax中的组件 如下
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
*/
/**
* 指定宽高压缩图片
* @param bufferedImage 图片
* @param width 宽
* @param height 高
* @param suffix 后缀名 如jpg png等
*/
public static byte[] compressImage(BufferedImage bufferedImage,int width,int height,String suffix) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){
BufferedImage compressedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 绘制压缩后的图片
Graphics2D g2d = compressedImage.createGraphics();
g2d.drawImage(bufferedImage, 0, 0, width, height, null);
g2d.dispose();
ImageIO.write(compressedImage,suffix,byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}catch (Exception ex){
log.error("压缩图片报错",ex);
}
return null;
}