微服务开发与实战Day04

一、网关路由

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

在SpringCloud中网关的实现包括两种:

1. 快速入门

Spring Cloud Gateway

步骤:

①新建hm-gateway模块

②引入依赖pom.xml(hm-gateway)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>hmall</artifactId><groupId>com.heima</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>hm-gateway</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><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></project>

③新建启动类GatewayApplication

package com.hmall.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}

④添加配置文件application.yaml 以及 从hm-service中拷贝SearchController到item-service

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.126.151:8848gateway:routes:- id: item-service # 路由规则id,自定义,唯一uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务- Path=/items/**,/search/** # 这里是以请求路径作为判断规则- id: cart-serviceuri: lb://cart-servicepredicates:- Path=/carts/**- id: user-serviceuri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: trade-serviceuri: lb://trade-servicepredicates:- Path=/orders/**- id: pay-serviceuri: lb://pay-servicepredicates:- Path=/pay-orders/**

⑤同时启动这7个服务

测试

2. 路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标识
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

2.1 路由断言

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

名称说明示例

After

是某个时间点后的请求

- After=2037-01-20T17:42:47.789-07:00[America/Denver]

Before

是某个时间点之前的请求

- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]

Between

是某两个时间点之前的请求

- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]

Cookie

请求必须包含某些cookie

- Cookie=chocolate, ch.p

Header

请求必须包含某些header

- Header=X-Request-Id, \d+

Host

请求必须是访问某个host(域名)

- Host=**.somehost.org,**.anotherhost.org

Method

请求方式必须是指定方式

- Method=GET,POST

Path

请求路径必须符合指定规则

- Path=/red/{segment},/blue/**

Query

请求参数必须包含指定参数

- Query=name, Jack或者- Query=name

RemoteAddr

请求者的ip必须是指定范围

- RemoteAddr=192.168.1.1/24

Weight

权重处理

- Weight=group1,2
XForwarded Remote Addr基于请求的来源IP做判断- XForwardedRemoteAddr=192.168.1.1/24

2.2 路由过滤器

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

名称说明示例
AddRequestHeader给当前请求添加一个请求头AddrequestHeader=headerName,headerValue
RemoveRequestHeader移除请求中一个请求头RemoveRequestHeader=headerName
AddResponseHeader给响应结果中添加一个响应头AddResponseHeader=headerName,headerValue
RemoveResponseHeader从响应结果中移除一个响应头RemoveResponseHeader=headerName
RewritePath请求路径重写RewritePath=/red/?(?<segment>.*), /$\{segment}
StripPrefix去除请求路径中的N段前缀StripPrefix=1,则路径/a/b转发时只保留/b
... ...

二、网关登录校验

1. 如何在网关转发之前做登录校验?

2. 网关如何将用户信息传递给微服务?

3. 如何在微服务之间传递用户信息?

网关请求处理流程

1. 自定义过滤器

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

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

两种过滤器的过滤方法签名完全一致:

1.1 自定义过滤器 GlobalFilter

自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可

步骤:hm-gateway模块

①MyGlobalFilter

package com.hmall.gateway.filter;import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@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);}@Overridepublic int getOrder () {return 0;}
}

②application.yaml

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.126.151:8848gateway:routes:# ... ...default-filters:- AddRequestHeader=truth, anyone long-press like button will be rich

1.2 自定义过滤器 GatewayFilter

自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory,示例如下:

步骤:hm-gateway模块

①PrintAnyGatewayFilterFactory

package com.hmall.gateway.filter;import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@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);}
}

②application.yaml

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.126.151:8848gateway:routes:# ... ...default-filters:- AddRequestHeader=truth, anyone long-press like button will be rich- PrintAny

PrintAnyGatewayFilterFactory

package com.hmall.gateway.filter;import lombok.Data;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String a = config.getA();String b = config.getB();String c = config.getC();System.out.println("a = " + a);System.out.println("b = " + b);System.out.println("c = " + c);System.out.println("print any filter running");return chain.filter(exchange);}}, 1);}// 自定义配置属性,成员变量名称很重要,下面会用到@Datapublic static class Config {private String a;private String b;private String c;}// 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取@Overridepublic List<String> shortcutFieldOrder() {return List.of("a", "b", "c");}// 将config字节码传递给父类,父类负责帮我们读取yaml配置public PrintAnyGatewayFilterFactory() {super(Config.class);}
}

application.yaml

server:port: 8080
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.126.151:8848gateway:routes:# ... ...default-filters:- AddRequestHeader=truth, anyone long-press like button will be rich- PrintAny=1,2,3

2. 实现登录校验

需求:在网关中基于过滤器实现登录校验功能

提示:黑马商城是基于JWT实现的登录校验,目前相关功能在hm-service模块。我们可以将其中的JWT工具拷贝到gateway模块,然后基于GlobalFilter来实现登录校验。

步骤:hm-gateway模块

①从hm-service拷贝以下登录校验相关的文件到hm-gateway模块

②在AuthProperties里加上component注解

③复制相关配置到application.yaml

hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**- /hi

④AuthGlobalFilter

package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
public class AuthGloabalFilter 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;}
}

3. 网关传递用户

3.1 在网关的登录校验过滤器中,把获取到的用户写入请求头

需求:修改gateway模块中的登录校验拦截器,在校验成功后保存用户到下游请求的请求头中。

提示:要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:

exchange.mutate()  // mutate就是对下游请求做更改.request(builder -> builder.header("user-info", userInfo)).build();

①AuthGlobalFilter

package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
public class AuthGloabalFilter 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();}// 5. 传递用户信息String userInfo = userId.toString();ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info", userInfo)).build();// 6. 放行return chain.filter(swe);}// ... ...
}

②cart-service模块中的CartController

@ApiOperation("查询购物车列表")
@GetMapping
public List<CartVO> queryMyCarts(@RequestHeader(value="user-info", required = false)String userInfo){System.out.println("userInfo = " + userInfo);return cartService.queryMyCarts();
}

③重启CartApplication和GatewayApplication

3.2 在hm-common中编写SpringMVC拦截器,获取登录用户

需求:由于每个微服务都可能有获取登录用户的需求,因此我们直接在hm-common模块定义拦截器,这样微服务只需要引入依赖即可生效,无需重复编写。

①在hm-common模块新增一个UserInfoInterceptor

package com.hmall.common.interceptors;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取登录用户信息String userInfo = request.getHeader("user-info");// 2. 判断是否获取了用户,如果有,存入TreadLocalif(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();}
}

②在hm-common模块下新增MvcConfig

package com.hmall.common.config;import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
// 只在微服务生效,在网关里不要生效
@ConditionalOnClass(DispatcherServlet.class)  // 确保只有在Web环境中(即存在DispatcherServlet的情况下)才会生效
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}

③cart-service模块下的CartServiceImpl(获取用户信息)

    @Overridepublic List<CartVO> queryMyCarts() {// 1.查询我的购物车列表List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();if (CollUtils.isEmpty(carts)) {return CollUtils.emptyList();}// 2.转换VOList<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);// 3.处理VO中的商品信息handleCartItems(vos);// 4.返回return vos;}

④在hm-common模块中的spring.factories中添加如下:(SpringBoot的自动装配原理)

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

⑤重启GatewayApplication和CartApplication进行测试

http://localhost:18080/cart.html

4. OpenFeign传递用户

微服务项目中的很多业务要多个微服务共同合作完成,而这个过程中也需要传递登录用户信息,例如:

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

其中的RequestTemplate类中提供了一些方法可以让我们修改请求头:

步骤:hm-api模块

①pom.xml

<!--hm-common-->
<dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version>
</dependency>

②DefaultFeignConfig

package com.hmall.api.config;import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;public class DefaultFeignConfig {@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {Long userId = UserContext.getUser();if(userId != null) {template.header("user-info", userId.toString());}}};}
}

三、配置管理

  • 微服务重复配置过多,维护成本高
  • 业务配置经常变动,每次修改都要重启服务
  • 网关路由配置写死,如果变更要重启网关

1. 配置共享

1.1 添加配置到Nacos

添加一些共享配置到Nacos中,包括:jdbc、MyBatisPlus、日志、Swagger、OpenFeign等配置

http://192.168.126.151:8848/nacos/#/configurationManagement?serverId=center&group=&dataId=&namespace=&appName=&pageSize=&pageNo=

spring:datasource:url: jdbc:mysql://${hm.db.host:192.168.126.151}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: ${hm.db.un:root}password: ${hm.db.pw:123456}
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandlerglobal-config:db-config:update-strategy: not_nullid-type: auto

logging:level:com.hmall: debugpattern:dateformat: HH:mm:ss:SSSfile:path: "logs/${spring.application.name}"

knife4j:enable: trueopenapi:title: ${hm.swagger.title:黑马商城接口文档}description: ${hm.swagger.desc:黑马商城接口文档}email: zhanghuyi@itcast.cnconcat: 虎哥url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- ${hm.swagger.package}

1.2 拉取共享配置

基于NacosConfig拉取共享配置代替微服务的本地配置

步骤:cart-service模块

①引入依赖 pom.xml(cart-service)

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

②新建boorstrap.yaml  记得更改为自己的nacos地址

spring:application:name: cart-service # 服务名称profiles:active: devcloud:nacos:server-addr: 192.168.126.151 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- data-id: shared-jdbc.yaml # 共享mybatis配置- data-id: shared-log.yaml # 共享日志配置- data-id: shared-swagger.yaml # 共享日志配置

③application.yaml

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

④重启CartApplication

注:其他模块也可以进行同样的配置

2. 配置热更新

配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效

前提条件:

①nacos中要有一个与微服务名有关的配置文件

②微服务中要以特定方式读取需要热更新的配置属性

2.1 实现购物车添加商品上限的配置热更新

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

步骤:

①cart-service模块下新建CartProperties

package com.hmall.cart.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {private Integer maxItems;
}

②CartServiceImpl

package com.hmall.cart.service.impl;import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.cart.config.CartProperties;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.mapper.CartMapper;
import com.hmall.cart.service.ICartService;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;/*** <p>* 订单详情表 服务实现类* </p>** @author 虎哥* @since 2023-05-05*/
@Service
@RequiredArgsConstructor // 必备参数的构造函数
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {private final ItemClient itemClient;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()));}}// ... ...
}

③新增配置管理

hm:cart:maxItems: 1

④重启CartApplication

⑤maxItems修改为10 (无需重启,即刻生效)

3. 动态路由

要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。

我们需要完成两件事情:

①监听Nacos配置变更的消息

②当配置变更时,将最新的路由信息更新到网关路由表

3.1 监听Nacos配置

监听Nacos配置变更可以参考官方文档:Java SDK

步骤:hm-gateway模块

①在pom.xml(hm-gateway)

<!--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: gateway # 服务名称profiles:active: devcloud:nacos:server-addr: 192.168.126.151 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- data-id: shared-log.yaml # 共享日志配置

③修改application.yaml

server:port: 8080
hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**- /hi

④新增routers/DynamicRouteLoader

package com.hmall.gateway.routers;import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.concurrent.Executor;@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouterLoader {private final NacosConfigManager nacosConfigManager;private final String dataId = "gateway-routes.json";private final String group = "DEFAULT_GROUP";@PostConstructpublic void initRouterConfigListener() throws NacosException {// 1. 项目启动时,先拉取一次配置,并且添加配置监听器String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {// 2. 监听到配置变更,需要去更新路由表updateConfigInfo(configInfo);}});// 3. 第一次读取到配置,也需要更新到路由表updateConfigInfo(configInfo);}public void updateConfigInfo(String configInfo) {// TODO}
}

3.2 更新路由表

监听到路由信息后,可以利用RouteDefinitionWriter来更新路由表

3.3 路由配置语法

为了方便解析从Nacos读取道德路由配置,推荐使用json格式的路由配置,模板如下:

步骤:hm-gateway

①DynamicRouteLoader

package com.hmall.gateway.routers;import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {private final NacosConfigManager nacosConfigManager;private final RouteDefinitionWriter writer;private final String dataId = "gateway-routes.json";private final String group = "DEFAULT_GROUP";private final Set<String> routeIds = new HashSet<>();@PostConstructpublic void initRouterConfigListener() throws NacosException {// 1. 项目启动时,先拉取一次配置,并且添加配置监听器String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {// 2. 监听到配置变更,需要去更新路由表updateConfigInfo(configInfo);}});// 3. 第一次读取到配置,也需要更新到路由表updateConfigInfo(configInfo);}public void updateConfigInfo(String configInfo) {log.debug("监听到路由配置信息:" + configInfo);// 1. 解析配置信息,转为RouterDefinitionList<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);// 2. 删除旧的路由表for (String routeId : routeIds) {writer.delete(Mono.just(routeId)).subscribe();}routeIds.clear();// 3. 更新路由表for (RouteDefinition routeDefinition : routeDefinitions) {// 3.1 更新路由表writer.save(Mono.just(routeDefinition)).subscribe();// 3.2 记录路由id,便于下一次更新时删除routeIds.add(routeDefinition.getId());}}
}

②动态添加路由信息

[{"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"}
]

③重启GateApplication测试

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

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

相关文章

解锁俄罗斯市场:如何选择优质的俄罗斯云服务器

在当前云计算市场上&#xff0c;很多大型的云厂商并没有俄罗斯服务器的云节点&#xff0c;这给许多企业在拓展海外业务时带来了一定的困扰。然而&#xff0c;俄罗斯作为一个经济发展迅速的国家&#xff0c;其市场潜力不可忽视。因此&#xff0c;选择一台优质的俄罗斯云服务器成…

【MySQL】(基础篇三) —— 创建数据库和表

管理数据库和表 管理数据库 创建数据库 在MySQL中&#xff0c;创建数据库的SQL命令相对简单&#xff0c;基本语法如下&#xff1a; CREATE DATABASE 数据库名;如果你想避免在尝试创建已经存在的数据库时出现错误&#xff0c;可以添加 IF NOT EXISTS 子句&#xff0c;这样如…

数据结构(C):二叉树前中后序和层序详解及代码实现及深度刨析

目录 &#x1f31e;0.前言 &#x1f688;1.二叉树链式结构的代码是实现 &#x1f688;2.二叉树的遍历及代码实现和深度刨析代码 &#x1f69d;2.1前序遍历 ✈️2.1.1前序遍历的理解 ✈️2.1.2前序代码的实现 ✈️2.1.3前序代码的深度解剖 &#x1f69d;2.2中序遍历 ✈…

计算机网络:数据链路层 - 扩展的以太网

计算机网络&#xff1a;数据链路层 - 扩展的以太网 集线器交换机自学习算法单点故障 集线器 这是以前常见的总线型以太网&#xff0c;他最初使用粗铜轴电缆作为传输媒体&#xff0c;后来演进到使用价格相对便宜的细铜轴电缆。 后来&#xff0c;以太网发展出来了一种使用大规模…

AI菜鸟向前飞 — LangChain系列之十七 - 剖析AgentExecutor

AgentExecutor 顾名思义&#xff0c;Agent执行器&#xff0c;本篇先简单看看LangChain是如何实现的。 先回顾 AI菜鸟向前飞 — LangChain系列之十四 - Agent系列&#xff1a;从现象看机制&#xff08;上篇&#xff09; AI菜鸟向前飞 — LangChain系列之十五 - Agent系列&#…

Springboot使用webupload大文件分片上传(包含前后端源码)

Springboot使用webupload大文件分片上传&#xff08;包含源码&#xff09; 1. 实现效果1.1 分片上传效果图1.2 分片上传技术介绍 2. 分片上传前端实现2.1 什么是WebUploader&#xff1f;功能特点接口说明事件APIHook 机制 2.2 前端代码实现2.2.1&#xff08;不推荐&#xff09;…

计算机组成原理之计算机系统层次结构

目录 计算机系统层次结构 复习提示 1.计算机系统的组成 2.计算机硬件 2.1冯诺依曼机基本思想 2.1.1冯诺依曼计算机的特点 2.2计算机的功能部件 2.2.1MAR 和 MDR 位数的概念和计算 3.计算机软件 3.1系统软件和应用软件 3.2三个级别的语言 3.2.1三种机器语言的特点 3…

★pwn 24.04环境搭建保姆级教程★

★pwn 24.04环境搭建保姆级教程★ &#x1f338;前言&#x1f33a;Ubuntu 24.04虚拟机&#x1f337;VM&#x1f337;Ubuntu 24.04镜像 &#x1f33a;工具&#x1f337;可能出现的git clone错误&#x1f337;复制粘贴问题&#x1f337;攻击&#x1f337;编题 &#x1f33a;美化&…

【AI大模型】Transformers大模型库(五):AutoModel、Model Head及查看模型结构

目录​​​​​​​ 一、引言 二、自动模型类&#xff08;AutoModel&#xff09; 2.1 概述 2.2 Model Head&#xff08;模型头&#xff09; 2.3 代码示例 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#xff0c;为huggingface上数以万计的预…

使用 Keras 的 Stable Diffusion 实现高性能文生图

前言 在本文中&#xff0c;我们将使用基于 KerasCV 实现的 [Stable Diffusion] 模型进行图像生成&#xff0c;这是由 stable.ai 开发的文本生成图像的多模态模型。 Stable Diffusion 是一种功能强大的开源的文本到图像生成模型。虽然市场上存在多种开源实现可以让用户根据文本…

【会议征稿,IEEE出版】第三届能源与电力系统国际学术会议 (ICEEPS 2024,7月14-16)

如今&#xff0c;全球能源行业正面临着前所未有的挑战。一方面&#xff0c;加快向清洁、可再生能源转型是遏制能源环境污染问题的最佳途径之一&#xff1b;另一方面&#xff0c;电力系统中新能源发电、人工智能技术、电力电子装备等被广泛应用和期待&#xff0c;以提高能源可持…

transformer - 注意力机制

Transformer 的注意力机制 Transformer 是一种用于自然语言处理任务的模型架构&#xff0c;依赖于注意力机制来实现高效的序列建模。注意力机制允许模型在处理一个位置的表示时&#xff0c;考虑输入序列中所有其他位置的信息&#xff0c;而不仅仅是前面的几个位置。这种机制能…

ATTCK红队评估(五)

环境搭建 靶场拓扑图&#xff1a; 靶机下载地址: 漏洞详情 外网信息收集 确定目标靶机地址&#xff1a; 发现主机192.168.135.150主机是本次攻击的目标地址。探测靶机开放的端口信息&#xff1a; 目标靶机开放了两个端口&#xff1a;80、3306&#xff0c;那没什么意外的话就是…

每天壁纸不重样~下载必应每日图片

下载必应每日图片 必应不知道你用过没有你下载过必应的图片没有你又没搜索过桌面图片你是不是安装过桌面图片软件你是不是为找一个好看的图片下载过很多桌面软件 必应每日图片 必应每天都会有一张不同的风景图片&#xff0c;画质清晰&#xff0c;而且不收费可以下载使用 但…

重生之我要精通JAVA--第八周笔记

文章目录 多线程线程的状态线程池自定义线程池最大并行数多线程小练习 网络编程BS架构优缺点CS架构优缺点三要素IP特殊IP常用的CMD命令 InetAddress类端口号协议UDP协议&#xff08;重点&#xff09;UDP三种通信方式 TCP协议&#xff08;重点&#xff09;三次握手四次挥手 反射…

sqlmap直接嗦 dnslog注入 sqllibs第8关

dnslog注入是解决注入的时候没有回显的情况&#xff0c;通过dns外带来进行得到我们想要的数据。 我们是用了dns解析的时候会留下记录&#xff0c;这时候就可以看见我们想要的内容。 这个时候我们还要了解unc路径以及一个函数load_file()以及concat来进行注入。看看我的笔记 unc…

sqli-labs 靶场 less-8、9、10 第八关到第十关详解:布尔注入,时间注入

SQLi-Labs是一个用于学习和练习SQL注入漏洞的开源应用程序。通过它&#xff0c;我们可以学习如何识别和利用不同类型的SQL注入漏洞&#xff0c;并了解如何修复和防范这些漏洞。Less 8 SQLI DUMB SERIES-8判断注入点 当输入id为1时正常显示&#xff1a; 加上单引号就报错了 …

零基础非科班也能掌握的C语言知识19 动态内存管理

动态内存管理 1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free 3.calloc和realloc3.1 calloc3.2realloc 4.常见的动态内存的错误4.1对NULL指针的解引用操作4.2对动态开辟空间的越界访问4.3对非动态内存开辟的空间free4.4使用free释放⼀块动态开辟内存的⼀部分4.5对同…

在Anaconda中安装keras-contrib库

文章目录 1. 有git2. 无git2.1 步骤12.2 步骤22.3 步骤3 1. 有git 如果环境里有git&#xff0c;直接运行以下命令&#xff1a; pip install githttps://www.github.com/farizrahman4u/keras-contrib.git2. 无git 2.1 步骤1 打开网址&#xff1a;https://github.com/keras-tea…

Vue3【十四】watchEffect自动监视多个数据实现,不用明确指出监视哪个数据

Vue3【十四】watchEffect自动监视多个数据实现&#xff0c;不用明确指出监视哪个数据 Vue3【十四】watchEffect自动监视多个数据实现&#xff0c;不用明确指出监视哪个数据 进入立即执行一次&#xff0c;并监视数据变化 案例截图 目录结构 代码 Person.vue <template>&…