微服务——网关、网关登录校验、OpenFeign传递共享信息、Nacos共享配置以及热更新、动态路由

news2025/3/12 13:33:55

之前学习了Nacos,用于发现并注册、管理项目里所有的微服务,而OpenFeign简化微服务之间的通信,而为了使得前端可以使用微服务项目里的每一个微服务的接口,就应该将所有微服务的接口管理起来方便前端调用,所以有了网关。

前端调用后端微服务项目的接口时,不需要指定每一个接口具体的地址,只需要将请求发送到后端的网关即可。

网关介绍

网关是网络的关口,负责请求的路由、转发、身份校验 。

网关模块的配置

1、新建一个maven空模块,配置一下依赖

<dependencies>
......<!-- 其它依赖 -->

<!--网关-->  
<dependency>  
    <groupId>org.springframework.cloud</groupId>  
    <artifactId>spring-cloud-starter-gateway</artifactId>  
</dependency> 
 
<!--nocos 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>  

2、创建com.<XXX项目名称>.gateway包,报下名新建配置类

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

3、在静态资源目录下新建application.yaml文件,配置网关相关属性

server:
  port: 8080  # 网关服务的端口号,指定网关运行在 8080 端口
spring:
  application:
    name: gateway  # 应用名称,注册到 Nacos 的服务名称
  cloud:
    nacos:
      server-addr: 192.168.52.128:8848  # Nacos 服务器地址,配置 Nacos 注册中心地址
    gateway:
      routes:  # 路由配置
        - id: item-service  # 路由 ID,唯一标识,可以随便命名
          uri: lb://item-service  # 目标服务地址,即从注册中心获取 item-service 的地址
          predicates:  # 断言,即路由转发的规则
            - Path=/items/**,/search/**  # 匹配 /items/ 开头的和 /search/ 开头的请求到 item-service 服务获取响应
        - id: user-service  
          uri: lb://user-service  
          predicates:  
            - Path=/items/**,/search/** 

4、最后启动整个项目的时候也要把网关启动

由下图可见网关的效果有了

 网关的登录校验

网关过滤器有两种,分别是:

  • GatewayFilter: 路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
  • GlobalFilter: 全局过滤器,作用范围是所有路由;声明后自动生效。

网关加公共依赖XXX-common实现请求的校验

1、网关过滤器过滤请求(Filters文件夹)

@Component // 将该类标记为Spring组件,使其成为Spring容器管理的Bean
@RequiredArgsConstructor // Lombok注解,自动生成一个包含所有final字段的构造函数
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    // 依赖注入JwtTool,用于JWT的解析和验证
    private final JwtTool jwtTool;
    // 依赖注入AuthProperties,包含认证相关的配置信息,如排除路径等
    private final AuthProperties authProperties;
    // AntPathMatcher用于路径匹配,判断请求路径是否在排除路径中
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取当前请求对象
        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); // 获取第一个authorization头,通常为Bearer Token
        }

        // 4. 校验并解析token
        Long userId = null;
        try {
            // 使用JwtTool解析token,获取用户ID
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果token无效或解析失败,拦截请求并返回401 Unauthorized状态码
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete(); // 结束请求处理
        }

        // 打印用户ID(通常用于调试,生产环境中不建议直接打印敏感信息)
        System.out.println("userId = " + userId);
        String userInfo = userId.toString();
        // 将用户信息存入请求头
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header("user-info", userInfo))
                .build();
        // 5. 如果token有效,继续执行后续的过滤器链
        return chain.filter(swe);
    }

    // 判断请求路径是否在排除路径列表中
    private boolean isExclude(String path) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            // 使用AntPathMatcher进行路径匹配
            if (antPathMatcher.match(pathPattern, path)) {
                return true; // 如果匹配到排除路径,返回true
            }
        }
        return false; // 否则返回false
    }

    @Override
    public int getOrder() {
        // 返回过滤器的执行顺序,0表示最高优先级
        return 0;
    }
}

 过滤器里涉及的一些依赖

// jwt校验工具
@Component
public class JwtTool {
    private final JWTSigner jwtSigner;

    public JwtTool(KeyPair keyPair) {
        this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
    }
    public String createToken(Long userId, Duration ttl) {
        // 1.生成jws
        return JWT.create()
                .setPayload("user", userId)
                .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
                .setSigner(jwtSigner)
                .sign();
    }

    /**
     * 解析token
     *
     * @param token token
     * @return 解析刷新token得到的用户信息
     */
    public Long parseToken(String token) {
        // 1.校验token是否为空
        if (token == null) {
            throw new UnauthorizedException("未登录");
        }
        // 2.校验并解析jwt
        JWT jwt;
        try {
            jwt = JWT.of(token).setSigner(jwtSigner);
        } catch (Exception e) {
            throw new UnauthorizedException("无效的token", e);
        }
        // 2.校验jwt是否有效
        if (!jwt.verify()) {
            // 验证失败
            throw new UnauthorizedException("无效的token");
        }
        // 3.校验是否过期
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            throw new UnauthorizedException("token已经过期");
        }
        // 4.数据格式校验
        Object userPayload = jwt.getPayload("user");
        if (userPayload == null) {
            // 数据为空
            throw new UnauthorizedException("无效的token");
        }

        // 5.数据解析
        try {
            return Long.valueOf(userPayload.toString());
        } catch (RuntimeException e) {
            // 数据格式有误
            throw new UnauthorizedException("无效的token");
        }
    }
}


 // 拦截器拦截
@Data
@Component
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {
    private List<String> includePaths;
    private List<String> excludePaths;
}

2、网关的yaml文件里配置不需要校验直接放行的请求 

hm:
  jwt: #解析jwt密钥文件
    location: classpath:hmall.jks
    alias: hmall
    password: hmall123
    tokenTTL: 30m
  auth:
    excludePaths:
      - /search/**
      - /users/login
      - /items/**
      - /hi

3、由于每一个微服务都导入了XX-common模块的依赖,所以在XX-common模块里配置并注册拦截器,拦截所有发送到每个微服务里的请求,用于将请求头里用户信息存入线程池。

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取登录用户信息
        String userInfo = request.getHeader("user-info"); // 从请求头里获取
        // 2. 判断是否获取了用户,如果有,存入ThreadLocal
        if (StrUtil.isNotBlank(userInfo)) {
            UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3. 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理用户
        UserContext.removeUser();
    }
}

 4、注册XX-common模块里的拦截器

@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 使得网关不去生效改拦截器
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
        // 默认拦截所有的请求,目的是为了将每一个请求里包含的用户信息存入线程池
    }
}

5、配置静态资源文件夹下的spring.factories文件,取保每个微服务可以读取到XX-common模块里的拦截器

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig,\
  com.hmall.common.config.JsonConfig

OpenFeign传递用户信息

使用OpenFeign时,一个微服务发送给另一个微服务的请求也要携带用户信息到请求头里,要和网关发送给微服务的请求一样。所有要在公共api模块里加拦截器,使得每一个请求的请求头里添加用户信息。

写到OpenFeign的配置类里,且微服务的启动类加上@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)的注解

 // 写到OpenFeign的配置类里,且微服务的启动类加上
// @EnableFeignClients(basePackages = "com.hmall.api.client",
//  defaultConfiguration = DefaultFeignConfig.class)的注解
@Bean // 声明为一个Bean,可以被Spring容器管理
    public RequestInterceptor userInfoRequestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                // 获取当前用户的ID
                Long userId = UserContext.getUser(); // 导入XXX-common模块里的线程池
                // 如果用户ID不为空,则添加到请求头中
                if (userId != null) { // 确保每一个微服务之间发送的请求也携带user-info到请求头里
                    // 将用户ID添加到请求头中,key为"user-info"
                    System.out.println("将用户ID添加到请求头中,key为user-info,id为" + userId);
                    template.header("user-info", userId.toString());
                }
            }
        };
    }

nacos共享配置

由于每一个微服务的yaml文件里有多个共同的配置信息,所有可以将其抽取出来的配置共同使用nacos注册中心配置。

 每一个微服务里导入如下依赖即可实现。

<!-- 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>  

nacos里共同配置的信息()${XXX:YY}表示如果读取不到XXX则默认为YY

# 数据库和mybatis
spring:
  datasource:
    url: jdbc:mysql://${hm.db.host:192.168.52.128}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: ${hm.db.un:root}
    password: ${hm.db.pw:123}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto

# 日志记录
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"

# swagger配置
knife4j:
  enable: true
  openapi:
    title: ${hm.swagger.title:黑马商城接口文档}
    description: ${hm.swagger.desc:黑马商城接口文档}
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - ${hm.swagger.package}

拉取nacos里的配置文件到本地微服务(以下为bootstrap.yaml文件)

spring:  
  main:  
    additional-properties: --add-opens=java.base/java.lang.invoke=ALL-UNNAMED  
  application:  
    # 应用程序名称,用于标识该服务,在Nacos或其他服务注册中心中可用到  
    name: cart-service  
  cloud:  
    nacos:  
      # Nacos的服务地址,用于连接到Nacos服务器  
      server-addr: localhost:8848  # nacos地址  
      config:  
        # 配置文件的格式,这里指定为YAML格式  
        file-extension: yaml  
        # 定义共享配置文件列表,这些配置将从Nacos服务器加载  
        shared-configs:   # 一定对应好nacos里的Data ID
          - data-id: shared-jdbc.yaml  # JDBC共享配置文件  
          - data-id: shared-log.yaml    # 日志共享配置文件  
          - data-id: shared-swagger.yaml # Swagger共享配置文件  

nacos配置里的变量在本地微服务里配置好(以下为application.yaml文件)

server:
  port: 8082
feign:
  okhttp:
    enabled: true
hm:
  db:
    database: hm-cart
  swagger:
    title: "黑马城市购物车服务接口文档"
    package: com.hmall.cart.controller

配置热更新

配置热更新:修改配置文件里的配置的时候,不需要重新启动微服务项目配置就可以生效配置。

具体应用实例

需求:购买车的限定数量目前是写死在业务中的,将其改为读取配置文件属性,并将配置置交给Nacos管理,实现热更新。

首先在nocas配置要限定数量所在的微服务的yaml文件

之后在对应的微服务里添加config文件 

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart") // 对应yaml文件里的配置
public class CartProperties {
    private Integer maxItems;
}

最后在业务文件里面就可以去使用了

private final CartProperties cartProperties; // 导入依赖
......
    private void checkCartsFull(Long userId) {
        int count = lambdaQuery().eq(Cart::getUserId, userId).count();
        if (count >= cartProperties.getMaxItems()) {
            throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));
        }
    }
......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

comctl32!ListView_OnSetItem函数分析LISTSUBITEM结构中的image表示图标位置

第一部分&#xff1a; BOOL ListView_SetSubItem(LV* plv, const LV_ITEM* plvi) { LISTSUBITEM lsi; BOOL fChanged FALSE; int i; int idpa; HDPA hdpa; if (plvi->mask & ~(LVIF_DI_SETITEM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) { …

数据结构——多项式问题(顺序存储结构or链式存储结构)

补充&#xff1a;malloc函数&#xff1a; malloc 函数是 C 语言标准库中的一个重要函数&#xff0c;位于 <stdlib.h> 头文件中&#xff0c;主要用于在程序运行时动态分配内存。以下将详细介绍其用法。 前面的返回值指针可以自己定义&#xff0c;如 &#xff08;int*&am…

记录小白使用 Cursor 开发第一个微信小程序(一):注册账号及下载工具(250308)

文章目录 记录小白使用 Cursor 开发第一个微信小程序&#xff08;一&#xff09;&#xff1a;注册账号及下载工具&#xff08;250308&#xff09;一、微信小程序注册摘要1.1 注册流程要点 二、小程序发布流程三、下载工具 记录小白使用 Cursor 开发第一个微信小程序&#xff08…

vue2项目修改浏览器显示的网页图标

1.准备一个新的图标文件&#xff0c;通常是. ico格式&#xff0c;也可以是. Png、. Svg等格式 2.将新的图标文件(例如&#xff1a;faviconAt.png)放入项目的public文件夹中。如下图 public文件夹中的所有文件都会在构建时原样复制到最终的输出目录(通常是dist) 3. 修改vue项目…

【网络安全工程】任务10:三层交换机配置

CSDN 原创主页&#xff1a;不羁https://blog.csdn.net/2303_76492156?typeblog三层交换机是指在OSI&#xff08;开放系统互连&#xff09;模型中的第三层网络层提供路由功能的交换机。它不仅具备二层交换机的交换功能&#xff0c;还能实现路由功能&#xff0c;提供更为灵活的网…

侯捷 C++ 课程学习笔记:C++内存管理机制

内存管理从平地到万丈高楼 内存管理入门&#xff08;Memory Management 101&#xff09; 需要具有动态分配并使用memory&#xff08;存储&#xff08;器&#xff09;&#xff0c;&#xff08;计算机的&#xff09;内存&#xff09;&#xff0c;使用过C标准库的容器&#xff0…

JVM常用概念之本地内存跟踪

问题 Java应用启动或者运行过程中报“内存不足&#xff01;”&#xff0c;我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言&#xff0c;需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构&#xff0c;来保证JVM应用的成功启动以及未来平…

【鸿蒙开发】Hi3861学习笔记- 软件定时器示例

00. 目录 文章目录 00. 目录01. 定时器概述02. 定时器API03. 定时器常用API3.1 osTimerNew3.2 osTimerDelete3.3 osTimerStart3.4 osTimerStop 04. 程序示例05. 附录 01. 定时器概述 软件定时器&#xff0c;是基于系统Tick时钟中断且由软件来模拟的定时器&#xff0c;当经过设…

在Html5中仿Matlab自定义色带生成实践

目录 前言 一、RGB的相关知识 1、RGB的基本原理 2、RGB的数值表示 3、应用场景 二、ColorMap生成实战 1、外部库介绍 2、相关API 3、实例生成 三、总结 前言 在现代网页开发与数据可视化领域&#xff0c;色彩的表现力对于信息传达和视觉体验起着至关重要的作用。色带&…

贪心算法--

1.柠檬水找零 link:860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; code class Solution { public:bool lemonadeChange(vector<int>& bills) {// 贪心算法&#xff0c; 优先花出大面额bill&#xff0c; 尽可能保护小面额billint five 0, ten 0;// 不…

如何选择国产串口屏?

目录 1、迪文 2、淘晶驰 3、广州大彩 4、金玺智控 5、欣瑞达 6、富莱新 7、冠显 8、有彩 串口屏&#xff0c;顾名思义&#xff0c;就是通过串口通信接口&#xff08;如RS232、RS485、TTL UART等&#xff09;与主控设备进行通信的显示屏。其核心功能是显示信息和接收输入…

matlab慕课学习3.1

3.1顺序结构程序 于20250306 3.1.1程序和程序设计 程序是用某种计算机能够理解并且能够执行的语言来描述的解决问题的方法和步骤。 3.1.2程序的三种基本结构 1.顺序结构 2.选择结构 3.循环结构 3.1.3脚本文件和函数文件 脚本文件是可在命令行窗口直接执行的文件&#xff0…

cesium地图设置3d,2d,2.5d动态切换

通过修改cesium实例vw的scene的显示模式&#xff0c;来切换最终的显示模式。 Cesium.SceneMode总共有四个变量值&#xff0c;分别如下&#xff1a;NameTypeDescriptionMORPHINGnumber在3d与2d之间切换变体 between mode, e.g., 3D to 2D.COLUMBUS_VIEWnumber2.5d模式&#xff0…

【数据结构】二叉搜索树、平衡搜索树、红黑树

二叉搜索树&#xff08;Binary Search Tree&#xff09; 二叉搜索树是一种特殊的二叉树&#xff0c;它用来快速搜索某个值&#xff0c;对于每个节点都应该满足以下条件&#xff1a; 若该节点有左子树&#xff0c;那么左子树中所有节点的值都应该小于该节点的值。若该节点有右…

密码学(终极版)

加密 & 解密 备注&#xff1a;密码学领域不存在完全不能破解的密码&#xff0c;但是如果一个密码需要很久很久&#xff0c;例如一万年才能破解&#xff0c;就认为这个密码是安全的了。 对称加密 非对称加密 公钥加密、私钥解密 私钥签名、公钥认证 非对称的底层原理是…

经销商管理系统选型解析:8款产品详评

本文主要介绍了以下8款经销商管理系统&#xff1a;1.纷享销客&#xff1b; 2.用友T6经销商管理系统&#xff1b; 3.金蝶经销商管理系统&#xff1b; 4.鼎捷经销商管理系统&#xff1b; 5.浪潮经销商管理系统&#xff1b; 6.销售易&#xff1b; 7.SAP Business One Distributor …

【C++】函数重载与nullptr

1、函数重载 C支持在同一个作用域中出现同名函数&#xff0c;但是要求这些同名函数的形参不同&#xff0c;可以是形参个数不同或者类型不同。这样C函数调用就表现出了多态行为&#xff0c;使用更灵活。C语言是不支持同一作用域中出现同名函数的。 代码&#xff1a; 形参类型不…

处理动态分页:自动翻页与增量数据抓取策略-数据议事厅

一、案例场景 Lily&#xff08;挥舞着数据报表&#xff09;&#xff1a;“用户反馈我们的股票舆情分析总是缺失最新跟帖&#xff01;这些动态分页像狡猾的狐狸&#xff0c;每次抓取都漏掉关键数据&#xff01;” 小王&#xff08;调试着爬虫代码&#xff09;&#xff1a;“传…

用android studio模拟器,模拟安卓手机访问网页,使用Chrome 开发者工具查看控制台信息

web 网页项目在安卓手机打开时出现问题&#xff0c;想要查看控制台调试信息。记录一下使用android studio 模拟器访问的方式。 步骤如下&#xff1a; 1.安装android studio&#xff0c;新增虚拟设备&#xff08;VDM- virtual device manager) 点击Virtual Device Manager后会…

【Linux内核系列】:深入理解缓冲区

&#x1f525; 本文专栏&#xff1a;Linux &#x1f338;作者主页&#xff1a;努力努力再努力wz ★★★ 本文前置知识&#xff1a; 文件系统以及相关系统调用接口 输入以及输出重定向 那么在此前的学习中&#xff0c;我们了解了文件的概念以及相关的系统调用接口&#xff0c;并…