1.什么是网关

什么是网关

在springCloud中网关实现有两种:
网关实现

1.1快速搭建网关

1.1创建项目
创建项目
1.2引入依赖
在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>

1.3启动类

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

1.4配置路由
接下来,在hm-gateway模块的resources目录新建一个application.yaml文件:

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

1.2路由属性

在这里插入图片描述
路由断言
spring提供了12种基本的RoutePredicateFactory实现:
路由断言
路由过滤器
网关中提供了33种路由过滤器,每种过滤器都有独特的作用:
路由过滤器

2.用户登录校验功能

我们可以在网关向后端需要身份校验的服务发送请求前,在网关校验
流程
网关请求流程:
网关请求流程

2.1自定义过滤器

网关过滤器有两种:
网关过滤器种类
方法
方法


@Component
public class MyGlobalFiler implements GlobalFilter , Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //todo 模拟登录校验逻辑
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println( headers);

        //放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        //优先级,过滤值值越小优先级越高,越先执行
        return 0;
    }
}

2.2实现登录功能

登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service中已经有了,我们直接拷贝过来:
目录
具体作用如下:

  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
  • SecurityConfig:工具的自动装配
  • JwtTool:JWT工具,其中包含了校验和解析token的功能
  • hmall.jks:秘钥文件

接下来,我们定义一个登录校验的过滤器
过滤器

package com.hmall.gateway.filters;

import cn.hutool.core.text.AntPathMatcher;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.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.List;


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

    private  final AuthProperties authProperties;
    private  final JwtTool jwtTool;
    //spring提供的路径匹配器
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取request
        ServerHttpRequest request = exchange.getRequest();
        //2.判断是否需要拦截
        if (isExclude( request.getPath().toString())){
            //放行
            return chain.filter(exchange);
        }
        //3.获取token
        String token =null;
        List<String> authorization = request.getHeaders().get("authorization");
        if (authorization != null|| !authorization.isEmpty()){
            token=authorization.get(0);
        }
        //4.校验token
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        }catch (UnauthorizedException e){
            //拦截,设置状态码为401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

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

    private boolean isExclude(String string) {
        for (String path : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(path, string)){
                return true;
            }
        }
        return false;
    }

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

3.传递用户信息

3.1网关向微服务传递用户信息

实现步骤

步骤一:
步骤一
在AuthGlobalFilter自定义过滤器中利用exchange.mutate(),将用户信息存入request并传递给下面微服务。

        //5.传递用户信息
        System.out.println("userId:"+userId);
        String userIdStr = userId.toString();
        ServerWebExchange serverWebExchange = exchange.mutate()
                .request(builder -> builder.header("user-info", userIdStr))
                .build();
        return chain.filter(serverWebExchange);

步骤二:
步骤二
我们每一个微服务都需要接收网关传递给我们的用户信息,所以我们可以解耦,因为每一个微服务项目都引入了hm-common模块,所以把这个通用功能放到hm-common模块实现。
先创建一个用户拦截器

package com.hmall.common.interceptors;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import net.bytebuddy.asm.Advice;
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 {
        //1.获取用户信息
        String userInfo = request.getHeader("user-info");
        //2.判断是否有用户信息,有就存入ThreadLocal
        if (StrUtil.isNotBlank(userInfo)){
            UserContext.setUser(Long.valueOf(userInfo));
        }
        //3.放行


        return true;
    }


    //在请求处理之后,视图渲染之前执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清理用户
        UserContext.removeUser();
    }
}

只是创建用户拦截器不能生效还需要注册拦截器,所以还需要写一个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)//判断DispatcherServlet类是否存在,来做为是否被扫描的标记,因为网关没有web模块,所以网关的配置类不能被扫描
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor()).addPathPatterns("/**");
    }
}

因为用户拦截器和各个微服务不在一个包下,不能被spring自动扫描到,需要我们在spring.factories做相应配置
自动扫描装配
但是这样,我们启动网关仍然会报错,因为网关微服务也引入hm-common模块,我们只希望MvcConfig只在微服务生效,不在网关生效,我们知道spring的自动装配是可以带条件的,所以我们可以加@ConditionalOnClass(DispatcherServlet.class)

@ConditionalOnClass(DispatcherServlet.class)
//判断DispatcherServlet类是否存在,来做为是否被扫描的标记,因为网关没有web模块,所以网关的配置类不能被扫描`

3.2不同微服务之间传递用户信息(OpenFeign传递)

实际业务举例:
实际业务

openfeign提供拦截器接口
我们就可以在hm-api中DefaultFeignConfig注册想对应的拦截器,让不同微服务之间,发起请求时携带用户信息的请求头。

	 @Bean
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Long user = UserContext.getUser();
                if (user == null) {
                    //如果空直接跳过
                    return;
                }
                //如果不为空则放入请求头,传递给下游
                requestTemplate.header("user-info", user.toString());
            }
        };
    }

整个登录流程拦截器的全过程
拦截器

4.配置管理

没有用配置管理的缺点
我们的nacos注册中心也可以配置管理服务

4.1配置共享

步骤一:
![添加配置到nacos中
](https://i-blog.csdnimg.cn/direct/9763bdb244694b0bb0359463f56e95f0.png)
配置了三份配置文件
三分配置文件
步骤二:
步骤二
引入依赖
创建bootstrap文件
bootstrap配置
application配置

4.2配置热更新

当修改配置文件中的配置时,微服务无需重启即可生效。
前提条件:
前提条件
案列:
案例

1.业务代码改造

    private final CartProperties cartProperties;
    
    @Override
    public void addItem2Cart(CartFormDTO cartFormDTO) {
        // 1.获取登录用户
        Long userId = UserContext.getUser();

        // 2.判断是否已经存在
        if (checkItemExists(cartFormDTO.getItemId(), userId)) {
            // 2.1.存在,则更新数量
            baseMapper.updateNum(cartFormDTO.getItemId(), userId);
            return;
        }
        // 2.2.不存在,判断是否超过购物车数量
        checkCartsFull(userId);

        // 3.新增购物车条目
        // 3.1.转换PO
        Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
        // 3.2.保存当前用户
        cart.setUserId(userId);
        // 3.3.保存到数据库
        save(cart);
    }
    
    private void checkCartsFull(Long userId) {
        int count = lambdaQuery().eq(Cart::getUserId, userId).count();
        if (count >= cartProperties.getMaxSize()) {
            throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxSize()));
        }
    }

2.还要创建一个文件读取添加购物车的最大值
创建一个config包
包
3.nacos配置

更多推荐