【JAVA企业级开发】Spring Cloud组件OpenFeign消费者采用POJO对象向对服务提供者传递表单的过程中,总结我们团队遇到很多关于OpenFeign传递参数的问题和坑。

一级目录

二级目录

三级目录

一环境

首先我们团队一如既往保持了使用了稳定的Springcloud和Springboot版本

<dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
</dependency>

2OpenFeign下的服务消费者controller和service

controller

@RequestMapping(value = "loginjwt",method = {RequestMethod.POST, RequestMethod.GET})
    public Object loginjwt(Account account) {
        String account1 = account.getAccount();
        String password = account.getPassword();
        System.out.println("账号密码:"+account1+","+password);
        return feignOpenServiceFace.loginjwt(account);
    }

service

package fengbo.service;

import fengbo.entity.Account;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

/*
 * Created by @author LiuChunhang on 2020/7/27.

*/


/**使用openfeign,定制化服务*/

@FeignClient(value = "accountprovider",fallback = SentinelEcho.class)
@Component
public interface FeignOpenServiceFace {
    @RequestMapping(value = "login")
    public String login();
    @RequestMapping(value = "loginjwt" )
    public String loginjwt(Account account);
}

package fengbo.service;

import fengbo.entity.Account;
import org.springframework.stereotype.Component;

/**
 * Created by @author LiuChunhang on 2020/7/29.
 * 设置统一降级
 */

@Component
public class SentinelEcho  implements  FeignOpenServiceFace{
    @Override
    public String login() {
        System.out.println("fallback");
        return "login";
    }

    @Override
    public String loginjwt(Account account) {
        System.out.println("fallback");
        return "loginjwt";
    }
    }


3服务提供者的controller

@RequestMapping(value = "loginjwt" ,method = {RequestMethod.POST, RequestMethod.GET})
    public String loginjwt(Account account, HttpServletResponse response) {
        String jsonWebToken =null;
        String user = account.getAccount();
        String password = account.getPassword();
        System.out.println("账号密码"+user+","+password);
        boolean validate = providerServiceInterFace.validate(account);
        System.out.println("权限验证:"+validate);
        if (validate){
            if (user.equals("fengbo")){
                response.addCookie( providerServiceInterFace.AddTokenToCookie(account));
                return "redirect:admin.html";
            }else{
                return "redirect:index.html";
            }
        }else {
            return "redirect:login.html";
        }

    }

4前端表单

 <form id="myform" action="loginjwt" method="get">
            <div class="layui-user-icon larry-login">
                <input type="text" placeholder="账号" class="login_txtbx" name="account" id="account"/>
            </div>
            <div class="layui-pwd-icon larry-login">
                <input type="password" placeholder="密码" class="login_txtbx" name="password" id="password"/>
            </div>
            <div class="layui-submit larry-login">
                <input type="submit" type="button" value="立即登陆" class="submit_btn"/>
            </div>
</form>

由上述可知,消费者端由Contoller层接收表单参数,然后再通过Openfeign调用服务提供者并传参,但是通过单独测试消费者端可知,消费者端由Contoller层的POJO对象可以接收到表单参数,但不能传递参数到服务提供者端。
所以问题就出现在 OpenFeign如何传递POJO对象的问题。

二总结

1问题

浏览器提示:

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

2网上查阅的方案

①服务提供者Controller层和服务消费者service接口层,使用 @RequestBody和@postmapping接收实体,并且提交表单使用Ajax提交,提交的数据格式application/json

package fengbo.service;

import fengbo.entity.Account;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

/*
 * Created by @author LiuChunhang on 2020/7/27.

*/


/**使用openfeign,定制化服务*/

@FeignClient(value = "accountprovider",fallback = SentinelEcho.class)
@Component
public interface FeignOpenServiceFace {
    @RequestMapping(value = "login")
    public String login();
    @RequestMapping(value = "loginjwt",method= RequestMethod.POST,consumes = "application/json" )
    public String loginjwt(Account account);
}

$.ajax({
  type : "post",
  url : loginjwt,
  data : JSON.stringify(formData),
  contentType:"application/json",
  dataType : "json",
  success : function(data) {
});

httpclient依赖

<dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>4.5.3</version>
</dependency>
<dependency>
  <groupId>com.netflix.feign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>${feign-httpclient}</version>
</dependency>

feign-httpclient配置

feign.httpclient,enabled = true

②在消费者service层,使用 @SpringQueryMap和@GETmapping接收实体,并且提交表单提交的数据格式application/x-www-form-urlencoded,并且添加HttpClient,Feign-HttpClient依赖

消费者controller

  @RequestMapping(value = "loginjwt",method = {RequestMethod.POST, RequestMethod.GET})
    public Object loginjwt(Account account) {
        String account1 = account.getAccount();
        String password = account.getPassword();
        System.out.println("账号密码:"+account1+","+password);
        return feignOpenServiceFace.loginjwt(account);
    }

消费者 service

package fengbo.service;

import fengbo.entity.Account;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

/*
 * Created by @author LiuChunhang on 2020/7/27.

*/


/**使用openfeign,定制化服务*/

@FeignClient(value = "accountprovider",fallback = SentinelEcho.class)
@Component
public interface FeignOpenServiceFace {
    @RequestMapping(value = "login")
    public String login();
    @RequestMapping(value = "loginjwt" )
    public String loginjwt(@SpringQueryMap Account account);
}

提供者controller

@RequestMapping(value = "loginjwt" ,method = {RequestMethod.POST, RequestMethod.GET})
    public String loginjwt(Account account, HttpServletResponse response) {
        String jsonWebToken =null;
        String user = account.getAccount();
        String password = account.getPassword();
        System.out.println("账号密码"+user+","+password);
        boolean validate = providerServiceInterFace.validate(account);
        System.out.println("权限验证:"+validate);
        if (validate){
            if (user.equals("fengbo")){
                response.addCookie( providerServiceInterFace.AddTokenToCookie(account));
                return "redirect:admin.html";
            }else{
                return "redirect:index.html";
            }
        }else {
            return "redirect:login.html";
        }

    }

前端表单

<form id="myform" action="loginjwt" method="post">
            <div class="layui-user-icon larry-login">
                <input type="text" placeholder="账号" class="login_txtbx" name="account" id="account"/>
            </div>
            <div class="layui-pwd-icon larry-login">
                <input type="password" placeholder="密码" class="login_txtbx" name="password" id="password"/>
            </div>
            <div class="layui-submit larry-login">
                <input type="submit" type="button" value="立即登陆" class="submit_btn"/>
            </div>
</form>

3关于 @RequestBody

  @RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody@RequestParam()可以同时使用,@RequestBody(解析json并根据KEY值绑定JAVAPOJO类)最多只能有一个,而@RequestParam()(根据KEY值绑定单个参数)可以有多个。

使用@RequestBody注解时,是用于接收Content-Type为application/json类型的请求,BODY中的数据类型必须设置成JSON:{“aaa”:“111”,“bbb”:“222”}

不使用@RequestBody注解时,可以接收Content-Type为application/x-www-form-urlencoded类型的请求所提交的数据,数据格式:aaa=111&bbb=222 ,form表单提交以及jQuery的.post()方法所发送的请求就是这种类型。

4关于POST数据格式(enctype=“multipart/form-data”)

①application/x-www-form-urlencoded

这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):

POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

首先,Content-Type 被指定为 application/x-www-form-urlencoded;
其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。
大部分服务端语言都对这种方式有很好的支持。例如 PHP 中, P O S T [ ‘ t i t l e ’ ] 可 以 获 取 到 t i t l e 的 值 , _POST[‘title’] 可以获取到 title 的值, POST[title]title_POST[‘sub’] 可以得到 sub 数组。
很多时候,我们用 Ajax 提交数据时,也是使用这种方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8」。

②multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctyped 等于这个值。
这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。

上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段原生 form 表单也只支持这两种方式。

但是随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。

③application/json

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{“title”:“test”,“sub”:[1,2,3]}

④text/xml

它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。
XML-RPC 协议简单、功能够用,各种语言的实现都有。它的使用也很广泛,如 WordPress 的 XML-RPC Api,搜索引擎的 ping 服务等等。JavaScript 中,也有现成的库支持以这种方式进行数据交互,能很好的支持已有的 XML-RPC 服务。不过,我个人觉得 XML 结构还是过于臃肿,一般场景用 JSON 会更灵活方便。

-----------------------------------华丽的分割线---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

通过上面对POST提交的简单介绍,对Post提交Form表单已经有了认识:

1、Content-Type 被指定为 application/x-www-form-urlencoded;

2、提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码

三feign的坑

1 FeignGet请求自动转化成POST的坑

原因——Caused by: feign.FeignException$MethodNotAllowed: status 405 reading或Required request body is missing:public java.util.Object错误,因为Feign默认使用的连接工具实现类,所以里面发现只要你有body体对象,就会强制的把GET请求转换成POST请求。
解决——更换Apache的HttpClient。

2Feign 传参及传输Date类型参数时差的坑

原因——CTS的误解
Feign客户端在进行通信时,会将Date类型对象转为String类型,如果这个时间是北京时间2019年2月19日20点30分,因为中国的时区叫做CTS,所以转化后的String为“Tue Feb 19 20:30:00 CST 2019”.
服务端将接收的String类型日期转换为Date类型,转换采用的是Date的默认构造器new Date(‘Tue Feb 19 20:30:00 CST 2019’),这里就是错误发生的时刻,因为CTS代表的时区其实有四个(Central Standard Time (USA) UT-6:00、Central Standard Time (Australia) UT+9:30、China Standard Time UT+8:00、Cuba Standard Time UT-4:00),同时表示美国,澳大利亚,中国,古巴四个国家的标准时间。根据JavaDoc,jvm会将CTS理解成了美国中部时区,因此造成了时区错误。
JVM见到CTS默认是美国的时区(UT-6:00),与中国时区(UT+8:00)相差14个小时。

解决——当发送时间类型时,直接用String发送(推荐)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牵牛刘先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值