好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

feign调用中文参数被encode编译的问题

Feign调用中文参数被encode编译

原因

在实现一个feign调用时使用了Post请求,并且拼接url参数,name传值为中文时被encode转译,且最终接取数据之前未被decode转译回,问题探索:

feign:

?

1

2

3

4

5

@FeignClient (name = "service-test" )

public interface TestServiceApi {

    @PostMapping ( "/test/abc" )

    public String getTestNo( @RequestParam ( "code" ) String code, @RequestParam ( "name" ) String name);

}

controller:

?

1

2

3

4

5

6

7

8

9

@RequestMapping ( "/test" )

public interface TestController {

     @Autowired

     private TestService testService;

    @PostMapping ( "/abc" )

    public String getTestNo( @RequestParam ( "code" ) String code, @RequestParam ( "name" ) String name) {

        return testService.query(code, name); 

    }    

}

在controller中接到的中文参数被URIEncode转译了

测试 被转译成:%E6%B5%8B%E8%AF%95

找到feign的入口类ReflectiveFeign中拼装RequestTemplate的方法:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

    @Override

    public RequestTemplate create(Object[] argv) {

      RequestTemplate mutable = new RequestTemplate(metadata.template());

      if (metadata.urlIndex() != null ) {

        int urlIndex = metadata.urlIndex();

        checkArgument(argv[urlIndex] != null , "URI parameter %s was null" , urlIndex);

        mutable.insert( 0 , String.valueOf(argv[urlIndex]));

      }

      Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();

      for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {

        int i = entry.getKey();

        Object value = argv[entry.getKey()];

        if (value != null ) { // Null values are skipped.

          if (indexToExpander.containsKey(i)) {

            value = expandElements(indexToExpander.get(i), value);

          }

          for (String name : entry.getValue()) {

            varBuilder.put(name, value);

          }

        }

      }

      // 组装template的方法

      RequestTemplate template = resolve(argv, mutable, varBuilder);

      if (metadata.queryMapIndex() != null ) {

        // add query map parameters after initial resolve so that they take

        // precedence over any predefined values

        template = addQueryMapQueryParameters(argv, template);

      }

      if (metadata.headerMapIndex() != null ) {

        template = addHeaderMapHeaders(argv, template);

      }

      return template;

    }

在RequestTemplate类中如果是拼接在url后的param那么会被使用encodeValueIfNotEncoded都encode转译,但是不会走decode的方法 

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

/**

    * Resolves any template parameters in the requests path, query, or headers against the supplied

    * unencoded arguments. <br> <br><br><b>relationship to JAXRS 2.0</b><br> <br> This call is

    * similar to {@code javax.ws.rs.client.WebTarget.resolveTemplates(templateValues, true)} , except

    * that the template values apply to any part of the request, not just the URL

    */

   RequestTemplate resolve(Map<String, ?> unencoded, Map<String, Boolean> alreadyEncoded) {

    replaceQueryValues(unencoded, alreadyEncoded);

    Map<String, String> encoded = new LinkedHashMap<String, String>();

    for (Entry<String, ?> entry : unencoded.entrySet()) {

      final String key = entry.getKey();

      final Object objectValue = entry.getValue();

      String encodedValue = encodeValueIfNotEncoded(key, objectValue, alreadyEncoded);

      encoded.put(key, encodedValue);

    }

    String resolvedUrl = expand(url.toString(), encoded).replace( "+" , "%20" );

    if (decodeSlash) {

      resolvedUrl = resolvedUrl.replace( "%2F" , "/" );

    }

    url = new StringBuilder(resolvedUrl);

    Map<String, Collection<String>> resolvedHeaders = new LinkedHashMap<String, Collection<String>>();

    for (String field : headers.keySet()) {

      Collection<String> resolvedValues = new ArrayList<String>();

      for (String value : valuesOrEmpty(headers, field)) {

        String resolved = expand(value, unencoded);

        resolvedValues.add(resolved);

      }

      resolvedHeaders.put(field, resolvedValues);

    }

    headers.clear();

    headers.putAll(resolvedHeaders);

    if (bodyTemplate != null ) {

      body(urlDecode(expand(bodyTemplate, encoded)));

    }

    return this ;

   }

如果传入的值在requestBody中,则不会被encode转译

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

    @Override

     public void encode(Object requestBody, Type bodyType, RequestTemplate request)

             throws EncodeException {

         // template.body(conversionService.convert(object, String.class));

         if (requestBody != null ) {

             Class<?> requestType = requestBody.getClass();

             Collection<String> contentTypes = request.headers().get( "Content-Type" );

             MediaType requestContentType = null ;

             if (contentTypes != null && !contentTypes.isEmpty()) {

                 String type = contentTypes.iterator().next();

                 requestContentType = MediaType.valueOf(type);

             }

             for (HttpMessageConverter<?> messageConverter : this .messageConverters

                     .getObject().getConverters()) {

                 if (messageConverter.canWrite(requestType, requestContentType)) {

                     if (log.isDebugEnabled()) {

                         if (requestContentType != null ) {

                             log.debug( "Writing [" + requestBody + "] as \""

                                     + requestContentType + "\" using ["

                                     + messageConverter + "]" );

                         }

                         else {

                             log.debug( "Writing [" + requestBody + "] using ["

                                     + messageConverter + "]" );

                         }

                     }

                     FeignOutputMessage outputMessage = new FeignOutputMessage(request);

                     try {

                         @SuppressWarnings ( "unchecked" )

                         HttpMessageConverter<Object> copy = (HttpMessageConverter<Object>) messageConverter;

                         copy.write(requestBody, requestContentType, outputMessage);

                     }

                     catch (IOException ex) {

                         throw new EncodeException( "Error converting request body" , ex);

                     }

                     // clear headers

                     request.headers( null );

                     // converters can modify headers, so update the request

                     // with the modified headers

                     request.headers(getHeaders(outputMessage.getHeaders()));

                     // do not use charset for binary data

                     if (messageConverter instanceof ByteArrayHttpMessageConverter) {

                         request.body(outputMessage.getOutputStream().toByteArray(), null );

                     } else {

                         request.body(outputMessage.getOutputStream().toByteArray(), Charset.forName( "UTF-8" ));

                     }

                     return ;

                 }

             }

             String message = "Could not write request: no suitable HttpMessageConverter "

                     + "found for request type [" + requestType.getName() + "]" ;

             if (requestContentType != null ) {

                 message += " and content type [" + requestContentType + "]" ;

             }

             throw new EncodeException(message);

         }

     }

综合上述的调试,如果在Post中拼接参数那么会被encode转译,且不会被decode转译,如果使用body传参,那么不会出现转译问题,如果必须使用拼接传参,那么可以使用方法

1. @RequestLine的注解自定义参数的格式,具体参考该注解的使用方式。

2.在Feign的RequestInterceptor将传递的值decode的扩展方法。

记录今天遇到的feign多参数问题

1.Post方式

错误写法示例如下:

?

1

public int save( @RequestBody final User u, @RequestBody final School s);

错误原因:

fegin中可以有多个@RequestParam,但只能有不超过一个@RequestBody,@RequestBody用来修饰对象,但是既有@RequestBody也有@RequestParam,

那么参数就要放在请求的Url中,@RequestBody修饰的就要放在提交对象中。

注意!!! 用来处理@RequestBody Content-Type 为 application/json,application/xml编码的内容

正确写法示例如下:

?

1

public int save( @RequestBody final Person p, @RequestParam ( "userId" ) String userId, @RequestParam ( "userTel" ) String userTel);

2.Get方式

错误写法示例如下:

?

1

2

@RequestMapping (value= "/test" , method=RequestMethod.GET)  

Model test( final String name,   final int age); 

错误原因:

异常原因:当使用Feign时,如果发送的是get请求,那么需要在请求参数前加上@RequestParam注解修饰,Controller里面可以不加该注解修饰,@RequestParam可以修饰多个,@RequestParam是用来修饰参数,不能用来修饰整个对象。

注意:@RequestParam Content-Type 为 application/x-www-form-urlencoded 而这种是默认的

正确写法示例如下:

?

1

2

3

@GetMapping ( "/getSchoolDetail" )

    public ResultMap getSchoolDetail( @RequestParam ( "kSchoolId" ) LongkSchoolId,

      @RequestParam ( "kSchoolYearId" ) Long kSchoolYearId);

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/baidu_34932610/article/details/107092448

查看更多关于feign调用中文参数被encode编译的问题的详细内容...

  阅读:22次