遇坑说明
曾经也经常使用Feign进行数据传输,更多的是关注服务熔断和降级的处理。最近参与的一个项目中,有一个发送邮件的功能,本来一切都是那么的简单,最开始的时候,内部写一个工具类调用就可以了,已经成功上线运行。过了一段时间, 邮件里面需要发送附件,后面针对文件都需要做安全扫描,需要调用内部公共扫描接口,由于申请权限过程复杂,流程很长,项目运行过程中不能等待。这时就想着调用已有服务的接口,传输邮件信息就可以了,由被调用服务做安全扫描。最开始也觉得没什么难度,调用一下就可以了,找到别人服务的接口人,发了一个调用实例过来,由于别人也比较忙,就没有多问,好吧,直接开始开工把。
Feign发送表单数据
下面的内容,不是真实的项目内容,但是能够表达问题所在。
实体类似这样,其中我不能够理解为什么文件需要使用map接收,没get到接口人的点,不知道大家是否能够理解:
@Data
public class EmailDTO {
/**邮件主题*/
private String title;
/**正文*/
private String content;
/**接受者*/
private String receiver;
/**附件列表*/
private Map<String, MultipartFile> fileList;
}
远程服务接口大概是这样的:
@RestController
@RequestMapping("/email")
public class EmailSendRest {
@PostMapping("/sendMail")
public String sendMail(EmailDTO emailDTO) {
//do something
return "调用成功";
}
}
大家看到这样的服务,看到第一眼应该觉得非常简单吧,不就是发送一个表单数据嘛,直接这样堆代码就可以了,第一次调用如下:
@FeignClient(name = "EmailService", configuration = EmailServiceClient.FeignSupportConfig.class)
public interface EmailServiceClient {
@PostMapping(value = "/email/sendMail", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void sendEmail(EmailDTO emailDTO);
/**
* 支持表单提交
*/
@Configuration
class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
}
最开始的时候,一切都很正常,没有什么异常出现。之后,发送带附件的邮件,想都不想,非常简单啊,直接这样来:
EmailDTO emailDTO = new EmailDTO();
emailDTO.setContent("正文内容");
emailDTO.setTitle("邮件标题");
Map<String,MultipartFile> fileList=new HashMap<>();
fileList.put(multipartFile.getOriginalFilename(),multipartFile);
emailDTO.setFileList(fileList);
this.emailServiceClient.sendEmail(emailDTO);
一切有那么顺利吗?当然没有,接下来你会看到下面的异常:
feign.codec.EncodeException: class java.util.HashMap is not a type supported by this encoder.
蒙圈了,什么情况?不支持?通过debug调试,发现在MultipartFormContentProcessor.process方法中调用了如下方法:
Writer writer = findApplicableWriter(entry.getValue());
这里是用于找到数据对应的Writer,可目前就支持这几种:
addWriter(new ByteArrayWriter());
addWriter(new FormDataWriter());
addWriter(new SingleFileWriter());
addWriter(new ManyFilesWriter());
addWriter(new ParameterWriter());
addWriter(new PojoWriter(writers));
好吧,找到问题所在了,确实不支持HashMap类型,这个时候有点慌了,离发布版本又近了一步了,问过许多同事,他们也没有遇到这种情况,没有使用Feign的表单形式传递Map。抱怨几句对方写接口的负责人,还是看看有没有别的办法吧。
RestTemplate发送表单数据
示例代码:
File file = new File("F:\\软件包\\sublime快捷键.txt");
//设置请求头为表单文件类型数据
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE);
FileSystemResource fileSystemResource = new FileSystemResource(file);
//使用MultiValueMap ,一个key可以对应多个value,在多文件的时候很好使用,这里就使用它 MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("title", "邮件标题");
multiValueMap.add("content", "邮件正文");
multiValueMap.add("fileList[" + file.getName() + "]", fileSystemResource);
//封装请求体
HttpEntity<MultiValueMap<String, Object>> params = new HttpEntity<>(multiValueMap, headers);
//调用
String s = this.restTemplate.postForObject("http://localhost:8080/email/sendMail", params, String.class);
采用RestTemplate解决了上述麻烦的问题,或许我们能够自定义FormEncoder来解决Feign调用问题,如果有Boss使用Feign解决了上述问题,麻烦不吝赐教,谢谢。
总结
记录在真实项目中遇到的一个问题,由于能力有限,无法采用更好的方式解决,希望能够对遇到该问题的伙伴们一个指引。
分享一个小知识点
数组或集合按照指定分隔符拼接字符串:
String[] names ={"张三", "李四", "王五", "赵六"};
String join = StringUtils.join(names, ";");