# ants-parent **Repository Path**: ly-springcloud-alibaba/ants-parent ## Basic Information - **Project Name**: ants-parent - **Description**: SpringCloud Alibaba - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-05-22 - **Last Updated**: 2025-07-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### SpringCloudAlibaba框架的搭建 #### **1.技术选型** Maven:3.6.3 持久层:mybatisplus 数据库:5.7 Springboot:2.7.18 Springcloudalibaba:2021.0.6.2 Nacos:2.5.1 Sentinel:1.8.6 #### **2.模块设计** ants-parent: 父工程 ants-common: 公共模块 ants-gatewaty:网关模块 ants-oauth2-auth: 授权模块 ants-oauth2-gatewaty: 授权网关模块 ants-order: 订单模块 ants-product: 产品(库存)模块 ants-user: 用户模块 #### **3.基础准备** ​ 为了提高依赖包的下载速度,推荐使用国内镜像下载,这里提供几个国内镜像,maven的setting.xml配置如下 ``` alimaven central Aliyun Maven https://maven.aliyun.com/repository/public huaweicloud central Huawei Cloud Maven https://repo.huaweicloud.com/repository/maven/ tencentcloud central tencent Cloud Maven https://mirrors.cloud.tencent.com/maven/ wangyicloud central wangyi Cloud Maven https://maven.163.com/repository/maven-public/ ``` #### **4.框架搭建** ##### 4.1 创建父工程 ​ 创建maven工程,名称为ants-parent。pom.xml配置如下,各个版本对应的关系可查看该链接https://developer.aliyun.com/article/1532079 ``` 4.0.0 com.ants ants-parent 1.0.0 pom ants-parent ants-common ants-user ants-product ants-order ants-gateway ants-oauth2-auth ants-oauth2-gateway UTF-8 org.springframework.boot spring-boot-starter-parent 2.7.18 org.springframework.boot spring-boot-starter-log4j2 2.7.18 org.springframework.boot spring-boot-starter-validation 2.7.18 org.springframework.boot spring-boot-starter-aop 2.7.18 org.springframework.boot spring-boot-starter-data-redis 2.7.18 io.netty netty-common io.netty netty-handler org.springframework spring-context io.netty netty-common 4.1.118.Final io.netty netty-handler 4.1.118.Final com.fasterxml.jackson.dataformat jackson-dataformat-xml 2.18.0 com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-databind 2.18.0 com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-core 2.18.0 com.fasterxml.jackson.core jackson-annotations 2.18.0 com.alibaba fastjson 1.2.83 net.sf.json-lib json-lib 2.4 jdk15 commons-logging commons-logging commons-beanutils commons-beanutils commons-collections commons-collections org.aspectj aspectjweaver 1.9.7 org.apache.commons commons-lang3 3.17.0 com.mysql mysql-connector-j 8.2.0 com.google.protobuf protobuf-java com.google.protobuf protobuf-java 3.25.5 com.alibaba druid-spring-boot-starter 1.2.20 org.springframework.boot spring-boot-starter-security 2.7.18 org.springframework.cloud spring-cloud-starter-oauth2 2.2.5.RELEASE com.nimbusds nimbus-jose-jwt 10.3 eu.bitwalker UserAgentUtils 1.21 org.apache.httpcomponents httpclient 4.5.13 org.apache.httpcomponents httpmime 4.5.5 compile cn.hutool hutool-all 5.8.24 org.springframework.boot spring-boot-starter-test 2.7.18 test com.jayway.jsonpath json-path org.xmlunit xmlunit-core org.xmlunit xmlunit-core 2.10.0 test com.jayway.jsonpath json-path 2.9.0 test org.springframework.cloud spring-cloud-dependencies 2021.0.9 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2021.0.6.2 pom import com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config 2021.0.6.2 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2021.0.6.2 org.springframework.cloud spring-cloud-starter-openfeign 4.1.4 org.springframework.cloud spring-cloud-starter-loadbalancer 3.1.0 com.alibaba.cloud spring-cloud-starter-alibaba-sentinel 2021.0.6.2 org.springframework.cloud spring-cloud-starter-gateway 3.1.0 org.springframework.boot spring-boot-starter-webflux 3.5.0 org.springframework.security spring-security-oauth2-resource-server 5.7.11 org.springframework.security spring-security-oauth2-client 5.7.11 org.springframework.security spring-security-oauth2-jose 5.7.11 org.springframework.security spring-security-config 5.7.11 ``` ##### 4.2 创建公共模块 ###### 4.2.1新建module工程,命名为ants-common,pom.xml引入以下依赖 ``` 4.0.0 com.ants ants-parent 1.0.0 ants-common jar ants-common http://maven.apache.org UTF-8 com.mysql mysql-connector-j 8.2.0 com.google.protobuf protobuf-java com.google.protobuf protobuf-java 3.25.5 com.alibaba druid-spring-boot-starter 1.2.20 com.baomidou mybatis-plus-boot-starter 3.5.5 com.github.pagehelper pagehelper-spring-boot-starter 1.4.5 org.mybatis mybatis org.mybatis mybatis-spring org.aspectj aspectjweaver 1.9.7 org.apache.commons commons-lang3 org.springframework.boot spring-boot-starter-validation 2.7.6 org.springframework.boot spring-boot-starter-aop com.fasterxml.jackson.dataformat jackson-dataformat-xml com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-annotations ants-common ``` ###### 4.2.2分别新建简单的实体类 ![image-20250730100502767](./img/image-20250730100502767.png) ​ a. Orers类 ``` @TableId(value = "oid",type = IdType.AUTO) private Integer oid; /** * 用户id */ private Integer uid; private String username; private Integer pid; private String pname; private String price; private Integer number; ``` ​ b. Product类 ``` @TableId(value = "pid",type = IdType.AUTO) private Integer pid; private String pname; private String price; private Integer stock; @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; ``` ​ c. User类 ``` @TableId(value = "uid",type = IdType.AUTO) private Integer uid; private String username; private String password; private String phone; @TableField(exist = false) private List roles; ``` ###### 4.2.3分别创建子模块ants-order、ants-product和ants-user服务 ​ a. 这里我们以ants-user为例,该项目配置完成后,其他项目按照该方法配置即可,ants-user的pom.xml配置如下 ``` 4.0.0 com.ants ants-parent 1.0.0 ants-user 8 8 UTF-8 com.ants ants-common 1.0.0 compile org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-log4j2 org.springframework.boot spring-boot-starter-validation org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-redis 2.7.18 io.netty netty-common 4.1.118.Final io.netty netty-handler 4.1.118.Final com.fasterxml.jackson.dataformat jackson-dataformat-xml com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.core jackson-core com.fasterxml.jackson.core jackson-annotations com.alibaba fastjson 1.2.83 net.sf.json-lib json-lib jdk15 org.aspectj aspectjweaver com.mysql mysql-connector-j com.google.protobuf protobuf-java com.alibaba druid-spring-boot-starter io.springfox springfox-swagger2 2.9.2 com.github.xiaoymin swagger-bootstrap-ui 1.9.6 com.google.guava guava 32.0.0-jre com.github.whvcse easy-captcha 1.6.2 com.google.code.gson gson 2.9.0 eu.bitwalker UserAgentUtils 1.21 org.apache.httpcomponents httpclient 4.5.13 org.apache.httpcomponents httpmime 4.5.5 compile cn.hutool hutool-all 5.8.24 org.springframework.boot spring-boot-starter-test test org.xmlunit xmlunit-core test com.jayway.jsonpath json-path test ants-user org.springframework.boot spring-boot-maven-plugin 2.7.6 com.ants.user.UserApplicationStart true org.apache.maven.plugins maven-compiler-plugin 3.13.0 1.8 1.8 ${java.home}/lib/rt.jar${path.separator}${java.home}/lib/jce.jar ``` ​ b. 资源文件配置 ​ 新建ants-user数据库以及user表,在resources目录下,新建application.yml和application-dev.yml文件,编写测试方法,比如根据id获取用户信息,并启动项目,测试是否能正常访问。 ​ application.yml配置如下: ``` spring: application: #项目名,为了区分每个项目,这里注意不同的项目记得修改 name: ants-user main: allow-circular-references: true profiles: #使用dev环境 active: dev ``` ​ application-dev.yml配置如下 ``` #端口号 server: port: 8081 servlet: #项目路径 context-path: /ants-user-dev #Mybatis配置.xml文件路径、模型路径 mybatis-plus: mapper-locations: classpath*:/mapper/*Mapper.xml type-aliases-package: com.ants.common.** configuration: map-underscore-to-camel-case: true #sql打印 #控制台输出,方便调试 #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志文件输出 log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl #Mybatis分页配置 pagehelper: helperDialect: mysql reasonable: true supportMethodsArguments: true params: count=countSql #log4j2 日志配置 logging: level: root: info com.alibaba.cloud.nacos: debug # config: classpath:log4j2.xml spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.99.72:3306/ants_user?serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 #验证连接的有效性,当连接空闲时,是否执行连接测试 username: root password: root druid: #初始化大小,最小,最大 initial-size: 5 min-idle: 5 max-active: 20 #配置获取连接等待超时的时间 max-wait: 6000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 #配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false #打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 #sql健监控filter filter: stat: # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true enabled: true db-type: mysql wall: config: multi-statement-allow: true #WebStatFilter配置 web-stat-filter: enabled: true exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: false login-username: admin login-password: 123456 redis: #Redis数据库索引(默认为0) database: 0 host: localhost port: 6379 password: #连接超时时间 2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位 timeout: 6S jedis: pool: #接池最大连接数(使用负值表示没有限制) max-active: 200 #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1 #连接池中的最大空闲连接 max-idle: 20 #连接池中的最小空闲连接 min-idle: 8 ``` ##### 4.3 **集成注册中心nacos** ​ 我们同样以ants-user为例,首先下载nacos2.5.1,解压后,使用cmd命令进入到bin目录下,通过startup.cmd -m standalone命令启动nacos。如下启动成功,通过控制台给出的地址可访问。进入到内部后,点击新建命名空间,并新建ants-parent-dev空间名称。新建该命名空间是为了区分不同的环境,为后续开发做铺垫(主要是为了区分不同的环境,因为有些项目有开发、测试、生产)。 ![image-20250730104215664](./img/image-20250730104215664.png) ###### 4.3 .1改造ants-user ​ a.我们使用nacos为注册中心,并配置config,将项目中各种配置全部都放到一个集中的地方进行统一管理,并提供一套标准的接口,当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。 当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。 ![image-20250730104939035](./img/image-20250730104939035.png) ​ b.在子工程ants-user的pom.xml加入以下依赖并在启动类上加@EnableDiscoveryClient注解 ``` com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ``` ​ c.application-dev.yml修改如下 ``` #nacos注册中心的地址,如果有用户名和密码,记得要配置 nacos: url: localhost:8848 #端口号 server: port: 8081 #项目名称,每个项目都不一样 servlet: context-path: /ants-user-dev #以下是nacos的config配置 spring: cloud: nacos: config: server-addr: ${nacos.url} #指定配置文件扩展名为yml file-extension: yml #配置的所属组 group: ANTS_GROUP #命名空间 对应系nacos配置运行环境:dev|test|prod namespace: ants-parent-${spring.profiles.active} discovery: server-addr: ${nacos.url} #服务的命名空间 namespace: ants-parent-${spring.profiles.active} #服务的所属组 group: ANTS_GROUP config: import: - optional:nacos:${spring.application.name}-common-${spring.profiles.active}.yml # 公共配置 - optional:nacos:${spring.application.name}-database-${spring.profiles.active}.yml # mysql数据库开发环境 - optional:nacos:${spring.application.name}-redis-${spring.profiles.active}.yml # redis开发环境配置 ``` 4.3 .2 nacos注册中心增加配置 ​ 点击配置管理-->配置列表-->命名空间选择ants-parent-dev-->创建配置。 ``` Data id :ants-user-common-dev.yml Group: ANTS_GROUP 内容: #Mybatis配置.xml文件路径、模型路径 mybatis-plus: mapper-locations: classpath*:/mapper/*Mapper.xml type-aliases-package: com.ants.common.** configuration: map-underscore-to-camel-case: true #sql打印 #控制台输出,方便调试 #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志文件输出 log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl #Mybatis分页配置 pagehelper: helperDialect: mysql reasonable: true supportMethodsArguments: true params: count=countSql #log4j2 日志配置 logging: level: root: info com.alibaba.cloud.nacos: debug # config: classpath:log4j2.xml --------------------------------分割线------------------------- Data id : ants-user-redis-dev.yml Group: ANTS_GROUP 内容: spring: redis: #Redis数据库索引(默认为0) database: 0 host: localhost port: 6379 password: #连接超时时间 2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位 timeout: 6S jedis: pool: #接池最大连接数(使用负值表示没有限制) max-active: 200 #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1 #连接池中的最大空闲连接 max-idle: 20 #连接池中的最小空闲连接 min-idle: 8 --------------------------------分割线------------------------- Data id :ants-user-database-dev.yml Group: ANTS_GROUP 内容: spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.99.72:3306/ants_user?serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8 #验证连接的有效性,当连接空闲时,是否执行连接测试 username: root password: root druid: #初始化大小,最小,最大 initial-size: 5 min-idle: 5 max-active: 20 #配置获取连接等待超时的时间 max-wait: 6000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 #配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false #打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 #sql健监控filter filter: stat: # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true enabled: true db-type: mysql wall: config: multi-statement-allow: true #WebStatFilter配置 web-stat-filter: enabled: true exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: false login-username: admin login-password: 123456 ``` ​ 以上配置完成后,重新启动程序,在nacos管理界面,服务管理-->服务列表-->ants-parent-dev(命名空间)可查看注册的服务,并测试之前写的方法是否能正常访问,同理,ants-order和ants-product配置方式和步骤遵循ants-user的配置即可。 ##### 4.4 基于Feign实现服务调用 ​ Feign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。但是随着版本的更新,负载均衡被单独集成了一个依赖,因此要集成loadbalancer包,否则项目无法启动。 ###### 4.4.1引入依赖 ​ a.pom.xml加入依赖包,并在启动类上加入@EnableFeignClients注解, ``` org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer ``` ​ b.在订单服务工程(ants-order)中,新建FeignProductService,通过商品id获取库存。模拟用户通过商品的id购买商品,并生成订单,在ants-product项目中,编写具体的实现方法和逻辑 ![image-20250730112520237](./img/image-20250730112520237.png) ![image-20250730112440463](./img/image-20250730112440463.png) ![image-20250730112708439](./img/image-20250730112708439.png) ​ c.分别启动订单服务和商品服务,通过url地址,模拟用户买了商品id为1的商品 ![image-20250730112744779](./img/image-20250730112744779.png) ##### 4.5 引入gateway实现网关服务 ​ Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。 ​ 所谓的API网关,就是指系统的**统一入口**,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等 **优点:** ​ 性能强劲:是第一代网关Zuul的1.6倍 ​ 功能强大:内置了很多实用的功能,例如转发、监控、限流等 ​ 设计优雅,容易扩展 **缺点:** ​ 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高 ​ 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 ​ 需要Spring Boot 2.0及以上的版本,才支持 ###### 4.5.1 pom.xml配置 ``` 4.0.0 com.ants ants-parent 1.0.0 ants-gateway 8 8 UTF-8 org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` ###### 4.5.2 application.yml以及application-dev.yml配置 ​ a.application.yml配置 ``` spring: application: name: ants-gateway main: allow-circular-references: true profiles: active: dev ``` ​ b.application-dev.yml配置 ``` nacos: url: localhost:8848 #端口号 server: port: 7000 spring: cloud: nacos: discovery: server-addr: ${nacos.url} #服务的命名空间 namespace: ants-parent-${spring.profiles.active} #服务的所属组 group: ANTS_GROUP gateway: discovery: locator: enabled: true # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] routes: - id: product_route uri: lb://ants-product # 请求要转发到的地址 predicates: # 断言(就是路由转发要满足的条件) - Path=/ants-product-dev/** # 当请求路径满足Path指定的规则时,转发到product服务 - id: order_route uri: lb://ants-order # 请求要转发到的地址 predicates: # 断言(就是路由转发要满足的条件) - Path=/ants-order-dev/** # 当请求路径满足Path指定的规则时,转发到order服务 ``` 4.5.3 测试 ​ 分别启动ants-order、ants-product、ants-gateway服务,通过网关进行访问,测试结果如下,这样通过统一的入口,就能访问到不同服务 ![image-20250730113525736](./img/image-20250730113525736.png) ##### 4.6 集成oauth2 a.新建ants-oauth2-auth项目,引入依赖 ``` 4.0.0 com.ants ants-parent 1.0.0 ants-oauth2-auth 8 8 UTF-8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.cloud spring-cloud-starter-oauth2 com.nimbusds nimbus-jose-jwt org.springframework.boot spring-boot-starter-data-redis 2.7.18 io.netty netty-common 4.1.118.Final io.netty netty-handler 4.1.118.Final org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` ​ b.使用java自带的命令生成密钥库,进入到java的bin目录下,使用keytool命令生成 ``` keytool -genkeypair -alias jwt -keyalg RSA -keystore jwt.jks -validity 365 -storetype JKS ``` ​ 命令解读: ​ -genkeypair 用于生成密钥对。 ​ -alias jwt 指定密钥对的别名为jwt ​ -keyalg RSA 指定密钥算法为RSA。 ​ -keystore jwt.jks 指定输出文件名为jwt.jks。 ​ -validity 365 指定证书有效期为365天。 ​ -storetype JKS 指定密钥库类型为JKS。 ​ -keysize 2048 指定密钥长度,可以不用设置,使用默认 ​ 输入以上命令之后,要求你输入钥匙库的口令,输入口令之后下面的都默认,知道最后输入y,结束后在bin目录下,会有一个jwt.jks文件,将文件复制到该项目的resources目录下即可 ![image-20250730135308948](./img/image-20250730135308948.png) ​ c.application.yml文件配置 ``` spring: application: name: ants-oauth2-auth main: allow-circular-references: true profiles: active: dev ``` d.application-dev.yml文件配置 ``` nacos: url: localhost:8848 #端口号 server: port: 8083 spring: cloud: nacos: discovery: server-addr: localhost:8848 #服务的命名空间 namespace: ants-parent-${spring.profiles.active} #服务的所属组 group: ANTS_GROUP redis: #Redis数据库索引(默认为0) database: 0 host: localhost port: 6379 password: #连接超时时间 2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位 timeout: 6S jedis: pool: #接池最大连接数(使用负值表示没有限制) max-active: 200 #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1 #连接池中的最大空闲连接 max-idle: 20 #连接池中的最小空闲连接 min-idle: 8 ``` e.以下是java程序相关配置 - ​ 新建Tokenconfig,这里我使用了redis为载体,生成的token都在redis中保存 ``` @Configuration public class TokenConfig { @Autowired(required = false) private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setKeyPair(keyPair()); //对称秘钥,资源服务器使用该秘钥来验证 return converter; } @Bean public KeyPair keyPair() { //从classpath下的证书中获取秘钥对 //jwk.jks为刚刚我们生成的密钥库文件名称,changeit为密钥库的密码 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"),"changeit".toCharArray()); //jwt 为生产密钥库时指定的别名,changeit为密码 return keyStoreKeyFactory.getKeyPair("jwt", "changeit".toCharArray()); } } ``` - ​ 新建JwtTokenEnhancer类,实现TokenEnhancer接口,增强JWT的内容,往生成的token中加入用户的id,其他的根据需要添加。 ``` @Component public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { SecurityUser securityUser = (SecurityUser) oAuth2Authentication.getPrincipal(); Map info = new HashMap<>(); //把用户ID设置到JWT中 info.put("id", securityUser.getUid()); ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info); return oAuth2AccessToken; } } ``` - 配置oauth2服务 ``` @Configuration @EnableAuthorizationServer public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter { private static final String DEMO_RESOURCE_ID = "order"; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter accessTokenConverter; @Autowired private UserServiceImpl userServiceDetail; @Autowired private JwtTokenEnhancer jwtTokenEnhancer; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //配置客户端,password认证,这里设置客户端的密码为123456 clients.inMemory().withClient("client_1") .resourceIds(DEMO_RESOURCE_ID) .authorizedGrantTypes("password", "refresh_token") .scopes("all") //.secret是为了判断传过来的值和这里设置的要相等 //这里我使用了new BCryptPasswordEncoder,是因为 WebSecurityConfig 中配置了 //因此为了密码校验通过,必须使用同等的方法进行加密后判断 .secret(new BCryptPasswordEncoder().encode(("123456"))) //设置 client_1 令牌过期时间秒 2个小时,refreshTokenValiditySeconds设置的是刷新时间 .accessTokenValiditySeconds(60 * 60 * 2) .refreshTokenValiditySeconds(60 * 60 * 5); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenServices(tokenService()) .userDetailsService(userServiceDetail) .authenticationManager(authenticationManager); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } @Bean public AuthorizationServerTokenServices tokenService() { DefaultTokenServices service = new DefaultTokenServices(); service.setSupportRefreshToken(true); service.setTokenStore(tokenStore); //令牌增强 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List tokenEnhancers = new ArrayList<>(); tokenEnhancers.add(jwtTokenEnhancer); tokenEnhancers.add(accessTokenConverter); tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(60 * 60 * 2); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(60 * 60 * 5); // 刷新令牌默认有效期5小时 return service; } } ``` - ​ 其中UserServiceImpl 类,是我们的实现类,这里应该从数据库中获取是否有用户,以及用户所具有的权限,为了方便,我们暂时不从数据库中读取,使用全局变量,以及初始化时赋值。 ``` @Service public class UserServiceImpl implements UserDetailsService { private List userList; //程序启动时,往全局变量userList塞入用户,以及用户所具有的权限 @PostConstruct public void initData() { String password = new BCryptPasswordEncoder().encode("123456"); userList = new ArrayList<>(); userList.add(new UserDto(1,"macro", password,"18698589620", Arrays.asList("ROLE_ADMIN"))); userList.add(new UserDto(2,"andy", password,"18698589620", Arrays.asList("ROLE_TEST","ROLE_USER"))); } //重写loadUserByUsername方法,根据传入的用户名查询是否有该用户 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List findUserList = userList.stream() .filter(item -> item.getUsername().equals(username)).collect(Collectors.toList()); if (findUserList.isEmpty()) { throw new UsernameNotFoundException("用户名或密码错误!"); } UserDto user = findUserList.get(0); List authorities = user.getRoles().stream() .map(SimpleGrantedAuthority::new).collect(Collectors.toList()); SecurityUser securityUser = getSecurityUser(user, authorities); return securityUser; } private static SecurityUser getSecurityUser(UserDto user, List authorities) { SecurityUser securityUser = new SecurityUser(user.getUsername(), user.getPassword(), authorities); if (!securityUser.isEnabled()) { throw new DisabledException("账户已禁用"); } else if (!securityUser.isAccountNonLocked()) { throw new LockedException("账户已锁定"); } else if (!securityUser.isAccountNonExpired()) { throw new AccountExpiredException("用户账户已经过期"); } else if (!securityUser.isCredentialsNonExpired()) { throw new CredentialsExpiredException("用户凭证已过期"); } securityUser.setUid(user.getUid()); securityUser.setUsername(user.getUsername()); return securityUser; } } ``` - 最后WebSecurityConfig配置如下 ``` @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //将oauth开头的请求以及/rsa/publicKey请求不拦截 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .antMatchers("/rsa/publicKey").permitAll() .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated(); } //AuthenticationManager 授权管理器 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //密码加密的方式 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 这样我们就完成了oauth2的项目,启动ants-oauth2-auth服务,并请求url获取token 参数说明如下: ​ username:为需要授权的用户名,就是UserServiceImpl 中的配置 ​ password:为用户名的密码 ​ grant_type: 授权类型为密码,OAuth2ServerConfig类中的authorizedGrantTypes ​ client_id: OAuth2ServerConfig的withClient配置 ​ scope: OAuth2ServerConfig的scopes配置 ​ client_secret: 客户端的密码,OAuth2ServerConfig的secret配置,如果不想使用明文,可以前端使用加密配置,后端secret使用解密配置即可。 ![image-20250730144057511](./img/image-20250730144057511.png) ##### 4.7 oauth2和gateway集成 ###### 4.7.1 基础文件配置 ​ a.新建ants-oauth2-gateway项目,子项目pom.xml配置如下 ``` 4.0.0 com.ants ants-parent 1.0.0 ants-oauth2-gateway 8 8 UTF-8 org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-webflux org.springframework.security spring-security-oauth2-resource-server org.springframework.security spring-security-oauth2-client org.springframework.security spring-security-oauth2-jose org.springframework.security spring-security-config com.nimbusds nimbus-jose-jwt org.springframework.boot spring-boot-starter-data-redis 2.7.18 io.netty netty-common 4.1.118.Final io.netty netty-handler 4.1.118.Final com.alibaba fastjson 1.2.83 ``` ​ b.application.yml配置如下 ``` spring: application: name: ants-oauth2-gateway main: allow-circular-references: true profiles: active: dev ``` ​ c.application-dev.yml配置如下 ``` nacos: url: localhost:8848 #端口号 server: port: 7000 spring: cloud: nacos: discovery: server-addr: ${nacos.url} #服务的命名空间 namespace: ants-parent-${spring.profiles.active} #服务的所属组 group: ANTS_GROUP gateway: discovery: locator: #开启从注册中心动态创建路由的功能 enabled: true ##使用小写服务名,默认是大写 lower-case-service-id: true # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] routes: - id: ants-product-route uri: lb://ants-product # 请求要转发到的地址 predicates: # 断言(就是路由转发要满足的条件) - Path=/ants-product-dev/** # 当请求路径满足Path指定的规则时,才进行路由转发 - id: ants-oauth2-route uri: lb://ants-oauth2 predicates: - Path=/** security: oauth2: resourceserver: jwt: jwk-set-uri: 'http://localhost:8083/rsa/publicKey' #配置RSA的公钥访问地址 ``` ###### 4.7.2 程序设计 ​ a.上述基础配置文件配置好后,需要在ants-oauth2-auth项目中,编写RSA钥匙库地址,供gateway访问 ``` @RestController public class KeyPairController { @Resource private KeyPair keyPair; @GetMapping("/rsa/publicKey") public Map getKey() { RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAKey key = new RSAKey.Builder(publicKey).build(); return new JWKSet(key).toJSONObject(); } } ``` ​ b.编写请求授权管理器类RestfulAuthorizationManager ``` @Component public class RestfulAuthorizationManager implements ReactiveAuthorizationManager { Logger logger = LoggerFactory.getLogger(RestfulAuthorizationManager.class); @Autowired private RedisTemplate redisTemplate; private static final String GATEWAY_RESOURCE_ROLE_KEY = "gateway:resource"; @Override public Mono check(Mono authentication, AuthorizationContext authorizationContext) { ServerWebExchange exchange = authorizationContext.getExchange(); ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); //从redis中获取事先已经配置好的授权路由 Object obj = redisTemplate.opsForList().range(GATEWAY_RESOURCE_ROLE_KEY,0,-1); // 不在权限范围内的url,全部拒绝 if (obj == null) { return Mono.just(new AuthorizationDecision(false)); } String redisMapStr = JSON.toJSONString(obj); List list = JSON.parseArray(redisMapStr); List authorities= new ArrayList<>(); for (Object o : list){ Map map = JSONObject.parseObject(JSON.toJSONString(o)); for (Map.Entry entity: map.entrySet()){ //带通配符的可以使用这个进行匹配 if (checkPermit(path,entity.getKey())){ authorities = JSON.parseArray(JSON.toJSONString(entity.getValue()),String.class); } } } logger.info("访问路径:[{}],所需要的权限是:[{}]", path, authorities); // option 请求,全部放行 if (request.getMethod() == HttpMethod.OPTIONS) { return Mono.just(new AuthorizationDecision(true)); } List finalAuthorities = authorities; return authentication .filter(Authentication::isAuthenticated) .filter(a -> a instanceof JwtAuthenticationToken) .cast(JwtAuthenticationToken.class) .doOnNext(token -> { System.out.println(token.getToken().getHeaders()); System.out.println(token.getTokenAttributes()); }) .flatMapIterable(Authentication::getAuthorities) .map(GrantedAuthority::getAuthority) .any(finalAuthorities::contains) .map(AuthorizationDecision::new) .defaultIfEmpty(new AuthorizationDecision(false)); } /** * 通过通配符校验 * @param path * @return */ private boolean checkPermit(String path,String redisPath) { AntPathMatcher pathMatcher = new AntPathMatcher(); return pathMatcher.match(redisPath,path); } } ``` ​ 因为我们是从redis中获取访问那种路由需要哪种权限的,因此我们需要在auth项目中提前配置好权限,为了方便我们在程序启动时往redis中,提前插入了路由权限(这部分可根据需要自定义处理,并且在RestfulAuthorizationManager中重新编写逻辑),以下是我的代码 ``` @Service public class ResourceServiceImpl { private static final String GATEWAY_RESOURCE_ROLE_KEY = "gateway:resource"; private Map> resourceRolesMap; @Autowired private RedisUtils redisUtils; //项目启动时往redis中手动插入,真正的环境时需要根据需求编写 //一般是在权限web页面配置,将配置好的路由权限插入到数据库中持久化处理,然后在ants-oAuth2-gateway项目中的RestfulAuthorizationManager中读取 //是从redis中读取还是直接从数据库中读取,根据需求编写 @PostConstruct public void initData() { resourceRolesMap = new TreeMap<>(); resourceRolesMap.put("/ants-product-dev/product/**", Arrays.asList("ROLE_ADMIN")); resourceRolesMap.put("/ants-product-dev/test/**", Arrays.asList("ROLE_TEST")); redisUtils.rightPush(GATEWAY_RESOURCE_ROLE_KEY,resourceRolesMap,0); } } ``` ​ c.编写资源服务ResourceServerConfig ``` @Configuration @EnableWebFluxSecurity public class ResourceServerConfig { /*** * 白名单,直接放行的 */ private final String[] permissiveUrlList = { "/oauth/**", "/ants-product-dev/product/testOauth2" }; @Autowired private RestfulAuthorizationManager restfulAuthorizationManager; /** * 未授权 * @return */ @Bean public RestfulAccessDeniedHandler restfulAccessDeniedHandler(){ return new RestfulAccessDeniedHandler(); } /** * 未认证 * @return */ @Bean public RestfulAuthenticationEntryPoint restfulAuthenticationEntryPoint(){ return new RestfulAuthenticationEntryPoint(); } @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.oauth2ResourceServer().jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()); http.authorizeExchange() .pathMatchers(permissiveUrlList).permitAll()//白名单配置 .anyExchange().access(restfulAuthorizationManager)//鉴权管理器配置 .and().exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler())//处理未授权 .authenticationEntryPoint(restfulAuthenticationEntryPoint())//处理未认证 .and().csrf().disable(); return http.build(); } @Bean public Converter> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); //取消权限的前缀,默认会加上SCOPE_ jwtGrantedAuthoritiesConverter.setAuthorityPrefix(""); //从那个字段中获取权限 jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities"); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } ``` ​ 在上述代码中RestfulAccessDeniedHandler和RestfulAuthenticationEntryPoint是我们自定义的未授权以及未认证类,可以使用默认不自定义编写,这里为了能够更加方便的处理,我都做了自定义处理,以下是代码。 ``` package com.ants.gateway.handle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; /** * @author 蚂蚁会花呗 * @create 2025/5/28 15:26 * * 用户没有权限时返回的数据 */ public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler { private Logger logger = LoggerFactory.getLogger(RestfulAccessDeniedHandler.class); @Override public Mono handle(ServerWebExchange exchange, AccessDeniedException denied) { ServerHttpRequest request = exchange.getRequest(); return exchange.getPrincipal() .doOnNext(principal -> logger.info("用户:[{}]没有访问:[{}]的权限.", principal.getName(), request.getURI())) .flatMap(principal -> { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.FORBIDDEN); String body = "{\"code\":403,\"msg\":\"您无权限访问\"}"; DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)) .doOnError(error -> DataBufferUtils.release(buffer)); }); } } ``` ``` package com.ants.gateway.handle; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; /** * @author 蚂蚁会花呗 * @create 2025/5/28 15:24 * 认证失败返回的数据 * */ public class RestfulAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @Override public Mono commence(ServerWebExchange exchange, AuthenticationException ex) { return Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> { response.setStatusCode(HttpStatus.UNAUTHORIZED); String body = "{\"code\":401,\"msg\":\"token不合法或过期\"}"; DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)) .doOnError(error -> DataBufferUtils.release(buffer)); }); } } ``` d.编写过滤器,用于拦截请求中是否携带token ``` @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private static Logger logger = LoggerFactory.getLogger(AuthGlobalFilter.class); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if (StringUtils.isEmpty(token)) { return chain.filter(exchange); } try { //从token中解析用户信息并设置到Header中去 String realToken = token.replace("Bearer ", ""); JWSObject jwsObject = JWSObject.parse(realToken); String userStr = jwsObject.getPayload().toString(); logger.info("AuthGlobalFilter.filter() user:{}",userStr); ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build(); exchange = exchange.mutate().request(request).build(); } catch (ParseException e) { e.printStackTrace(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } ``` ###### 4.7.3 测试 ​ 启动ants-oauth2-auth、ants-oauth2-gateway、ants-product项目。首先测试设置了白名单的情况,我们在ResourceServerConfig类中,设置了/ants-product-dev/product/testOauth2请求为白名单,我们通过统一网关进行访问,发现可以正常通过请求。 ![image-20250730153715193](./img/image-20250730153715193.png) ​ 当我们使用统一网关进行访问其他路由时,提示我们 401,没有授权,token不合法或过期。(测试的时候没有携带token,如果携带了,但是不正确,同样不能访问) ![image-20250730154225105](./img/image-20250730154225105.png) ​ 正确的方式时,首先通过统一网关获取token,将返回的access_token复制到请求头中,再次访问即可。 - ​ 首先使用macro用户登录,获取access_token ![image-20250730155018617](./img/image-20250730155018617.png) - ​ 将获取access_token作为请求头携带 ![image-20250730155230922](./img/image-20250730155230922.png) ​ 最后验证不同的路由需要不同的权限才能访问的逻辑,我们在ants-oauth2-auth项目中的ResourceServiceImpl类设置了两个路由,分别是/ants-product-dev/product/** 访问时需要ROLE_ADMIN权限, /ants-product-dev/test/** 时需要ROLE_TEST权限。同样是在该项目中,UserServiceImpl类里,我们设置了两个用户,一个是macro,具有ROLE_ADMIN权限,andy用户具有ROLE_TEST和ROLE_USER。我们使用macro登录获取的access_token能够正常访问/ants-product-dev/product/1接口,但是不能访问 /ants-product-dev/test/get接口,提示我们没有权限。只有当我们使用andy用户登录后获取的access_token才能正常访问,以下是验证。 - 使用macro登录获取到的token访问时 ![image-20250730160219253](./img/image-20250730160219253.png) - 使用andy登录获取到的token访问时 ![image-20250730160424547](./img/image-20250730160424547.png) 至此分布式系统框架的nacos、Feign、oauth2以及gateway都已搭建完成,下一步将继续集成Sentinel--服务容错 ##### 4.8 集成Sentinel--服务容错 ​ 在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。 ​ Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。 Sentinel 具有以下特征: - ​ 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。 - ​ 完备的实时监控:Sentinel 提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。 - ​ 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 SpringCloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。 - ​ 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。 ###### 4.8.1 加入依赖 ​ 我们以ants-order为例,在pom.xml中加入以下配置 ``` com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` ​ application-dev.yml增加配置 ``` - optional:nacos:${spring.application.name}-sentinel-${spring.profiles.active}.yml # sentinel开发环境配置 ``` ​ nacos控制台增加ants-order-sentinel-dev.yml配置,组和之前配置的一样,都是ANTS-GROUP,内容如下 ``` spring: cloud: sentinel: transport: #跟控制台交流的端口,随意指定一个未使用的端口即可 port: 9999 #控制台服务的地址 dashboard: localhost:8868 ``` ###### 4.8.2 安装sentinel控制台 ​ 从github上下载https://github.com/alibaba/Sentinel/releases,这里我选择的是1.8.6版本。直接将jar包下载,然后使用以下命令启动,启动成功后,通过浏览器访问,默认用户名密码是sentinel/sentinel ``` #指定启动端口号为8868 java -jar sentinel-dashboard-1.8.6.jar --server.port=8868 ``` ###### 4.8.3 编写代码 ​ 在ants-order项目中编写接口服务,使用注解测试流量监控,同时需要在ants-oauth2-gateway项目中添加白名单/ants-order-dev/order/hello,其中@SentinelResource注解中的blockHandler是sentinel对资源进行限流或者熔断时的处理规则,fallback是资源抛出异常后,对异常捕获后的处理,这里我们使用blockHandler对资源进行限流,当请求大于设置时执行blockHandler指定的方法 ``` @GetMapping("/hello") @SentinelResource(value = "helloValue", blockHandler = "handleException") public String helloWang() { // 模拟业务逻辑,此处仅为示例,实际业务逻辑应放在这里。 return "Hello, Sentinel!这是正常访问"; } // 定义一个异常处理方法,当流量被拦截时会调用此方法。 public String handleException(BlockException ex) { return "哈哈哈"; } ``` ###### 4.8.4登录控制台 ​ 查看Sentinel,但是并无监控信息。这是因为Sentinel是一款 `懒加载` 式的架构,需要对接口进行请求后,才能获得相关监控信息。这里我们请求接口http://192.168.99.72:7000/ants-order-dev/order/hello, 能够正常返回数据,在控制台的蔟点链路中有我们设置的helloValue控流名称。 ![image-20250730173357233](./img/image-20250730173357233.png) ![image-20250730173507800](./img/image-20250730173507800.png) ​ 点击helloValue的 流控按钮,新增流控规则,这里我们设置为1,表示每秒的并发数为1,保存后,我们正常访问能够返回,当我们在浏览器快速的刷新时,即可发现,返回到了我们的handleException方法。 ![image-20250730173739347](./img/image-20250730173739347.png) ![image-20250730173922283](./img/image-20250730173922283.png) 这样我们就完成了Sentinel的配置,关于Sentinel的更多配置以及规则持久化请自行查阅文档。 #### 5.结语 以上就是SpringCloud Alibab、Feign、OAuth2、gateway、以及Sentinel限流的所有基础框架的搭建,感谢阅读。