千家信息网

Feign怎么解决服务之间传递文件、传递list,map、对象等情况

发表于:2025-01-26 作者:千家信息网编辑
千家信息网最后更新 2025年01月26日,这篇文章主要介绍"Feign怎么解决服务之间传递文件、传递list,map、对象等情况",在日常操作中,相信很多人在Feign怎么解决服务之间传递文件、传递list,map、对象等情况问题上存在疑惑,
千家信息网最后更新 2025年01月26日Feign怎么解决服务之间传递文件、传递list,map、对象等情况

这篇文章主要介绍"Feign怎么解决服务之间传递文件、传递list,map、对象等情况",在日常操作中,相信很多人在Feign怎么解决服务之间传递文件、传递list,map、对象等情况问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Feign怎么解决服务之间传递文件、传递list,map、对象等情况"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

先说下背景,前段时间有一个需求,需要将服务A生成的一个文件传递到服务B,交予服务B去做处理,最开始的时候使用的spring-cloud-starter-openfeign,发现这一块是不支持的,然后引入了io.github.openfeign.form ,解决,但过一段时间又有新需求,在传递文件的同时,还传递对象和一些其他参数,这个时候发现feign就有些不行了。这个时候引入了feign-httpclient,暂时解决。用了一段时间,发现大文件的时候又出现了数据丢失等等问题。还有其他各种坑就不说了,都是用升级版本,引入其他的jar来解决的,但这个大文件数据丢失的问题一直不行。

之前使用的maven重要坐标

    io.github.openfeign.form    feign-form    ${feign-form-version}    io.github.openfeign.form    feign-form-spring    ${feign-form-version}    io.github.openfeign    feign-httpclient    ${feign-httpclient}

决定解决这个,首先说下使用的版本,这点很重要、很重要、很重要!

使用的版本:

springboot 2.0.3.RELEASE
springcloud Finchley.RELEASE

替换为下面的maven。上面的那些maven地址没必要了。

            2.0.8                8.17.0                        org.springframework.cloud            spring-cloud-starter-openfeign                                    com.netflix.feign            feign-core            ${netflix.feign-version}                            com.netflix.feign            feign-jackson            ${netflix.feign-version}                            com.netflix.feign            feign-slf4j            ${netflix.feign-version}                                    org.springframework            spring-mock            ${spring-mock-version}        

注意,我已经测试过 openfeign、feign-httpclient,如果使用这些版本的并不能解决文件传递的问题,虽然可以接收文件,但是文件是残缺的,一定要替换成上面的maven才行。

核心思路就是:对编码器重写,Encoder的原理就是将每个参数json序列化,设置requestHeader为Multipart/form-data,采用表单请求去请求生成者提供的接口。这个方法能够同时发送多个实体文件,以及MultipartFile[]的数组.

首先对编码器重写,

import feign.RequestTemplate;import feign.codec.EncodeException;import feign.codec.Encoder;import org.springframework.core.io.InputStreamResource;import org.springframework.core.io.Resource;import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.util.LinkedMultiValueMap;import org.springframework.web.client.RestTemplate;import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.lang.reflect.Type;import java.nio.charset.Charset;import java.util.Arrays;import java.util.List;import java.util.Map;import java.util.Map.Entry;/** * @author :LX * 创建时间: 2020/10/14. 15:06 * 地点:广州 * 目的: 自定义表单编码器。feign 实现多pojo传输与MultipartFile上传 编码器,需配合开启feign自带注解使用 *      用于支持多对象和文件的上传 * *      Encoder的原理就是将每个参数json序列化,设置requestHeader为Multipart/form-data,采用表单请求去请求生成者提供的接口。 *      这个方法能够同时发送多个实体文件,以及MultipartFile[]的数组. * *      参考资料: *              https://github.com/pcan/feign-client-test * 备注说明: */public class FeignSpringFormEncoder implements Encoder{    private final List> converters = new RestTemplate().getMessageConverters();    public static final Charset UTF_8 = Charset.forName("UTF-8");    public FeignSpringFormEncoder() {}    /**     * 实现一个 HttpOutputMessage     */    private class HttpOutputMessageImpl implements HttpOutputMessage{        /**         * 输出流,请求体         */        private final OutputStream body;        /**         * 请求头         */        private final HttpHeaders headers;        public HttpOutputMessageImpl(OutputStream body, HttpHeaders headers) {            this.body = body;            this.headers = headers;        }        @Override        public OutputStream getBody() throws IOException {            return body;        }        @Override        public HttpHeaders getHeaders() {            return headers;        }    }    /**     * 判断是否表单请求     * @param type     * @return     */    static boolean isFormRequest(Type type){        return MAP_STRING_WILDCARD.equals(type);    }    /**     * 内部静态类,保存 MultipartFile 数据     */    static class MultipartFileResource extends InputStreamResource {        /**         * 文件名         */        private final String filename;        /**         * 文件大小         */        private final long size;        /**         * 构造方法         * @param inputStream         * @param filename         * @param size         */        public MultipartFileResource(InputStream inputStream, String filename, long size) {            super(inputStream);            this.filename = filename;            this.size = size;        }        @Override        public String getFilename() {            return this.filename;        }        @Override        public InputStream getInputStream() throws IOException, IllegalStateException {            return super.getInputStream();        }        @Override        public long contentLength() throws IOException {            return size;        }    }    /**     * 重写编码器     * @param object     * @param bodyType     * @param template     * @throws EncodeException     */    @Override    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {        if (isFormRequest(bodyType)){            final HttpHeaders multipartHeaders = new HttpHeaders();            multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);            encodeMultipartFormRequest((Map) object, multipartHeaders, template);        } else {            final HttpHeaders jsonHeaders = new HttpHeaders();            jsonHeaders.setContentType(MediaType.APPLICATION_JSON);            encodeRequest(object, jsonHeaders, template);        }    }    /**     * 对有文件、表单的进行编码     * @param formMap     * @param multipartHeaders     * @param template     */    private void encodeMultipartFormRequest(Map formMap, HttpHeaders multipartHeaders, RequestTemplate template){        if (formMap == null){            throw new EncodeException("无法对格式为null的请求进行编码。");        }        LinkedMultiValueMap map = new LinkedMultiValueMap<>();        //对每个参数进行检查校验        for (Entry entry : formMap.entrySet()){            Object value = entry.getValue();            //不同的数据类型进行不同的编码逻辑处理            if (isMultipartFile(value)){                //单个文件                map.add(entry.getKey(), encodeMultipartFile((MultipartFile)value));            } else if (isMultipartFileArray(value)){                //多个文件                encodeMultipartFiles(map, (String) entry.getKey(), Arrays.asList((MultipartFile[]) value));            } else {                //普通请求数据                map.add(entry.getKey(), encodeJsonObject(value));            }        }        encodeRequest(map, multipartHeaders, template);    }    /**     * 对请求进行编码     * @param value     * @param requestHeaders     * @param template     */    private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template){        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);        try {            Class requestType = value.getClass();            MediaType requestContentType = requestHeaders.getContentType();            for (HttpMessageConverter messageConverter : converters){                if (messageConverter.canWrite(requestType, requestContentType)){                    ((HttpMessageConverter) messageConverter).write(value, requestContentType, dummyRequest);                    break;                }            }        } catch (IOException e) {            throw new EncodeException("无法对请求进行编码:", e);        }        HttpHeaders headers = dummyRequest.getHeaders();        if (headers != null){            for (Entry> entry : headers.entrySet()){                template.header(entry.getKey(), entry.getValue());            }        }        /*        请使用模板输出流。。。如果文件太大,这将导致问题,因为整个请求都将在内存中。         */        template.body(outputStream.toByteArray(), UTF_8);    }    /**     * 编码为json对象     * @param obj     * @return     */    private HttpEntity encodeJsonObject(Object obj){        HttpHeaders jsonPartHeaders = new HttpHeaders();        jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);        return new HttpEntity<>(obj, jsonPartHeaders);    }    /**     * 编码MultipartFile文件,将其转换为HttpEntity,同时设置 Content-type 为 application/octet-stream     * @param map 当前请求 map.     * @param name 数组字段的名称     * @param fileList 要处理的文件     */    private void encodeMultipartFiles(LinkedMultiValueMap map, String name, List fileList){        HttpHeaders filePartHeaders = new HttpHeaders();        //设置 Content-type        filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);        try {            for (MultipartFile file : fileList){                Resource multipartFileResource = new MultipartFileResource(file.getInputStream(), file.getOriginalFilename(), file.getSize());                map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders));            }        } catch (IOException e) {            throw new EncodeException("无法对请求进行编码:", e);        }    }    /**     * 编码MultipartFile文件,将其转换为HttpEntity,同时设置 Content-type 为 application/octet-stream     * @param file 要编码的文件     * @return     */    private HttpEntity encodeMultipartFile(MultipartFile file){        HttpHeaders filePartHeaders = new HttpHeaders();        //设置 Content-type        filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);        try {            Resource multipartFileResource = new MultipartFileResource(file.getInputStream(), file.getOriginalFilename(), file.getSize());            return new HttpEntity<>(multipartFileResource, filePartHeaders);        } catch (IOException e) {            throw new EncodeException("无法对请求进行编码:", e);        }    }    /**     * 判断是否多个 MultipartFile     * @param object     * @return     */    private boolean isMultipartFileArray(Object object){        return object != null && object.getClass().isArray() && MultipartFile.class.isAssignableFrom(object.getClass().getComponentType());    }    /**     * 判断是否MultipartFile文件     * @param object 要判断的对象     * @return     */    private boolean isMultipartFile(Object object){        return object instanceof MultipartFile;    }}

将该编码器注册为bean

import feign.Contract;import feign.codec.Encoder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * 上传文件所用配置 * @author admin */@Configurationpublic class MultipartSupportConfig {    /**     * 启用feigin自定义注解支持,如 @RequestLine 和 @Param     * @return     */    @Bean    public Contract feignContract(){        return new Contract.Default();    }    /**     * feign 实现多pojo传输与MultipartFile上传 编码器,需配合开启feign自带注解使用     * @return     */    @Bean    public Encoder feignSpringFormEncoder(){        //注入自定义编码器        return new FeignSpringFormEncoder();    }}

这个时候就基本告一段落,

所有的请求按照这个标准来写

/**     * 示例代码:请求方式和路径之间须有一个空格。 表单提交的话请求方式只能是post     * 支持如下的所有请求方式。     * 请求参数需要 @Param 修饰     * 在接收端,采用@RequestPart注解接收每一个参数。所有接收都用 @RequestPart(value = "advertiser", required = false)     * @return     */    @RequestLine(value = "POST /data/test01")    ResultJson test01(@Param(value = "name") String name,                      @Param(value = "nametwo") String nametwo,                      @Param(value = "file") MultipartFile file,                      @Param(value = "advertiserMap") Map advertiserMap,                      @Param(value = "materials") List materials,                      @Param(value = "user") User user,                      @Param(value = "files") MultipartFile[] files);

要使用 Feign 自带的注解,@RequesLine 和 @Param 来做请求参数的注入

我测试的时候,使用 如下这些参数,都可以完成传递、

 /**     * 示例代码:feign请求测试     * @return     */    public String test01(){        try {            String name = "中文";            String nametwo = "two";            MultipartFile file = fileToMultipartFile(new File("E:\\临时\\1.xlsx"));            MultipartFile file2 = fileToMultipartFile(new File("E:\\临时\\2.xlsx"));            Map advertiserMap = new HashMap<>();            User user = new User();            user.setXm("张三");            User user1 = new User();            user1.setXm("张四");            advertiserMap.put("zw", user);            advertiserMap.put("中", user1);            List list = new ArrayList<>();            list.add(user);            list.add(user1);            MultipartFile[] files = new MultipartFile[2];            files[0] = file;            files[1] = file2;            ResultJson resultJson = resourceAdminFeignImp.test01(name, nametwo, file, advertiserMap, list, user, files);            if (ResultEnum.SUCCESS.getStatus().equals(resultJson.getStatus())){                log.info("测试结果:{}", resultJson.getData());                return (String) resultJson.getData();            } else {                log.error("测试失败,失败原因,{}", resultJson.getMsg());                return null;            }        } catch (Exception e) {            e.printStackTrace();            log.error("服务不可用或服务调用失败,上传数据失败");            return null;        }    }

接收端同样要注意,要使用@RequestPart 来接收参数。

/**     * 演示用demo,用来测试这些类型是不是都可以接收     * @param name 普通参数     * @param file 普通文件     * @param advertiserMap 普通map对象     * @param materials 普通list对象     * @param user 对象     * @param files 多文件     * @return     */    @ResponseBody    @PostMapping("/test01")    public ResultJson test01(@RequestPart(value = "name", required = false) String name,                             @RequestPart(value = "nametwo", required = false) String nametwo,                             @RequestPart(value = "file", required = false) MultipartFile file,                             @RequestPart(value = "advertiserMap", required = false) Map advertiserMap,                             @RequestPart(value = "materials", required = false) List materials,                             @RequestPart(value = "user", required = false) User user,                             @RequestPart(value = "files", required = false) MultipartFile[] files){        log.info("name:{}", name);        log.info("nametwo:{}", nametwo);        log.info("文件名:{},文件大小:{},文件名:{}", file.getOriginalFilename(), file.getSize(), file.getName());        log.info("map对象大小:{}", advertiserMap.size());        log.info("list对象大小:{}", materials.size());        log.info("用户:{}", user.toString());        log.info("文件名:{},文件大小:{},文件名:{}", files[0].getOriginalFilename(), files[0].getSize(), files[0].getName());        return new ResultJson("查询成功", null);    }

注意,基础的数据类型,String 之类的可以不用写注解也可以接收。

到此,关于"Feign怎么解决服务之间传递文件、传递list,map、对象等情况"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0