引言:
springcloud alibaba 系列目录
一、搭建springcloud工程,请前往:保姆级教程构建springcloud工程(图文结合)
二、集成服务注册配置管理中心nacos,请前往:springcloud集成服务注册配置管理中心nacos
三、集成http客户端工具openfeign,请前往:springcloud集成http客户端工具openfeign
四、集成统一网关springcloud gateway,请前往:springcloud集成微服务api网关gateway(详解)
集成统一网关gateway
1.为什么需要网关和其作用?
- 路由转发: 网关可以作为所有客户端请求的入口,根据请求的url路径将请求路由到相应的微服务实例。通过网关,可以将客户端请求分发到不同的微服务中,实现了微服务之间的解耦。
- 负载均衡: 网关可以对请求进行负载均衡,将请求分发到多个相同服务的实例中。这样可以提高系统的性能和可用性,并且有效地利用了系统资源。
- 安全认证: 网关可以作为安全认证的入口,对请求进行身份验证、授权和安全检查。通过网关,可以集中管理系统的安全策略,保护微服务免受未经授权的访问。
- 流量控制: 网关可以实现流量控制进行限流,限制对微服务的访问速率,防止突发流量对系统造成影响。可以基于请求频率、并发连接数等指标对流量进行控制和限制。
- 监控和日志: 网关可以记录所有的请求和响应信息,并且可以进行统一的监控和日志记录。这样可以方便开发人员进行故障排查、性能优化和系统分析。
2.技术实现
在springcloud中网关的实现有两种:
- gateway 实现
- zuul 实现
zuul是基于servlet的实现,属于阻塞式编程。
springcloud gateway则是基于spring5中提供的webflux,属于响应式编程的实现,具备更好的性能。
3. 搭建网关服务
-
创建新的 cloud-gateway module,引入springcloud gateway的依赖及nacos的服务注册与发现依赖
<dependencies> <!-- 网关依赖 --> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-gateway</artifactid> </dependency> <!-- 服务注册与发现依赖 --> <dependency> <groupid>com.alibaba.cloud</groupid> <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid> </dependency> </dependencies>
-
在gateway 的 resources 下新建 bootstrap.yml,配置路由和nacos 服务注册与发现中心
这里如果创建的是bootstrap.yml的话,一定要引入 bootstrap的依赖,不然是不会进行识别和加载的,也可以不引入 bootstrap依赖,直接创建application.yml也是可以的
<!--bootstrap 启动器加载上下文--> <dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-bootstrap</artifactid> </dependency>
网关路由配置的内容包括:
- 路由id:唯一的路由标识
- uri:目标路由地址,支持http和lb两种
- predicates:路由断言,判断请求是否符合规则,符合路由规则在进行转发到目标路由地址
- filters:路由过滤器,处理请求或者响应
# 服务端口 server: port: 10717 spring: application: # 服务名称 name: gateway profiles: active: dev main: # description: # spring mvc found on classpath, which is incompatible with spring cloud gateway. # action: # please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency. #上述错误表明 springboot 与 springcloud gateway 不兼容, 设置属性为reactive 告诉spring boot使用响应式web应用程序类型,与spring cloud gateway兼容。 web-application-type: reactive # cloud: nacos: # nacos服务地址 server-addr: localhost:8848 # nacos的服务注册与发现 discovery: # nacos服务地址 # server-addr: localhost:8848 # 配置集群名称,也就是机房位置,例如:hz 杭州 cluster-name: hz # nacos可视化命名空间id namespace: cdd2a801-3280-4476-8362-d46318aac1a3 gateway: routes: # 网关路由配置 - id: orderservice # 路由id,自定义填写,唯一即可 # uri 有两种配置方法,一种 http ,一种 lb:服务名称 # uri: http://127.0.0.1:18088/ # 路由目标地址 uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称 predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件 - path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件 - id: paymentservice uri: lb://paymentservice predicates: - path=/paymentservice/**
-
新建gatewayapplication
import org.springframework.boot.springapplication; import org.springframework.boot.autoconfigure.springbootapplication; /** * @author waves * @date 2024/6/4 15:53 */ @springbootapplication public class gatewayapplication { public static void main(string[] args) { springapplication.run(gatewayapplication.class,args); system.out.println("gateway网关服务启动成功 ヾ( ̄ー ̄)x(^▽^)ゞ"); } }
启动网关服务,查看是否正常启动。
4.路由断言工厂
路由断言工厂 route predicate factory
- 在配置文件中写的断言规则只是字符串,这些字符串会被predicate factory读取并处理,转变为路由判断条件
- 在springcloud gateway 断言工厂有十几个,spring 提供了十一种基本的predicate工厂
- 例如 - path=/paymentservice/** 是按照路径匹配,这个规则是lorg.springframework.cloud.gateway.handler.predicate.pathroutepredicatefactory类来处理的
5.spring 提供了十一种基本的predicate工厂
名称 | 说明 | 示例 |
---|---|---|
after | 是某个时间点后的请求 | - after=2024-01-20t19:41:47.789-07:00[america/denver] |
before | 是某个时间点之前的请求 | - before=2024-04-13t19:14:47.433+08:00[asia/shanghai] |
between | 是某两个时间点之前的请求 | - between=2023-01-20t17:42:47.789-07:00[america/denver], 2024-10-21t17:42:47.789-07:00[america/denver] |
cookie | 请求必须包含某些cookie | - cookie=chocolate, ch.p |
header | 请求必须包含某些header | - header=x-request-id, \d+ |
host | 请求必须是访问某个host(域名) | - host=.somehost.org,.anotherhost.org |
method | 请求方式必须是指定方式 | - method=get,post |
path | 请求路径必须符合指定规则 | - path=/red/{segment},/blue/** |
query | 请求参数必须包含指定参数 | - query=name, jack或者- query=name |
remoteaddr | 请求者的ip必须是指定范围 | - remoteaddr=192.168.1.1/24 |
weight | 权重处理 |
6.gatewayfilter 路由过滤器
gatewayfilter是springcloud gateway 网关中提供的一种过滤器,对进入网关的请求和服务响应进行处理。
- 过滤器的作用是什么?
- 对服务的路由请求或响应进行处理,例如添加请求头,添加前缀路径等
- 配置在某个服务路由下的过滤器只对当前路由的请求生效
- default-filters 默认过滤器的作用?
- 对配置在网关的所有路由都生效的过滤器
1.spring 提供了31种不同的路由过滤器工厂。
如下:
名称 | 说明 |
---|---|
addrequestheadergatewayfilterfactory | 用于添加请求头。 |
addresponseheadergatewayfilterfactory | 用于添加响应头。 |
addrequestparametergatewayfilterfactory | 用于添加请求参数。 |
addrequestparametergatewayfilterfactory | 用于添加响应参数。 |
addresponseheadergatewayfilterfactory | 用于添加请求参数。 |
addrequestparametergatewayfilterfactory | 用于添加响应参数。 |
prefixpathgatewayfilterfactory | 用于添加前缀路径。 |
preservehostheadergatewayfilterfactory | 用于保留主机头。 |
removenonproxyheadersgatewayfilterfactory | 用于删除非代理头。 |
removerequestheadergatewayfilterfactory | 用于删除请求头。 |
removeresponseheadergatewayfilterfactory | 用于删除响应头。 |
requestratelimitergatewayfilterfactory | 用于请求速率限制。 |
retrygatewayfilterfactory | 用于请求重试。 |
secureheadersgatewayfilterfactory | 用于添加安全头。 |
setpathgatewayfilterfactory | 用于设置路径。 |
setpathgatewayfilterfactory | 用于设置路径。 |
setrequestheadergatewayfilterfactory | 用于设置请求头。 |
setresponseheadergatewayfilterfactory | 用于设置响应头。 |
setstatusgatewayfilterfactory | 用于设置状态码。 |
redirecttogatewayfilterfactory | 用于重定向。 |
rewritepathgatewayfilterfactory | 用于重写路径。 |
rewritelocationresponseheadergatewayfilterfactory | 用于重写响应头中的位置。 |
savesessiongatewayfilterfactory | 用于保存会话。 |
maprequestheadergatewayfilterfactory | 用于映射请求头。 |
mapresponseheadergatewayfilterfactory | 用于映射响应头。 |
maprequestparametergatewayfilterfactory | 用于映射请求参数。 |
mapresponseheadergatewayfilterfactory | 用于映射响应参数。 |
stripprefixgatewayfilterfactory | 用于删除前缀。 |
addresponseheadergatewayfilterfactory | 用于删除前缀。 |
addrequestparametergatewayfilterfactory | 用于删除前缀。 |
addrequestparametergatewayfilterfactory | 用于删除前缀。 |
2.测试addrequestheader
以addrequestheadergatewayfilterfactory 为例:给进入orderservice 的请求添加一个请求头 authorization = token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa
在gateway的 bootstrap.yml 文件中,在orderservice 的配置下添加路由过滤器
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: orderservice # 路由id,自定义填写,唯一即可
# uri 有两种配置方法,一种 http ,一种 lb:服务名称
# uri: http://127.0.0.1:18088/ # 路由目标地址
uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称
predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件
- path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件
# 路由过滤器
filters:
# 对进入 orderservice 的请求添加 请求头
- addrequestheader=authorization,token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa
在代码中测试是否能获取到请求头
/**
* 测试获取gateway中添加的 authorization
* @param request
* @return
*/
@getmapping("/gettoken")
public responseresult gettoken(httpservletrequest request){
return responseresult.success("操作成功",request.getheader("authorization"));
}
返回结果,成功获取到token
http://localhost:10717/orderservice/system/order/gettoken
{
"code": 100,
"data": "token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa",
"message": "操作成功"
}
3.默认过滤器
如果要对所有的路由都添加过滤器,可以把过滤器工厂配置在default下。如下配置:
spring:
application:
# 服务名称
name: gateway
profiles:
active: dev
cloud:
gateway:
routes: # 网关路由配置
- id: orderservice # 路由id,自定义填写,唯一即可
# uri 有两种配置方法,一种 http ,一种 lb:服务名称
# uri: http://127.0.0.1:18088/ # 路由目标地址
uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称
predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件
- path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件
# 路由过滤器
filters:
# 对进入 orderservice 的请求添加 请求头
- addrequestheader=authorization,token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa
- id: paymentservice
uri: lb://paymentservice
predicates:
- path=/paymentservice/**
# 默认过滤器,会对所有的路由请求都生效
default-filters:
- addrequestheader=authorization,token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa
代码测试,paymentservice服务未单独配置添加请求头,进行测试
/**
* 测试获取gateway中添加的 authorization
* @param request
* @return
*/
@getmapping("/gettoken")
public responseresult gettoken(httpservletrequest request){
return responseresult.success("操作成功",request.getheader("authorization"));
}
返回结果,成功获取到token
http://localhost:10717/paymentservice/payment/gettoken
{
"code": 100,
"data": "token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa",
"message": "操作成功"
}
4.全局过滤器
全局过滤器 globalfilter 的作用 与 gateway filter 的作用一样。处理一切进入网关的请求和服务的响应。
两个的区别在于gatewayfilter是通过配置定义,处理逻辑是不可变的。而globalfilter的逻辑需要开发人员自己写代码进行实现。方式是实现globalfilter接口
public interface globalfilter {
/**
* process the web request and (optionally) delegate to the next {@code webfilter}
* through the given {@link gatewayfilterchain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code mono<void>} to indicate when request processing is complete
*
* 处理web请求并(可选地)通过给定的gatewayfilterchain委托给下一个webfilter。
* params: exchange-请求上下文-提供了一种委托给下一个过滤器的方法
* 返回:mono<void>指示请求处理何时完成
*
*/
mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain);
}
自定义过滤器实现代码:
import org.springframework.cloud.gateway.filter.gatewayfilterchain;
import org.springframework.cloud.gateway.filter.globalfilter;
import org.springframework.core.ordered;
import org.springframework.http.httpstatus;
import org.springframework.http.server.reactive.serverhttprequest;
import org.springframework.stereotype.component;
import org.springframework.util.multivaluemap;
import org.springframework.web.server.serverwebexchange;
import reactor.core.publisher.mono;
/**
* 模拟登陆
* //@order(-1) // 优先级 配置方式有两种,一种直接加 @order(-1) 注解,另一种实现order接口
* @author waves
* @date 2024/6/5 14:36
*/
@component
public class authorizefilter implements globalfilter, ordered {
private static final string static_token = "token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa";
@override
public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
//1.获取请求中的参数
serverhttprequest request = exchange.getrequest();
multivaluemap<string,string> params = request.getqueryparams();
//2.获取请求中的authorization 参数值
string authtoken = params.getfirst("authorization");
//3.判断token是否合法
if (static_token.equals(authtoken)){
//4.合法,进行放行
return chain.filter(exchange);
}
//5.不合法,直接拦截,并返回状态码
exchange.getresponse().setstatuscode(httpstatus.unauthorized);
//拦截请求
return exchange.getresponse().setcomplete();
}
@override
public int getorder() {
return -1;
}
}
测试:
错误请求:
http://localhost:10717/paymentservice/payment/gettoken?authorization=1234
返回:
this page isn’t workingif the problem continues, contact the site owner.
http error 401
正确请求:
http://localhost:10717/paymentservice/payment/gettoken?authorization=token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa
返回:
{"code":100,"data":"token-bearer-5s3zuruotkn95zkg1qq7eo7ahs7wa","message":"操作成功"}
5.过滤器的执行顺序
当请求进入到网关会经过三类过滤器:针对配置的路由过滤器(filters:)、默认过滤器(default-filters:)、全局过滤器(globalfilter)
请求进入网关路由后,会将当前路由过滤器和默认过滤器,globalfilter合并到一个过滤器链(集合)中,进行排序后依次执行
当请求进入时
请求 --> 路由 --> 默认过滤器 --> 服务路由过滤器 --> 全局过滤器 --> 微服务
响应 --> 全局过滤器 --> 服务路由过滤器 --> 默认过滤器 --> 路由
1.自定义globalfilter 通过添加@order注解指定order值,或者实现 ordered接口 返回order的值。
2.每一个过滤器都必须指定一个int类型的order值,值越小,优先级就越高,执行顺序就越靠前。
3.路由过滤器和默认过滤器的order由spring指定,默认是按照声明顺序从1递增。
4.当过滤器的order值都一样的时候,会按照 默认过滤器 > 路由过滤器 > 全局过滤器 的顺序执行。
具体可以参考如下几个类的源码进行查看:
org.springframework.cloud.gateway.route.routedefinitionroutelocator#getfilters()方法是先加载defaultfilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.filteringwebhandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
7.跨域问题处理
什么是跨域:
当一个网页的源(origin)与所请求资源的源不一致时产生的问题。
跨域包括:
- 域名不同: 例如 www.baidu.com 和 fanyi.baidu.com
- 域名相同但端口不同:localhost:8088 和 localhost:18088
原因:
跨域时浏览器会执行同源策略(same-origin policy),限制页面中的javascript代码只能与其同源的资源进行交互,否则的话请求就会被拦截
解决方案:
- 使用cors(跨域资源共享):服务端设置合适的 cors 头部,允许特定的源访问资源。通过设置
access-control-allow-origin
头部,服务器可以允许指定源的请求访问资源。 - 代理:在同源的服务器上设置代理,将跨域请求转发到目标服务器,然后将响应返回给客户端。这种方法可以绕过同源策略限制,但需要在服务器端进行额外的配置。
- 反向代理:在服务器端配置反向代理,将客户端请求发送到目标服务器,并将响应返回给客户端。这种方法可以隐藏目标服务器的真实地址,同时解决跨域问题。
- jsonp(json with padding):jsonp 是通过动态添加
<script>
标签实现跨域请求的一种方法。由于<script>
标签的跨域特性,可以通过在请求 url 中添加回调函数名来获取数据,并在响应中返回 json 数据。 - iframe:通过在页面中嵌入 iframe,将目标页面作为 iframe 的 src,通过 window.postmessage() 方法进行通信,可以实现跨域通信。
- websocket:websocket 提供了一种在不同源之间进行双向通信的方式,可以用于跨域通信。
这里网关使用cors解决 跨域问题,并且在gateway中只需要简单配置即可实现:
spring:
cloud:
gateway:
# 全局跨域处理
globalcors:
# 解决options(预检请求)请求被拦截问题,默认是拦截
add-to-simple-url-handler-mapping: true
cors-configurations:
'[/**]':
allowedorigins: # 允许哪些网站的跨域请求
- "http://localhost:10717"
- "http://localhost:8088"
- "https://localhost:8099"
allowedmethods: #允许跨越的ajax请求
- "get"
- "post"
- "delete"
- "put"
- "options"
allowedheaders: "*" # 允许在请求头中携带头信息
allowcredentials: true # 是否允许携带cookie
maxage: 360000 # 跨域检测的有效时间
发表评论