zoukankan      html  css  js  c++  java
  • SpringCloud Feign Method has too many Body parameters 多实体与文件数组参数传递的方法 问题处理

    前一段时间在使用springcloud 中的feign组件的时候报了一 

    Method has too many Body parameters


    package com.cmbchina.ccd.itpm.consumer.service;
    
    import com.cmbchina.ccd.itpm.article.entity.Article;
    import com.cmbchina.ccd.itpm.common.entity.R;
    import com.cmbchina.ccd.itpm.common.vo.PageVo;
    import feign.Param;
    import feign.RequestLine;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.annotations.ApiParam;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Repository;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RequestPart;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.Valid;
    import java.util.List;
    
    /**
     * @Author zly
     * @Date 2019/10/24 09:35
     */
    @FeignClient(name = "asset-article")
    @Repository
    public interface ArticleService {
        @RequestLine("POST /article/add")
        R add(@RequestBody  Article article, @RequestBody List<String> labelIds,@RequestBody  MultipartFile file);
    
    }
    

      这个FeignClient 使用了三个@RequestBody 其中两个是实体类 和一个文件对象 我想应该在大多数的情况下都会遇到会传一个实体对象和一个文件对象的情况,这个时候启动就会报出 Method has too many Body parameters 问题

    好的下面就要解决这个问题

    第一步

    在消费者自定义个配置类

    package com.cmbchina.ccd.itpm.consumer.configure;
    
    import feign.Contract;
    import feign.codec.Encoder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
    * @Author ZLY
    * @Create 2019-10-29 16:45
    **/
    @Configuration
    public class FeignConfiguration {
    
    // 启用Fegin自定义注解 如@RequestLine @Param
       @Bean
       public Contract feignContract(){
           return new Contract.Default();
       }
    
    
       //feign 实现多pojo传输与MultipartFile上传 编码器,需配合开启feign自带注解使用
       @Bean
       public Encoder feignSpringFormEncoder(){
           return new FeignSpringFormEncoder();
       }
    }

    第二步 在定义个表单编码器

    package com.cmbchina.ccd.itpm.consumer.configure;
    
    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;
    
    /**
     * A custom {@link feign.codec.Encoder} that supports Multipart requests. It uses
     * {@link HttpMessageConverter}s like {@link RestTemplate} does.
     *  feign 实现多pojo传输与MultipartFile上传 编码器,需配合开启feign自带注解使用
     * @author Pierantonio Cangianiello
     */
    public class FeignSpringFormEncoder implements Encoder {
    
        private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();
        public static final Charset UTF_8 = Charset.forName("UTF-8");
    
        public FeignSpringFormEncoder() {
    
        }
    
        /**
         * {@inheritDoc }
         */
        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
            final HttpHeaders multipartHeaders = new HttpHeaders();
            final HttpHeaders jsonHeaders = new HttpHeaders();
            multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
            jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
            if (isFormRequest(bodyType)) {
                encodeMultipartFormRequest((Map<Object, ?>) object, multipartHeaders, template);
            } else {
                encodeRequest(object, jsonHeaders, template);
            }
        }
    
        /**
         * Encodes the request as a multipart form. It can detect a single {@link MultipartFile}, an
         * array of {@link MultipartFile}s, or POJOs (that are converted to JSON).
         *
         * @param formMap
         * @param template
         * @throws EncodeException
         */
        private void encodeMultipartFormRequest(Map<Object, ?> formMap, HttpHeaders multipartHeaders, RequestTemplate template) throws EncodeException {
            if (formMap == null) {
                throw new EncodeException("Cannot encode request with null form.");
            }
            LinkedMultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
            for (Entry<Object, ?> 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);
        }
    
        private boolean isMultipartFile(Object object) {
            return object instanceof MultipartFile;
        }
    
        private boolean isMultipartFileArray(Object o) {
            return o != null && o.getClass().isArray() && MultipartFile.class.isAssignableFrom(o.getClass().getComponentType());
        }
    
        /**
         * Wraps a single {@link MultipartFile} into a {@link HttpEntity} and sets the
         * {@code Content-type} header to {@code application/octet-stream}
         *
         * @param file
         * @return
         */
        private HttpEntity<?> encodeMultipartFile(MultipartFile file) {
            HttpHeaders filePartHeaders = new HttpHeaders();
            filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            try {
                Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
                return new HttpEntity<>(multipartFileResource, filePartHeaders);
            } catch (IOException ex) {
                throw new EncodeException("Cannot encode request.", ex);
            }
        }
    
        /**
         * Fills the request map with {@link HttpEntity}s containing the given {@link MultipartFile}s.
         * Sets the {@code Content-type} header to {@code application/octet-stream} for each file.
         *
         * @param the current request map.
         * @param name the name of the array field in the multipart form.
         * @param files
         */
        private void encodeMultipartFiles(LinkedMultiValueMap<Object, Object> map, String name, List<? extends MultipartFile> files) {
            HttpHeaders filePartHeaders = new HttpHeaders();
            filePartHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            try {
                for (MultipartFile file : files) {
                    Resource multipartFileResource = new MultipartFileResource(file.getOriginalFilename(), file.getSize(), file.getInputStream());
                    map.add(name, new HttpEntity<>(multipartFileResource, filePartHeaders));
                }
            } catch (IOException ex) {
                throw new EncodeException("Cannot encode request.", ex);
            }
        }
    
        /**
         * Wraps an object into a {@link HttpEntity} and sets the {@code Content-type} header to
         * {@code application/json}
         *
         * @param o
         * @return
         */
        private HttpEntity<?> encodeJsonObject(Object o) {
            HttpHeaders jsonPartHeaders = new HttpHeaders();
            jsonPartHeaders.setContentType(MediaType.APPLICATION_JSON);
            return new HttpEntity<>(o, jsonPartHeaders);
        }
    
        /**
         * Calls the conversion chain actually used by
         * {@link org.springframework.web.client.RestTemplate}, filling the body of the request
         * template.
         *
         * @param value
         * @param requestHeaders
         * @param template
         * @throws EncodeException
         */
        private void encodeRequest(Object value, HttpHeaders requestHeaders, RequestTemplate template) throws EncodeException {
            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<Object>) messageConverter).write(
                                value, requestContentType, dummyRequest);
                        break;
                    }
                }
            } catch (IOException ex) {
                throw new EncodeException("Cannot encode request.", ex);
            }
            HttpHeaders headers = dummyRequest.getHeaders();
            if (headers != null) {
                for (Entry<String, List<String>> entry : headers.entrySet()) {
                    template.header(entry.getKey(), entry.getValue());
                }
            }
            /*
            we should use a template output stream... this will cause issues if files are too big, 
            since the whole request will be in memory.
             */
            template.body(outputStream.toByteArray(), UTF_8);
        }
    
        /**
         * Minimal implementation of {@link org.springframework.http.HttpOutputMessage}. It's needed to
         * provide the request body output stream to
         * {@link org.springframework.http.converter.HttpMessageConverter}s
         */
        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;
            }
    
        }
    
        /**
         * Heuristic check for multipart requests.
         *
         * @param type
         * @return
         * @see feign.Types#MAP_STRING_WILDCARD
         */
        static boolean isFormRequest(Type type) {
            return MAP_STRING_WILDCARD.equals(type);
        }
    
        /**
         * Dummy resource class. Wraps file content and its original name.
         */
        static class MultipartFileResource extends InputStreamResource {
    
            private final String filename;
            private final long size;
    
            public MultipartFileResource(String filename, long size, InputStream inputStream) {
                super(inputStream);
                this.size = size;
                this.filename = filename;
            }
    
            @Override
            public String getFilename() {
                return this.filename;
            }
    
            @Override
            public InputStream getInputStream() throws IOException, IllegalStateException {
                return super.getInputStream(); //To change body of generated methods, choose Tools | Templates.
            }
    
            @Override
            public long contentLength() throws IOException {
                return size;
            }
    
        }
    
    }

    然后在feignClient接口中使用@RequestLine标记请求路径,使用@Param注解标记每一个请求参数 

    RequestLine注解的格式是
    @RequestLine(value = “POST 请求路径”)
    请求方式和路径之间须有一个空格。

    修改之后的feignClient接口

    package com.cmbchina.ccd.itpm.consumer.service;
    
    import com.cmbchina.ccd.itpm.article.entity.Article;
    import com.cmbchina.ccd.itpm.common.entity.R;
    import com.cmbchina.ccd.itpm.common.vo.PageVo;
    import feign.Param;
    import feign.RequestLine;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.annotations.ApiParam;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Repository;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RequestPart;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.Valid;
    import java.util.List;
    
    /**
     * @Author zly
     * @Date 2019/10/24 09:35
     */
    @FeignClient(name = "asset-article")
    @Repository
    public interface ArticleService {
        @RequestLine("POST /article/add")
        R add(@Param("article") Article article, @Param("labelIds") List<String> labelIds,@Param("file") MultipartFile file);
    }

    之后再生产者controller 入参方式使用 @RequestPart注解接收每一个参数。对于基础类型参数,你也可以使用RequestParam(好像没有必要,统一使用RequestPart注解不就好了)

    如下:

    import com.cmbchina.ccd.itpm.article.dto.ArticleDto;
    import com.cmbchina.ccd.itpm.article.entity.Article;
    import com.cmbchina.ccd.itpm.article.service.ArticleService;
    import com.cmbchina.ccd.itpm.common.entity.R;
    import com.cmbchina.ccd.itpm.common.entity.Staff;
    import com.cmbchina.ccd.itpm.common.utils.SessionUtil;
    import com.cmbchina.ccd.itpm.common.vo.PageVo;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import io.swagger.annotations.ApiParam;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RequestPart;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;
    import java.util.List;
    
    /**
     * @Author zly
     * @Date 2019/10/22 15:43
     */
    @RestController
    @RequestMapping("/article")
    @Api("文章")
    public class ArticleController {
        @Autowired
        private ArticleService articleService;
    
        @ApiOperation(value = "发布文章", notes = "根据 Article对象创建文章")
        @PostMapping(value = "/add")
        public R add(@RequestPart(value = "article") Article article,
                     @ApiParam(value = "上传文件", required = true) @RequestPart("multipartFile") MultipartFile multipartFile,
                     @RequestPart("labelIds") List<String> labelIds, HttpServletRequest request) {
            return articleService.add(article, labelIds, request, multipartFile);
        }
    }

    原文参考

  • 相关阅读:
    GUI学习笔记之一“Hello world”程序
    GDI和GUI的区别
    Convert.Int32、(int)和int.Parse三者的区别
    华为机试题汇总
    算法导论 第7章 课后习题
    算法导论 第8章 线性时间排序 课后习题
    算法导论 第21章 不相交集合的数据结构
    [转载]NIM(1) 一排石头的游戏
    算法导论 第22章 图论之拓扑排序
    编程珠玑第八章 算法设计艺术
  • 原文地址:https://www.cnblogs.com/blackCatFish/p/12009473.html
Copyright © 2011-2022 走看看