千家信息网

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

发表于:2025-11-19 作者:千家信息网编辑
千家信息网最后更新 2025年11月19日,这篇文章主要介绍"Feign怎么解决服务之间传递文件、传递list,map、对象等情况",在日常操作中,相信很多人在Feign怎么解决服务之间传递文件、传递list,map、对象等情况问题上存在疑惑,
千家信息网最后更新 2025年11月19日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、对象等情况"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

文件 编码 对象 参数 服务 编码器 数据 时候 注解 表单 测试 之间 普通 同时 大小 文件名 问题 情况 重要 多个 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 战网安全令服务器正在维护 浪潮服务器配置ip命令 建模和软件开发哪个难 深圳市网络安全工程师郑宗义 云服务器哪家性价比高 淮安营销软件开发哪家好 汽车电子电器软件开发工程师 徐汇区信息软件开发收费套餐 如何加强网络安全法制建设 讲网络安全常识 暨南大学网络安全学院甘文生 佳为收银系统的数据库 寿光软件开发自学步骤 计算机网络技术课题报告 瑞昌im即时通讯软件开发 嵌入式软件开发与设计实验 怎么在腾讯云服务器添加d盘 海底服务器上市公司 网络安全博士就业方向 网上购物软件开发流程 如何调查一个软件开发公司的技术 如何进入微信小程序数据库 什么工作负责网络安全和管理 华为网络安全法 什么是数据库处理技术 快吧游戏盒服务器怎么连接 用服务器玩游戏怎么样 数据库查询总分大于500语句 浙江潮流软件开发价格检测中心 网警开展国家网络安全宣传
0