微服务与网关

什么是网关

背景

单体项目中,前端只用访问指定的一个端口8080,就可以得到任何想要的数据

在这里插入图片描述

微服务项目中,ip是不断变化的,端口是多个的

在这里插入图片描述

解决方案:网关

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

在这里插入图片描述
在这里插入图片描述

前段还是访问之前的端口8080即可

在这里插入图片描述

后端对于前端来说是透明的

在这里插入图片描述

网关的两种实现

网关是一种开发规范,实际的实现有两种:

  • 官方
  • 网飞公司

在这里插入图片描述

步骤

在这里插入图片描述

maven坐标

在hm-gateway模块的pom.xml文件中引入依赖:

<?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>
    </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>

启动类

在hm-gateway模块的com.hmall.gateway包下新建一个启动类:

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);
    }
}

测试

启动GatewayApplication,以 http://localhost:8080 拼接微服务接口路径来测试。例如:
http://localhost:8080/items/page?pageNo=1&pageSize=1

在这里插入图片描述

路由属性

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

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

在这里插入图片描述

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

在这里插入图片描述

参考地址:https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories/addrequestheader-factory.html

设置请求头的filter

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.88.130:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
          filters: # 过滤器,可以对请求进行一些处理,比如添加请求头,添加请求参数等
            - AddRequestHeader=X-Request-red, blue # 添加请求头 X-Request-red,值为 blue
    @ApiOperation("分页查询商品")
    @GetMapping("/page")
    public PageDTO<ItemDTO> queryItemByPage(PageQuery query,
    @RequestHeader(value = "X-Request-red", required = false) String red
    ) {
        // 打印一下请求头
        System.out.println("X-Request-red: " + red);
        // 1.分页查询
        Page<Item> result = itemService.page(query.toMpPage("update_time", false));
        // 2.封装并返回
        return PageDTO.of(result, ItemDTO.class);
    }

在这里插入图片描述

默认路由是全局生效的,对所有的路由都会生效

在这里插入图片描述

网关的登录校验功能

架构图

在这里插入图片描述

  • 校验和授权,JWT校验可以在网关中进行,将相关数据向后传递

网关请求流程

在这里插入图片描述
在这里插入图片描述

网关过滤器

在这里插入图片描述
在这里插入图片描述

全局过滤器GlobalFilter(更常见)

  • 第一个参数:存储的是所有共享的数据
  • 第二个参数:过滤器链,前一个过滤器执行完毕之后,就会紧接着执行下一个过滤器

在这里插入图片描述

不知道如何实现自定义的过滤器,我们可以参考NettyRoutingFilter

  • 不仅实现了过滤器的类,还是实现了一个Ordered的接口

在这里插入图片描述

在这里插入图片描述

在网关中定义一个 过滤器

在这里插入图片描述

  • 实现接口GlobalFilter是必须得
  • 实现接口Ordered 是为了排序
  • 自定义的这个过滤器要在NettyRoutingFilter之前执行

在这里插入图片描述

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.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 {
    public static final String TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // TODO: 模拟登录校验
        // 1. 从请求头中获取token
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst(TOKEN);
        System.out.println("token: " + token);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 这个值是小所有filter的值的,会优先执行
        return 0;
    }

}

网关过滤器GatewayFilter

不用于全局过滤器,不是实现接口,而是继承一个抽象工厂

在这里插入图片描述

路由过滤器的代码

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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        GatewayFilter gatewayFilter = new GatewayFilter() {
            // 这个方法是真正执行过滤逻辑的方法 这个实现全局过滤器简直一模一样
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("print any filter running");
                System.out.println("config: " + config);
                return chain.filter(exchange);
            }
        };
        // 这个是给过滤器设置优先级的,值越小优先级越高
        // 可有可无,不设置默认是0
        return new OrderedGatewayFilter(gatewayFilter, 1);
    }
}


因为路由过滤器不是作用于全局的,需要针对路由进行配置之后才会生效

  • 配置PrintAny 的路由过滤器
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.88.130:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
          filters: # 过滤器,可以对请求进行一些处理,比如添加请求头,添加请求参数等
            - AddRequestHeader=X-Request-red, blue # 添加请求头 X-Request-red,值为 blue
            - PrintAny # 自定义过滤器,打印请求信息 没有任何参数信息

带属性的路由过滤器

在这里插入图片描述

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.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> {
    @Override
    public GatewayFilter apply(Config config) {
        GatewayFilter gatewayFilter = new GatewayFilter() {
            // 这个方法是真正执行过滤逻辑的方法 这个实现全局过滤器简直一模一样
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("print any filter running");
                System.out.println("config.a = " + config.a);
                System.out.println("config.b = " + config.b);
                System.out.println("config.c = " + config.c);
                return chain.filter(exchange);
            }
        };
        // 这个是给过滤器设置优先级的,值越小优先级越高
        // 可有可无,不设置默认是0
        return new OrderedGatewayFilter(gatewayFilter, 1);
    }

    PrintAnyGatewayFilterFactory() {
        super(Config.class);
    }

    @Data
    public static class Config {
        private String a;
        private String b;
        private String c;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return List.of("a", "b", "c");
    }
}

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.88.130:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
          filters: # 过滤器,可以对请求进行一些处理,比如添加请求头,添加请求参数等
            - AddRequestHeader=X-Request-red, blue # 添加请求头 X-Request-red,值为 blue
            - PrintAny=1,2,3 # 自定义过滤器,打印请求参数

实现登录校验

在这里插入图片描述

两个配置文件

  • yaml配置文件
  • 密钥文件

在这里插入图片描述
在这里插入图片描述

配置类,实现自动装配

在这里插入图片描述

安全配置类

在这里插入图片描述

工具类

  • 生成token
  • 解析token

在这里插入图片描述

将配置类转移到gateway服务下

在这里插入图片描述

把jwt工具包也转移过来,报错的话,给AuthProperties加上component注解

在这里插入图片描述

把秘钥相关配置也配置到yaml中

在这里插入图片描述

jwt密钥文件
在这里插入图片描述

在这里插入图片描述

package com.hmall.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    public static final String AUTHORIZATION = "authorization";

    private  final AuthProperties authProperties;

    private final JwtTool jwtTool;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1,获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 2,判断请求是不是需要拦截
        if(isExcludePath(request.getURI().getPath())){
            return chain.filter(exchange);
        }
        // 3. 获取token
        List<String> tokenList = request.getHeaders().get(AUTHORIZATION);
        String token = null;
        if(CollectionUtil.isNotEmpty(tokenList)){
            token = tokenList.get(0);
        }

        // 4. 校验并且解析token
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            // 4.1 如果解析失败,返回401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 到此为止,结束请求
            return response.setComplete();
        }

        // 5. 传递用户信息
        System.out.println("传递用户信息"+userId);
        // 6. 放行
        return chain.filter(exchange);
    }

    /**
     * 判断请求是否需要拦截
     * @param path 请求路径
     * @return 是否需要拦截
     */
    private boolean isExcludePath(String path) {
        return authProperties.getExcludePaths().stream().anyMatch(path::startsWith);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

没有登陆就会直接跳转过来

在这里插入图片描述

网关传递用户信息到微服务

在这里插入图片描述

步骤

在这里插入图片描述
在这里插入图片描述

package com.hmall.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    public static final String AUTHORIZATION = "authorization";
    public static final String HEADER_NAME = "userInfo";

    private  final AuthProperties authProperties;

    private final JwtTool jwtTool;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1,获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 2,判断请求是不是需要拦截
        if(isExcludePath(request.getURI().getPath())){
            return chain.filter(exchange);
        }
        // 3. 获取token
        List<String> tokenList = request.getHeaders().get(AUTHORIZATION);
        String token = null;
        if(CollectionUtil.isNotEmpty(tokenList)){
            token = tokenList.get(0);
        }

        // 4. 校验并且解析token
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            // 4.1 如果解析失败,返回401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 到此为止,结束请求
            return response.setComplete();
        }

        // 5. 传递用户信息
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header(HEADER_NAME, userId.toString()))
                .build();
        // 6. 放行
        return chain.filter(swe);
    }

    /**
     * 判断请求是否需要拦截
     * @param path 请求路径
     * @return 是否需要拦截
     */
    private boolean isExcludePath(String path) {
        return authProperties.getExcludePaths().stream().anyMatch(path::startsWith);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

改写一下cart-service中controller中的方法,看看请求中有没有userinfo

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

在这里插入图片描述

优化一下获取用户信息的方式

这个用户信息,以后肯定很多地方都会用到,不能每次都在controller中写吧。这么多怎么写的过来

在这里插入图片描述

user拦截器

package com.hmall.common.interceptor;

import cn.hutool.core.util.ObjUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头中获取user-info
        Long userInfo = Long.valueOf(request.getHeader("user-info"));
        // 将user-info放入UserContext中
        if (ObjUtil.isNotEmpty(userInfo)) {
            UserContext.setUser(userInfo);
        }
        // 放行
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 请求处理完毕后,清空UserContext
        UserContext.removeUser();
    }
}

mvc拦截器

package com.hmall.common.config;

import com.hmall.common.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 可以不设置拦截路径,因为在默认就是拦截所有路径
        // 而且此拦截器的作用是将请求头中的user-info放入UserContext中,而不是认证
        registry.addInterceptor(new UserInfoInterceptor());
//                .addPathPatterns("/**");
    }
}

但是mvc是在common模块下,但是微服务不再这个包,而且路径名称也不一样

  • 那就一定扫描不到这个config配置类

在这里插入图片描述

想要扫描到,需要借助配置文件spring.factories

在这里插入图片描述
在这里插入图片描述

配置

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

此时会报错FileNotFoundException

在这里插入图片描述

因为gateway的底层不是基于SpringMVC,但是WebMvcConfigurer是springmvc的包类

在这里插入图片描述

给此类的加载配置条件注解

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 可以不设置拦截路径,因为在默认就是拦截所有路径
        // 而且此拦截器的作用是将请求头中的user-info放入UserContext中,而不是认证
        registry.addInterceptor(new UserInfoInterceptor());
//                .addPathPatterns("/**");
    }
}

在这里插入图片描述
在这里插入图片描述

修改cart的购物车查询

在这里插入图片描述

    @Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }

        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);

        // 3.处理VO中的商品信息
        handleCartItems(vos);

        // 4.返回
        return vos;
    }

在这里插入图片描述
在这里插入图片描述

如果登录的时候被拦截了,可以在拦截器中放行一下登录路径

在这里插入图片描述

微服务之间的用户信息传递

  • 前端与微服务之间的用户信息,可以使用全局过滤器传递
  • 微服务之间的用户信息,需要使用openfeign配置类来实现

在这里插入图片描述
在这里插入图片描述

这个配置是给所有的openfeign转发的请求使用的

在这里插入图片描述

public class DefaultFeignConfig {

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public RequestInterceptor userInfoRequestInterceptor() {
        return requestTemplate -> {
            // 传递用户信息
            // 从UserContext中获取用户信息
            // 传递给下游服务
            Long userInfo = UserContext.getUser();
            if (ObjUtil.isNotEmpty(userInfo)) {
                requestTemplate.header("user-info", userInfo.toString());
            }

        };
    }
}

在启动类上加上,openfeign使用这个配置类

在这里插入图片描述
在这里插入图片描述

@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
@MapperScan("com.hmall.trade.mapper")
@SpringBootApplication
public class TradeApplication {
    public static void main(String[] args) {
        SpringApplication.run(TradeApplication.class, args);
    }
}

登录解决方案小结

在这里插入图片描述

配置管理

定义

微服务将服务重新拆分,导致很多配置要重写

在这里插入图片描述

其实nacos不仅是注册中心,还可以是配置中心

在这里插入图片描述

配置管理-共享配置

在这里插入图片描述
在这里插入图片描述

配置jdbc共享配置

打开nacos的地址:http://192.168.88.130:8848/nacos

  • 打开配置列表新建配置id:shared-jdbc.yaml

在这里插入图片描述

只保留jdbc相关的配置

  • 变动配置是可以 进行变量设置的:ip、端口、数据库实例、用户名、密码
spring:
  datasource:
    url: jdbc:mysql://${hm.db.host}:${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.doc:黑马商城购物车接口文档}
    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中的共享配置

在这里插入图片描述
在这里插入图片描述

完整步骤

在这里插入图片描述

上述步骤不能自己做,spring已经做好了相关的配置

在这里插入图片描述

在这里插入图片描述

相关文件

在这里插入图片描述

application.yaml

server:
  port: 8082 # 服务端口 8081  不能和其他服务端口重复


# feign配置
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

hm:
  db:
    database: hm-cart
  swagger:
    title: "购物车服务接口文档"
    package: "com.hmall.cart.controller"

bootstrap.yaml

spring:
  application:
    name: cart-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.88.130:8848 # Nacos服务地址
      discovery:
        server-addr: 192.168.88.130:8848 # Nacos服务地址
      config:
        file-extension: yaml
        shared-configs:
          - dataId: shared-jdbc.yaml
          - dataId: shared-log.yaml
          - dataId: shared-swagger.yaml

pom.xml

        <!--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共享配置

在这里插入图片描述

配置热更新

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

步骤

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

或者实用这种方式进行配置

在这里插入图片描述

案例

在这里插入图片描述

步骤

新增属性配置类 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中的调用逻辑

    // 注入CartProperties
    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()));
        }
    }

在nacos配置中心。新增配置文件

在这里插入图片描述
在这里插入图片描述

yaml配置文件冒号的前后都必须要有空格

hm:
  cart:
    maxItems : 10

尝试将最大数量改成2

在这里插入图片描述

nacos的应用-动态路由

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

官方文档中是这么写的

在这里插入图片描述

在这里插入图片描述

步骤

  1. 复制cart-service中关于nacos相关的maven坐标到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>
  1. 复制cart-service中bootStrap.yaml中的配置文件到gateway中
spring:
  application:
    name: gateway# 服务名称
  cloud:
    nacos:
      server-addr: 192.168.88.130:8848 # Nacos服务地址
      discovery:
        server-addr: 192.168.88.130:8848 # Nacos服务地址
      config:
        file-extension: yaml
        shared-configs:
          - dataId: shared-log.yaml
  1. 去除掉application.yaml中关于路由的配置

在这里插入图片描述

server:
  port: 8080



# jwt配置
hm:
  jwt:
    location: classpath:hmall.jks
    alias: hmall
    password: hmall123
    tokenTTL: 30m
  auth:
    excludePaths:
      - /search/**
      - /users/login
      - /items/**
      - /hi
  1. 新增动态路由的配置类

在这里插入图片描述

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;

    // 路由配置的DataId
    private final String dataId = "gateway-routes.json";
    // 路由配置的Group
    private final String group = "DEFAULT_GROUP";
    // 超时时间
    private final long timeout = 5000;
    // 存储路由id的set
    private final Set<String> routeIds = new HashSet<>();


    /**
     * 初始化路由配置监听器:此方法会在Bean初始化完成后执行
     */
    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        // 1. 项目启动时,加载一次路由配置
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(
                        dataId,
                        group,
                        timeout,
                        new Listener() {
                            @Override
                            public void receiveConfigInfo(String configInfo) {
                                // 2. 监听到路由变化时,需要更新路由配置表
                                updateRouteConfig(configInfo);
                            }

                            @Override
                            public Executor getExecutor() {
                                return null;
                            }
                        }
                );
        // 3. 第一次加载路由配置的时候,也需要更新路由配置表
        updateRouteConfig(configInfo);
    }

    /**
     * 更新路由配置表
     *
     * @param configInfo 路由配置信息
     */
    private void updateRouteConfig(String configInfo) {
        log.info("更新路由配置表开始:{}", configInfo);
        // 1. 解析路由配置信息,转换为RouteDefinition对象
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);

        // 2. 删除已有的路由
        routeIds.forEach(routeId -> {
            // delete方法是异步的,需要订阅才能生效
            writer.delete(Mono.just(routeId)).subscribe();
        });
        // 清空路由id
        routeIds.clear();

        // 3. 更新路由配置表
        routeDefinitions.forEach(routeDefinition -> {
            // 3.1 更新路由
            // save方法是异步的,需要订阅才能生效
            // save不能直接传入RouteDefinition对象,需要使用Mono.just()包装一下
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2 记录路由id 方便第二次更新时删除
            routeIds.add(routeDefinition.getId());
        });
        log.info("更新路由配置表结束:{}", configInfo);
    }
}


在这里插入图片描述
在这里插入图片描述

  1. 在nacos注册中惊醒gateway-routes.json的配置

在这里插入图片描述

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

上述配置发布成功了以后,重启所有服务,如果都好使,那么就成功了

参考链接:https://b11et3un53m.feishu.cn/wiki/UMgpwmmQKisWBIkaABbcwAPonVf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值