微服务组件----网关

ops/2024/9/23 2:24:01/

       小编目前大一,刚开始着手学习微服务的相关知识,小编会把它们整理成知识点发布出来。我认为同为初学者,我把我对知识点的理解以这种代码加观点的方式分享出来不仅加深了我的理解,或许在某个时候对你也有所帮助,同时也欢迎大家在评论区分享你们的观点。

       知不足而奋进,望远山而前行。  

目录

前言

快速入门

路由属性

网关登录校验


前言

        在我们把一个单体架构拆分成微服务时会遇到,服务端口过多,并且将来有可能发生变化,前端不知道该请求谁,又或者是每个服务都可能需要登录用户信息,各自去做不仅麻烦,还有密钥泄露的风险诸如此类的等等问题。为了解决上述问题,我们就需要了解网关相关的知识。

        网关就是网络的关口,负责请求的路由、转发、身份校验。微服务就相当于小区的住户,网关就是小区的保安,记录着每位业主的住址信息,几楼几号,对于陌生人要进小区会查验身份,身份合格后才会放你进来,并告诉你要找的人的住址。在我们项目中,就是前端直接发送请求到网关,网关身份校验后,再通过路由转发去调用指定的微服务。        

        另外我们通过注册中心来进行服务拉取获取服务列表,实现了网关对微服务状态信息的获取,如果服务挂掉了,我们也就不会路由转发给它。

        在SpringCloud中网关的实现包括两种:Spring Cloud Gateway Netfilx Zuul,具体二者的区别见下图:

快速入门

        接下来我们就来尝试使用一下网关,网关的快速入门主要分为两步:第一步搭建好网关,第二步就是配置路由规则,剩下的事情网关就可以自动去完成了。但是虽说是两步,但是引入依赖,编写启动类这些基础的其实也在里面。其实搭建网关服务,依赖,启动类就是重新创建个模块,还是不难的。重点就是配置路由规则,具体规则如下:

        id这一块我们直接以服务名称做id,方便还不容易出错,uri属性也是比较简单的。第三个predicates属性,就是判断请求是否符合规则,这里我们通常使用模糊匹配,匹配整个controller所有的方法。

        首先创建一个新的模块,我就把它叫做hm-gateway吧,接着第二步引入相关依赖。

    <dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><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></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

        第三步编写网关启动类

java">@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}

        第四步就是最重要的配置路由规则了,按照上面定义的规则,我们不难写出下面的代码。

java">spring:cloud:gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path=/items/**,/search/**- id: user-serviceuri: lb://user-servicepredicates:- Path=/users/**,/addresses/**

        以上步骤完成后,我们就可以去浏览器测试一下了。

        通过网关端口8080访问,也是请求到了数据,证明没有什么问题。

路由属性

        以上的路由属性配置已经能满足我们基本的需求了,但是难免有时候会出现我们有别的需求,所以我们有必要了解一下路由属性。

        我们知道在yaml文件中所有配置属性最终都会有一个对应的java类来读取,网关路由对应的java类型是RouteDefinition,其中常见的属性有:id、uri、predicates、filters。

        在Spring内部提供了12种基本的路由断言RoutePredicateFactory实现:

        网关中提供了33种路由过滤器,每种过滤器都有独特的作用。

        接下来我们就来尝试使用一下过滤器,首先我们要到yml文件种去配置过滤器,我这里就选择添加请求头。

java">    gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path=/items/**,/search/**filters:- AddRequestHeader=truth, anyone long-press like button will be rich

        设置好后,我们就去找个方法拿到请求头并打印到控制台。

java">@ApiOperation("分页查询商品")@GetMapping("/page")public PageDTO<ItemDTO> queryItemByPage(PageQuery query, @RequestHeader(value = "truth", required = false) String truth) {System.out.println("truth:" + truth);// 1.分页查询Page<Item> result = itemService.page(query.toMpPage("update_time", false));// 2.封装并返回return PageDTO.of(result, ItemDTO.class);}

        接下来我们就看看访问商品服务的分页查询功能时,能不能获取到请求头并打印到控制台。 

        很显然我们是成功了的。 另外过滤器也可以不用配置到routes中,也可以配置与routes同级,这样就叫做默认过滤器。这样就实现了给所有路由配置了过滤器,方便省事,同时也是生效的

 网关登录校验

     思路分析

        用户微服务实现了登录授权后会携带JWT令牌,但是其它服务,比如订单,购物车等也需要用户的信息的话,不能每个服务都做JWT校验,所以JWT的校验应该由网关来实现,同时该检验一定要放在路由转发之前。

        网关请求处理的流程图见下:

        既然NettyFilter是负责转发请求的,所以我们只需要自定义一个过滤器保证执行顺序在Netty路由过滤器之前并且在它的pre阶段中实现JWT的校验可以实现登录校验了。 另外网关在校验之后还需要将用户信息传递给微服务,这一块我们就可以将用户信息保存到请求头,这样微服务也可以从请求头中取出用户信息。还有一点需要注意,有时候微服务之间也会互相调用,那如何在微服务之间传递用户信息呢,微服务之间是通过OpenFeign去实现的,所以这一块和网关保存用户信息到请求头还是有所不同。

自定义过滤器

        网关过滤器有两种,分别是:GatewayFilterGlobalFilter。二者的区别如下:

        这两种过滤器的过滤方法都是filter,无论是返回值类型,还是参数都是一样的。

 

    自定义GlobalFilter

        其实这个也比较简单,我们只要让我们自定以的Filter继承GlobalFilter,接着重写filter方法就行了,另外我们还要保证我们的过滤器是在NettyFilter之前执行,所以我们还要继承Order接口,来设置一些优先级,NettyFilter是最低优先级,int的最大值,我们只要比这个数小,那么优先级就比它大。

java">@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// TODO 模拟实现登录逻辑ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();System.out.println("headers:" + headers);// 放行return chain.filter(exchange);}//  设置我们过滤器的优先级要比NettyFilter高//  NettyFilter是int的最大值,我们只要比这个数小,优先级就比它大@Overridepublic int getOrder() {return 0;}
}
    自定义GatewayFilter

        GatewayFilter相比于GlobalFilter更加灵活,可以任意指定路由,但是如果我们想自定义GatewayFilter就比较麻烦了。将来我们使用GlobalFilter更多,这一块仅作了解就好了。

        自定以GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterfactory

         下面就是使用工厂模式定义一个无参的GatewayFilter,我们将它的优先级指定为1,这样它就比我们刚刚定义的GlobalFilter级别高了。

java">@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.out.println("print any filter running");return chain.filter(exchange);}}, 1);}
}

        这一种过滤器仅作了解就好了,大部分情况我们使用的都是GlobalFilter

实现登录校验

        接着我们就来手写一个GlobalFilter来实现登录校验,这一块其实和我们之前写的JWT校验都差不多,只不过我们要设置一下排除一些路径,因为有些路径的访问时不需要做登录拦截的。

java">@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties;private final JwtTool jwtTool;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1. 获取request对象ServerHttpRequest request = exchange.getRequest();// 2. 判断是否需要做登录拦截if (isExclude(request.getPath().toString())) {// 放行return chain.filter(exchange);}// 3. 获取tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (headers != null && !headers.isEmpty()) {token = headers.get(0);}// 4. 校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 拦截,设置响应状态码为401ServerHttpResponse 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;}@Overridepublic int getOrder() {return 0;}
}

        这一块的代码也没有什么好说的,都是和之前一样的逻辑。但是我们还留下了一个问题,那就是我们还要将用户信息传递下去,这个应该怎么实现呢? 

网关传递用户

        对于微服务之间的服务调用或者网关到服务的路由发送,我们都可以通过SpringMVC提供的拦截器,获取用户信息并保存到ThreadLocal中。

        这里其实分为两步,第一步我们要在网关的登录校验过滤器中,把获取到的用户写入请求头,这一块其实已经提供好了现成的API,我们直接调用就好了。

java">        // 5.传递用户信息String userInfo = userId.toString();ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info", userInfo)).build();

        第二步就是在common模块中编写SpringMVC拦截器,获取登录用户。具体为什么定义在common模块时因为定义在这里就只需要写一遍了。

        拦截器定义这一块没什么好说的,都是和之前差不多的

java">public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取登录用户信息String userInfo = request.getHeader("user-info");// 2. 判断是否获取了用户,如果有,存入ThreadLocalif (StrUtil.isNotBlank(userInfo)) {UserContext.setUser(Long.valueOf(userInfo));}// 3. 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理用户UserContext.removeUser();}
}

        接着把拦截器加载到SpringMvc的配置类当中让它生效。

java">@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}

        好了,以上我们就学会了从网关到微服务的用户信息传递。

OpenFeign传递用户

        接着我们来看看微服务之间的调用如何进行用户信息的传递。例如当我们去支付订单后,支付服务需要去调用购物车服务删除购物车的数据。

        现在我们在服务之间的调用时也应该让它去带上请求头,但是这个服务之间的请求时OpenFeign帮我们发送的,所以应该怎么告诉OpenFeign发送千秋之前带上请求头呢?

        OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求。

         这一块我们直接使用匿名内部类就好了。

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

        到这里我们就实现好了微服务之间调用获取用户信息的需求。

        可能到这里已经那么多过滤器拦截器什么的,已经绕晕了,对照上面这张图,相信你可以理清这之间的关系。

        以上就是网关的相关知识。

        带着决心起床,带着满意入睡。


http://www.ppmy.cn/ops/105034.html

相关文章

稀疏镜像在OpenHarmony上的应用

一、稀疏镜像升级背景 常用系统镜像格式为原始镜像&#xff0c;即RAW格式。镜像体积比较大&#xff0c;在烧录固件或者升级固件时比较耗时&#xff0c;而且在移动设备升级过程时比较耗费流量。为此&#xff0c;将原始镜像用稀疏描述&#xff0c;可以大大地缩减镜像体积&#x…

macos 使用port安装mariadb/mysql数据库服务器

在mac下安装mariadb/mysql数据库服务器的方式 有多种,可以直接下载官方安装包安装,或者使用port, brew 这类macos下的专业包管理工具安装, 推荐使用port 包管理工具来安装mysql数据库服务器. 使用方法如下: 先使用port search xxx查找要安装的软件都有哪些安装包 命令: por…

儿童护眼大路灯怎么选择?5款儿童护眼落地灯分享

儿童护眼大路灯怎么选择&#xff1f;网上有很多与护眼大路灯相关的热门话题&#xff0c;比如“护眼大路灯怎么选”、“儿童护眼大路灯伤眼”等。由此可见&#xff0c;护眼大路灯虽然是当前公认最高效的照明工具&#xff0c;但却一直备受争议。究其根源&#xff0c;是市面上充斥…

30个值得收藏的国家超清三维地图

我们在《值得收藏的28个省会城市三维地形图》一文中&#xff0c;分享了精美的全国28个省会/首府城市的三维地形渲染图。 现在我们又精选30个国家的三维地形图分享给大家&#xff0c;你可以在文末查看该数据的领取方法。 30个国家三维地形图 本次一共整理30个国家的三维地图分…

【数据结构】顺序表和链表——链表(包含大量经典链表算法题)

文章目录 1. 单链表1.1 概念与结构1.1.1 结点1.1.2 链表的性质1.1.3 链表的打印 1.2 实现单链表1.3 链表的分类1.4 单链表算法题1.4.1 移除链表元素1.4.2 反转链表1.4.3 链表的中间结点1.4.4 合并两个有序链表1.4.5 链表分割1.4.6 链表的回文结构1.4.7 相交链表1.4.8 环形链表1…

算法的学习笔记—把数组排成最小的数(牛客JZ45)

&#x1f600;前言 在编程面试中&#xff0c;经常会遇到需要将问题转化为排序问题的题目。这些问题看似复杂&#xff0c;但只要抓住核心思路&#xff0c;便能迅速解决。今天我们就来看一道这样的题目&#xff1a;如何将一个非负整数数组拼接成最小的数字。 &#x1f3e0;个人主…

RTC(实时时钟)/BKP(备份寄存器

1 unix时间戳 2 时间戳转换函数 3 BKP&#xff08;备份寄存器&#xff09; 1 TAMPER引脚侵入事件 2 RTC校准时间 3 RST闹钟脉冲和秒脉冲 可以输出出来为其他信号提供 4 校准时钟&#xff0c;寄存器加输出RTC校准时钟 5 总结&#xff1a;3个功能只能同时使用一个 4 BKP基本…

将python项目打包成一个可执行文件(包含需要的资源文件)

目标 项目源码是采用Python编写&#xff0c;代码中需要读取部分资源文件。现在需要将项目打包成一个exe文件&#xff0c;没有其他任何多余文件&#xff0c;仅1个exe文件。 打包 安装pyinstaller 在自己项目的虚拟环境中&#xff0c;安装pyinstaller。注意一定要是虚拟环境&…