微服务之网关

embedded/2024/9/25 5:52:57/

1.网关需要的依赖

        <!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos discovery--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>

2.路由的yaml配置文件举例

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.*.*:8848gateway:routes:- id: item # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
2-1路由属性
  • id:路由唯一标识
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理
2-1-1 路由断言

Spring提供了12种基本的RoutePredicateFactory实现:

2-1-2 路由过滤器

3.网关登录校验

        以前单体架构,我们只需要在项目中定义一个拦截器,拦截所有请求,对用户身份进行验证。而现在的微服务模式下,这样显然行不通。而网关作为一个微服务群的入口,就担当了这一责任。网关的作用是做路由转发,那我们的登录校验操作是不是得在做路由转发之前来做呢?没错,我们应该了解一下网关路由转发的流程。

        如图,首先请求会经过路由映射器,通过路由断言找到匹配的路由,存入上下文,并把请求交给请求处理器,这个处理器会加载我们配置的多个过滤器,形成过滤器链,依次执行。过滤器分为两个阶段,一个是pre阶段,一个是post阶段,前者是之前后者是之后。如图,请求如果被过滤了,那么后续过滤器不在处理,最后一个过滤器是Netty路由过滤器,它的作用就是把请求转发到微服务中。由此,如果要做登录校验,我们就要自己定义一个过滤器,在Netty之前。Netty路由过滤器是自动就有的。把用户信息放在请求的请求头中。

3-1 自定义过滤器
  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效。要配置路由后才生效。就是刚才33种过滤器。
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
java">public interface GlobalFilter {Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
  • ServerWebExchange exchange :是请求上下文,包含整个过滤器链内共享数据,例如request,response等
  • GatewayFilterChain chain:过滤器链。当前过滤器执行完后,要调用过滤器链中下一个过滤器

我们自定义过滤器,首先要比NettyRoutingFilter的优先级要高

可以看到NettyRoutingFilter实现了GlobalFilter与Ordered接口,Ordered接口就是指定排序的

        可以看到Ordered接口的getOrder方法返回的int类型的最大值21亿多,于是我们自己定义的过滤器应该实现GlobalFilter与Ordered接口,getOrder方法返回值应该小于这个int的最大值。如下,举个例子。

简单示例1:
java">@Component
public class MyFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 模拟登录校验逻辑ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();System.out.println("headers = " + headers);//放行return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;}
}
具体示例2:
java">@RequiredArgsConstructor //通过构造方法变成bean对象
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool; //自定义JWT工具类,含有构建和解析jwt的方法private final AuthProperties authProperties;    //属性类,加载了配置文件中的exclude路径集合private final AntPathMatcher antPathMatcher = new AntPathMatcher(); //特殊的模式匹配,主要匹配路径是否符合标准@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取requestServerHttpRequest request = exchange.getRequest();//2.判断是否需要做拦截操作if (isExclude(request.getPath().toString())) {return chain.filter(exchange);  //如果是排除的路径,那就放行}//3.拿到请求头,取出token,健壮性判断List<String> tokens = request.getHeaders().get(HttpHeaders.AUTHORIZATION);String token = null;if (tokens != null && !tokens.isEmpty()) {token = tokens.get(0);}Long userId = null;//4.解析tokentry {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED); //如果解析失败,给响应设置状态码为401,即为授权失败return response.setComplete();  //后续过滤器不会再走,直接结束,把响应给前端}//5.拿到用户信息String userInfo = userId.toString();ServerWebExchange exc = exchange.mutate()   //mutate就是对下文请求做修改.request(builder -> builder.header("user-info", userInfo)).build();//6. 放行return chain.filter(exc);}private boolean isExclude(String path) {for (String excludePath : authProperties.getExcludePaths()) {// 如果传过来的path满足模式匹配,说明这个路径是排除的路径,直接放行就可if (antPathMatcher.match(excludePath, path)) return true;}return false;}@Overridepublic int getOrder() {return 0;}
}
3-2 拦截器来存放用户信息

        在做日常开发中,我们的微服务几乎一定会用到用户id来查询某些信息,之前我们在网关中,把用户id(userId)放进了请求头,我们可以使用SpringMVC的拦截器来把userId存入ThreadLocal中,但是每一个微服务都要用到这个userId,总不能在每一个微服务中都写一个mvc的拦截器吧。于是我们可以把这个拦截器写入一个公共的项目中,也就是每一个微服务都引入了这个项目的坐标。一般这种项目名称叫common-service。但是一般情况,来做网关的这个微服务也会引入这个公共项目,但是网关它和mvc是两个完全不在一个层面的东西,如果也引入,那一定会报错。所以,在拦截器中,就需要有条件的做拦截动作,而mvc它一定有一个公共的类的使用,那就是DispatcherServlet.class

java">// 这是自定义拦截器
public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 这个拦截器不做拦截,只做存取用户信息的工具,一律放行// 从请求头中取出用户idString userInfo = request.getHeader("user-info");// 健壮性判断if (StrUtil.isNotBlank(userInfo)) {// 存入 ThreadLocalUserContext.setUser(Long.valueOf(userInfo));}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理用户UserContext.removeUser();}
}
java">//这是mvc的配置
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}

而且还需要在静态资源下的META-INF下的spring.factories,放入MvcConfig的包路径,以成为ioc容器内的bean

java">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MvcConfig
3-3 openfeign的传递用户信息

        现在我们解决了网关到微服务之间的用户信息传递,但是微服务彼此之间的用户信息传递又该怎么办呢?拦截器是在mvc中进行的,如果我们从一个服务请求另一个服务,那么这个请求就是服务自己发的,不是前端发的,这个请求是一个新的请求,请求头中是没有用户信息(user-info)的,拦截器是在controller层前做拦截把用户信息放入ThreadLocal的,所以我们的这个请求就需要把本次从前端来的请求头中的用户信息再放入这个服务发起的请求中。

openfeign提供了一个接口,如下

java">public interface RequestInterceptor {void apply(RequestTemplate var1);
}

我们需要实现这个接口,来做把用户信息放入请求头中这个操作,这里我采用匿名内部类的方式

java">@Beanpublic RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate request) {Long userId = UserContext.getUser();if(userId != null){request.header("user-info", userId.toString());}}};}

注意!!!如果要让这个类生效,一定要把它加在openfeign的启动类上@EnableFeignClients

java">@EnableFeignClients(basePackages = "***.***.api.client", defaultConfiguration = DefaultFeignConfig.class)

这里用到了很多拦截器,如图


http://www.ppmy.cn/embedded/116473.html

相关文章

WordPress 要求插件开发人员进行双因素身份验证

全球超过40%的网站由 WordPress 提供支持&#xff0c;其庞大的插件和主题生态系统在全球范围内提供了灵活性和定制性。然而&#xff0c;这种受欢迎程度也使其成为网络攻击的主要目标。 WordPress 将为所有插件和主题开发人员引入强制性双因素身份验证 (2FA)&#xff0c;以应对…

如何修改音频的音量增益

一、前言 在开发音频相关的功能&#xff08;比如说语音通话、播放音乐&#xff09;时&#xff0c;经常会遇到音量太小的问题&#xff0c;这时候就需要我们对原始数据进行处理。本文将介绍如何通过修改原始音频数据来增加增益&#xff0c;本文包含如下内容&#xff1a; 1.音频数…

python全栈学习记录(十八)re、os和sys、subprocess

re、os和sys、subprocess 文章目录 re、os和sys、subprocess一、re1.正则字符2.正则表达式的使用3.group的使用4.贪婪匹配与惰性匹配5.其他注意事项 二、os和sys1.os2.sys 三、subprocess四、打印进度条 一、re python中的re模块用来使用正则表达式&#xff0c;正则就是用一系…

C++什么时候生成默认的拷贝构造函数

1. 默认拷贝构造存在的问题 因为默认使用的是位拷贝&#xff0c;那么如果存在指针一类对象&#xff0c;那么不同对象会持有相同对象资源。 2. 什么时候触发拷贝构造函数 &#xff08;1&#xff09;用一个对象&#xff0c;当另一个对象的构造参数。 A b; A a(b); &#xff…

哪个快?用300万个图斑测试ArcGIS Pro的成对叠加与经典叠加

​​​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 在使用ArcGIS Pro的过程中&#xff0c;很多朋友发现&#xff0c;Pro有个成对叠加工具集。很多…

光子架与电子架 -- 主从子架

主从子架模式可以实现物理上的多个子架在网管上作为一个网元统一管理&#xff0c;这样可以节省IP资源与管理开销&#xff0c;方便维护。 主从子架级联模式 在主从子架模式下&#xff0c;仅主子架可以和网管相连。和网管相连的主子架所在网元为网关网元。主从子架支持树型级联和…

一文入门生成式AI(理解ChatGPT的原理)

一、什么是生成式AI&#xff1f; 以ChatGPT为代表的生成式AI&#xff0c;是对已有的数据和知识进行向量化的归纳&#xff0c;总结出数据的联合概率。从而在生成内容时&#xff0c;根据用户需求&#xff0c;结合关联字词的概率&#xff0c;生成新的内容。 可以这么联想&#x…