【OpenFeign】【使用问题】OpenFeign 里如何调用 form-data 接口或者 MultipartFile 文件类型参数接口
1 前言
今儿有个需求涉及到文件上传的东西,关键是 OpenFeign 去调用,当然最后底牌我也可以创建普通的 HTTP 请求或者 RestTemplate 自己请求是不是也行,但是本人这个倔驴型性格,偶尔也会跟自己犟犟,就是要用 OpenFeign 把它搞出来。
首先我有两个这样的接口:
// 客户导入接口 @PostMapping(value = "/importCustomer") public ResultVo<ImportRes> importCustomer(@RequestBody MultipartFile file) { ImportRes importRes = customerExcelService.importExcel(file); return ResultVo.ok(importRes); }
// 商品导入接口 @PostMapping(value = "/itemImport") public ResultVo<ImportRes> importExcel(@ApiParam(name = "file", value = "file", required = true) @RequestBody(value = "file") MultipartFile file, @RequestParam(value = "ownOrgSign") String ownOrgSign, @RequestParam(value = "importName",required = false) String importName) { // 业务逻辑 return ResultVo.ok(importRes); }
我们来看看用 OpenFeign 如何调用。
2 实现步骤
2.1 引入 SpringFormEncoder
这是我们的关键类,首先双击 shift 搜搜自己的项目里边,有没有这个类 SpringFormEncoder,没有的话去添加一下依赖,引入进来哈。
2.2 新增配置类
import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; import org.springframework.beans.factory.ObjectFactory; 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; /** * @author kuku */ @Configuration public class FeignFormConfig { @Bean public Encoder feignFormEncoder() { return new SpringFormEncoder(); } /** * 这里必须要设置 SpringEncoder 为默认的 * 即 @Primary * 因为没有上边的 SpringFormEncoder 默认都是使用 SpringEncoder * 当你只引入上边的时候,我发现我别的 feign 调用就会出现错误, * 所以两者都引入,SpringEncoder 作为主的 * @param messageConverters * @return */ @Bean @Primary public Encoder springEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { return new SpringEncoder(messageConverters); } }
2.3 @FeignClient 中设置配置类
将上边的 FeignFormConfig 配置,设置进 @FeignClient:
/** * @author kuku */ @FeignClient(name = "${feign.provider.channel-manage:channel-manage}" , path = "/channel-manage" , configuration = FeignFormConfig.class , contextId = "channelManagerFeignService") public interface OaChannelFeignService { @PostMapping(value= "/import/importCustomer", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResultVo<ImportRes> importCustomer(MultipartFile file); }
/** * @author kuku */ @FeignClient(name = "${feign.provider.channel-manage:items-manage}" , path = "/items" , configuration = FeignFormConfig.class , contextId = "itemsManagerFeignService") public interface OaItemFeignService { @PostMapping(value= "/import/itemImport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ResultVo<ImportRes> importItem(@RequestParam("ownOrgSign") String ownOrgSign, @RequestParam("importName") String importName, MultipartFile file); }
我的 Adapter,这个因项目而异,我们的 feign 调用前边都会加一层 Adapter:
@Component public class OaAdapter { @Autowired private OaChannelFeignService oaChannelFeignService; @Autowired private OaItemFeignService oaItemFeignService; public ImportRes importCustomer(MultipartFile file) { ResultVo<ImportRes> resultVo = oaChannelFeignService.importCustomer(file); AppResultDtoUtil.parse(resultVo); return resultVo.getData(); }public ImportRes importItem(String ownOrgSign, String importName, MultipartFile file) { ResultVo<ImportRes> resultVo = oaItemFeignService.importItem(ownOrgSign, importName, file); AppResultDtoUtil.parse(resultVo); return resultVo.getData(); } }
2.4 调用
@Slf4j @Component public class OaDownloadImportDownStream implements DownStream, IOaReceiveSync<YTOaSyncReqDto, ImportRes> { @Autowired private OaAdapter oaAdapter; @Override public ImportRes execute(OaSyncReqDto reqDto) { String importType = reqDto.getImportType(); String fileUrl = reqDto.getFileUrl(); ConditionUtil.mustNotBlank(fileUrl, "fileUrl文件路径不能为空"); ConditionUtil.mustNotBlank(importType, "importType导入类型不能为空"); // 下载附件 File file = null; ImportRes res = null; try { file = File.createTempFile("random", ".xlsx"); HttpUtil.downloadFile(fileUrl, file); DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); String contentType = new MimetypesFileTypeMap().getContentType(file); // 这里切记要写死 file FileItem fileItem = diskFileItemFactory.createItem("file", contentType, false, file.getName()); try ( InputStream inputStream = new ByteArrayInputStream(FileCopyUtils.copyToByteArray(file)); OutputStream outputStream = fileItem.getOutputStream() ) { FileCopyUtils.copy(inputStream, outputStream); } catch (Exception e) { throw e; } // 包装成 MultipartFile MultipartFile multipartFile = new CommonsMultipartFile(fileItem); // 根据类型调用导入 switch (importType) { case "item" : { res = oaAdapter.importItem("123", "save", multipartFile); }; break; case "customer" : { res = oaAdapter.importCustomer(multipartFile); }; break; default: { throw new AppException("未知importType=" + importType + "当前导入类型未接入"); } } // todo分析下导入结果 System.out.println(res); } catch (IOException e) { log.error("", e); throw new Exception(e.getMessage()); } finally { if (Objects.nonNull(file)) { try { file.delete(); } catch (Exception e) { log.error("删除临时文件失败", e); } } } return res; } @Override public SyncBusinessTypeEnums getBusinessType() { return SyncBusinessTypeEnums.OA_DOWNLOAD_IMPORT; } @Override public String getBusinessCode(YTOaSyncReqDto data) { return data.getFlowNo(); } }
3 小结
好了,到这里,调用就结束了,但是期间还是有很多的疑问,下节我们从源码剖析下原理,