【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  小结

好了,到这里,调用就结束了,但是期间还是有很多的疑问,下节我们从源码剖析下原理,

posted @ 2024-01-15 21:27  酷酷-  阅读(852)  评论(0编辑  收藏  举报