一、网关
网关:就是网络的关口,负责请求的路由,转发,身份校验。
在SpringCloud中网关的实现:
SpringCloud Gateway
由SpringCloud官方出品
基于WebFlux响应式编程
无需调优即可获得优异性能
(1)网关路由
配置路由规则
spring:
cloud:
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb代表负载均衡
predicates: # 路由断言,判断请求是否符合规则,符合则路由到目标
- Path=/items/** # 以请求路径做判断,以/items开头则符合
- id: xx
uri: lb://xx-service
predicates:
- Path=/xx/**
①快速入门:
创建一个新的模块,引入网关的依赖,编写启动类,配置路由规则。
②路由属性
网关路由对应的java类型是RouteDefinition,其中常见的属性有:
id:路由唯一标识
uri:路由目标地址
predicates:路由断言,判断请求是否符合当前路由
filters:路由过滤器,对请求或响应做特殊处理
路由断言:
路由过滤器:
(2)网关登录校验
ctrl+H 查看该类的实现类
网关过滤器有两种,分别是:
GatewayFilter:路由过滤器作用于任意指定的路由,默认不生效,要配置到路由后生效。
GlobalFilter:全局过滤器,作用范围是所有路由,声明后自动生效。
①自定义全局过滤器
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
System.out.println("headers"+headers);
// 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 过滤器执行顺序,值越小,优先级越高
return 0;
}
}
②自定义过滤器GatewayFilter
自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new OrderedGatewayFilter(new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("print any filter running");
return chain.filter(exchange);
}
},1);
}
}
}
③实现登录校验
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final JwtTool jwtTool;
private final AntPathMatcher antPathMatcher=new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取request
ServerHttpRequest request = exchange.getRequest();
// 2.判断是否需要做登录拦截
if (isExclude(request.getPath().toString())){
// 放行
return chain.filter(exchange);
}
// 3.获取token
String token =null;
List<String> headers = request.getHeaders().get("authorization");
if (headers!=null&&!headers.isEmpty()){
token=headers.get(0);
}
// 4.校验并解析token
Long userId=null;
try{
userId = jwtTool.parseToken(token);
}catch(UnauthorizedException e){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// TODO 5.传递用户信息
System.out.println("userId"+userId);
// 6.放行
return chain.filter(exchange); }
private boolean isExclude(String path) {
for (String pathPattern :authProperties.getExcludePaths()){
if (antPathMatcher.match(pathPattern,path)){
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
④网关传递用户信息
一、在网关的登录校验过滤器中,把获取到的用户写入请求头
需求:修改gateway模块中的登录校验拦截器,在校验成功后保存用户到下游请求的请求头中。 提示:要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:
// 5.传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("userinfo", userInfo)).build();
从网关那里获取用户信息,通过过滤器做拦截,获取修改请求头,定义一个通用的拦截器,
二、在hm-common中编写SpringMVC拦截器,获取登录用户
需求:由于每个微服务都可能有获取登录用户的需求,因此我们直接在hm-common模块定义拦截器,这样微服务只需要引入依赖即可生效,无需重复编写。
@Component
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户登陆信息
String userinfo = request.getHeader("userinfo");
// 判断是否获取了用户,如果有,存入ThreadLocal
if (StrUtil.isNotBlank(userinfo)){
UserContext.setUser(Long.valueOf(userinfo));
}
// 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserContext.removeUser();
}
}
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig,\
com.hmall.common.config.JsonConfig
⑤OpenFeign传递用户(微服务之间的相互调用)
微服务项目中的很多业务要多个微服务共同合作完成,而这个过程中也需要传递登录用户信息。
OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
Long userId = UserContext.getUser();
if (userId!=null){
RequestTemplate userinfo = template.header("userinfo", userId.toString());}
}
};
}
(3)配置管理
①配置共享
i.添加一些通用配置到Nacos中,包括:Jdbc、MybatisPlus、日志、Swagger、OpenFeign等配置
ii.拉取共享配置
基于NacosConfig拉取共享配置代替微服务的本地配置
引入依赖
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
新建bootstrap.yaml
spring:
application:
name: cart-service #微服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 1.12.232.19:8848 #nacos地址
config:
file-extension: yaml #文件后缀名
shared-configs:
- data-id: shared-jdbc.yaml
- data-id: shared-log.yaml
- data-id: shared-swagger.yaml
②配置热更新
配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。
前提条件:nacos中要有一个与微服务名有关的配置文件。
微服务中要以特定方式读取需要热更新的配置属性
@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private Integer maxItems;
}
③动态路由
如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。
-
创建ConfigService,目的是连接到Nacos
-
添加配置监听器,编写配置变更的通知处理逻辑
在网关中引入依赖
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
然后在网关gateway
的resources
目录创建bootstrap.yaml
文件,内容如下
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.150.101
config:
file-extension: yaml
shared-configs:
- dataId: shared-log.yaml # 共享日志配置
接着,修改gateway
的resources
目录下的application.yml
,把之前的路由移除
server:
port: 8080
hm:
jwt:
location: classpath:hmall.jks
alias: hmall
password: hmall123
tokenTTL: 30m
auth:
excludePaths:
- /search/**
- /users/login
- /items/**
- /hi
然后,在gateway
中定义配置监听器
接下来,我们直接在Nacos控制台添加路由,路由文件名为gateway-routes.json
,类型为json
[
{
"id": "item",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
}],
"filters": [],
"uri": "lb://item-service"
},
{
"id": "cart",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/carts/**"}
}],
"filters": [],
"uri": "lb://cart-service"
},
{
"id": "user",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
}],
"filters": [],
"uri": "lb://user-service"
},
{
"id": "trade",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/orders/**"}
}],
"filters": [],
"uri": "lb://trade-service"
},
{
"id": "pay",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/pay-orders/**"}
}],
"filters": [],
"uri": "lb://pay-service"
}
]