一级目录
二级目录
三级目录
一环境
首先我们团队一如既往保持了使用了稳定的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发送(推荐)