最近折腾了好久的feign文件上传,记录一下。重点是在这期间碰到了很奇怪的问题。
先说feign的文件上传
本身这个文件上传不难,网上一找一大把,大都是类似的代码。
说一下我这个的场景:我这个是发邮件需要带着附件。整个过程中出了文件,还有发件人邮箱,主题等的邮件参数
我就直接贴代码了
一、服务端
关键点解释:
(1)这个必须要加的:consumes = MediaType.MULTIPART_FORM_DATA_VALUE
(2)我注释的代码是用来查看文件的。因为我在测试的时候,设置了文件名叫test.jpg,这就导致了我用@RequestPart(value = "attachmentFile")的时候获取不到。这里我打印了一下文件,其中的part.getName()就是能用的参数名。这个如果碰到了文件是null等类似的问题,可以尝试着这么调查一下。
@PostMapping(value = "/sendMail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public boolean sendMail(@RequestParam("to") String to,
@RequestParam("title") String title,
@RequestParam("content") String content,
@RequestParam(value = "attachmentName",required = false) String attachmentName,
@RequestPart(value = "attachmentFile",required = false) MultipartFile attachmentFile) throws Exception {
// HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
// Collection<Part> parts = request.getParts();
// for (Iterator<Part> iterator = parts.iterator(); iterator.hasNext();) {
// Part part = iterator.next();
// System.out.println("-----类型名称------->"+part.getName());
// System.out.println("-----类型------->"+part.getContentType());
// System.out.println("-----提交的类型名称------->"+part.getSubmittedFileName());
// System.out.println("----流-------->"+part.getInputStream());
// }
sendMailService.sendMail(to, title, content, attachmentName, attachmentFile);
log.info("邮件发送服务,接收到的信息:to:{},title:{},content:{},attachmentName:{}", to, title, content, attachmentName);
return true;
}
二、客户端
1、添加maven引用
版本的使用:
我用springboot1.x(feign)的时候,我用的是3.3.0
springboot2.x(openfeign)的时候,我用的是3.8.0。在github上看了。3.8.0的版本,已经抛弃spring-cloud-starter-feign了。
spring-cloud-starter-feign
is a deprecated dependency and it always uses the OpenFeign
's 9.* versions.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.3.0</version>
</dependency>
3、备注
1、feignclient的代码。这个地方需要注意的是MultipartSupportConfig这个内部类的使用
(1)如果是springboot1.X版本,请查看我的另一边文章。这个地方容易出问题
(2)如果是springboot2.X版本,随便用
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
@ConditionalOnClass(FeignClient.class)
@FeignClient(value = "iov-openservice", configuration = OpenServiceMailFeignService.MultipartSupportConfig.class)
@RequestMapping("/srv010600601/v0.0.1")
public interface OpenServiceMailFeignService {
/**
* 带附件邮件发送
* @param attachmentName
* @param to
* @param title
* @param content
* @param attachmentFile
* @return ResponseMessage
*/
@PostMapping(value = "/sendMail", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
@ResponseBody
boolean sendMail(@RequestParam("to") String to,
@RequestParam("title") String title,
@RequestParam("content") String content,
@RequestParam(value = "attachmentName",required = false) String attachmentName,
@RequestPart(value = "attachmentFile",required = false) MultipartFile attachmentFile);
/**
* feignclient为了支持文件上传,需要配置SpringFormEncoder
* 其中的@Component注解是为了在多个同名feignclient时configuration不产生覆盖
* @param
* @return {}
*/
@Component
class MultipartSupportConfig {
@Bean("multipartFormEncoder")
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {
@Override
public HttpMessageConverters getObject() throws BeansException {
return new HttpMessageConverters(new RestTemplate().getMessageConverters());
}
}));
}
@Bean("multipartLoggerLevel")
public feign.Logger.Level multipartLoggerLevel() {
return feign.Logger.Level.FULL;
}
}
}
2、MultipartSupportConfig类除了可以当做是内部类,还可以单独作为类使用。以下是单独的配置
(1)@Configuration这个注解可以不用的
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.web.client.RestTemplate;
@Configuration
public class MultipartSupportConfig {
@Bean("multipartFormEncoder")
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(new ObjectFactory<HttpMessageConverters>() {
@Override
public HttpMessageConverters getObject() throws BeansException {
return new HttpMessageConverters(new RestTemplate().getMessageConverters());
}
}));
}
@Bean("multipartLoggerLevel")
public feign.Logger.Level multipartLoggerLevel() {
return feign.Logger.Level.FULL;
}
}
3、我的测试代码
用这个测试代码注意的地方,就是生成文件的时候FileItem fileItem = new DiskFileItem("attachmentFile",
这个变量名就是我上面说的服务器端接收的变量名。设置的时候要注意。
import openservice.utils.ResponseMessage;
import openservice.mail.feign.OpenServiceMailFeignService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.file.Files;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("/samplemail")
public class SampleMailController {
@Autowired
private OpenServiceMailFeignService client;
/**
* 带附件邮件发送
// * @param to 收件人
// * @param title 主题
// * @param content 邮件内容
// * @param attachmentName 附件名称
// * @param attachmentFile 附件
* @return {boolean}
*/
@PostMapping("/sendMail")
public void sendMail() throws IOException {
String to = "xxx@xxx.com";
String title = "测试";
String content = "12312313啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊";
String attachmentName = "aslkdfjaslkwenjian.png";
MultipartFile attachmentFile = null;
String filePath = "E:aaaimageannere43e26feaaa603f1498b6ea48066d2d21e293237.jpg";
File file = new File(filePath);
// 需要导入commons-fileupload的包
FileItem fileItem = new DiskFileItem("attachmentFile", Files.probeContentType(file.toPath()),false,file.getName(),(int)file.length(),file.getParentFile());
byte[] buffer = new byte[4096];
int n;
try (InputStream inputStream = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()){
while ( (n = inputStream.read(buffer,0,4096)) != -1){
os.write(buffer,0,n);
}
//也可以用IOUtils.copy(inputStream,os);
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
System.out.println(multipartFile.getName());
attachmentFile = multipartFile;
}catch (IOException e){
e.printStackTrace();
}
client.sendMail(to, title, content, attachmentName, attachmentFile);
}
}