且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何动态请求参数网关? | 带你读《Spring Cloud Alibaba(2019)》之十二

更新时间:2022-03-07 07:42:12

上一篇:如何保证微服务接口的安全? | 带你读《Spring Cloud Alibaba(2019)》之十一
下一篇:什么是SpringCloud Sentinel | 带你读《Spring Cloud Alibaba(2019)》之十三

本文来自于《精通Spring Cloud Alibaba》课程的整理,讲师为余胜军,点击查看视频内容
本文系志愿者整理,供配合学习中心课程使用,不做商业用途。

动态请求参数网关

动态网关:任何配置都实现不用重启网关服务器都可以及时刷新网关配置。
方案:
1.基于数据库形式实现,特别建议,阅读性高
2.基于配置中心实现,不建议使用,需要定义json格式配置,阅读性差

注意:配置中心实现维护性比较差,建议采用数据库形式设计。

基于数据库表形式的设计
网关已经提供了API接口
1、直接新增
2、直接修改

思路:
默认加载时候
1、当我们的网关服务启动的时候,从我们数据库查询网关的配置。
2、将数据库的内容读取到网关内存中

网关配置要更新的时候,需要同步调用

伪代码步骤:
1、更新数据库
2、调用网关api更新

网关服务相关表

CREATE TABLE `mayikt_gateway` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `route_id` varchar(11) DEFAULT NULL,
  `route_name` varchar(255) DEFAULT NULL,
  `route_pattern` varchar(255) DEFAULT NULL,
  `route_type` varchar(255) DEFAULT NULL,
  `route_url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

代码实现动态服务网关过程

public class GatewayService implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    @Autowired
    private MayiktGatewayMapper mayiktGateway;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void initAllRoute() {
        // 从数据库查询配置的网关配置
        List<GateWayEntity> gateWayEntities = mayiktGateway.gateWayAll();
        for (GateWayEntity gw :
                gateWayEntities) {
            loadRoute(gw);
        }

    }


    public String loadRoute(GateWayEntity gateWayEntity) {
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        // 如果配置路由type为0的话 则从注册中心获取服务
        URI uri = null;
        if (gateWayEntity.getRouteType().equals("0")) {
            URI  uri = UriComponentsBuilder.fromUriString("lb://" + gateWayEntity.getRouteUrl() + "/").build().toUri();
        } else {
            uri = UriComponentsBuilder.fromHttpUrl(gateWayEntity.getRouteUrl()).build().toUri();
        }
        // 定义的路由唯一的id
        definition.setId(gateWayEntity.getRouteId());
        predicate.setName("Path");
        //路由转发地址
        predicateParams.put("pattern", gateWayEntity.getRoutePattern());
        predicate.setArgs(predicateParams);

        // 名称是固定的, 路径去前缀
        filterDefinition.setName("StripPrefix");
        filterParams.put("_genkey_0", "1");
        filterDefinition.setArgs(filterParams);
        definition.setPredicates(Arrays.asList(predicate));
        definition.setFilters(Arrays.asList(filterDefinition));
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }


}

实体类&访问层

public interface MayiktGatewayMapper {

    @Select("SELECT ID AS ID, route_id as routeid, route_name as routeName,route_pattern as routePattern\n" +
            ",route_type as routeType,route_url as routeUrl\n" +
            " FROM mayikt_gateway\n")
    public List<GateWayEntity> gateWayAll();

    @Update("update mayikt_gateway set route_url=#{routeUrl} where route_id=#{routeId};")
    public Integer updateGateWay(@Param("routeId") String routeId, @Param("routeUrl") String routeUrl);
}

Maven依赖

    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里巴巴数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.14</version>
</dependency>
### 127.0.0.1/mayikt  转到到http://www.mayikt.com/
  datasource:
    url: jdbc:mysql://localhost:3306/meite_gateWay?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

我们刷新一下,传入token,可以看到:
如何动态请求参数网关? | 带你读《Spring Cloud Alibaba(2019)》之十二

GateWay的词汇表有那些

路由:是网关基本的模块,分别为id(唯一)、目标uri(真实服务地址)、一组谓词+过滤器一起组合而成,如果谓词匹配成功,则路由匹配成功。
谓词: 匹配Http请求参数
过滤器:对下游的服务器之前和之后实现处理。

1、匹配时间之后

- id: mayikt
  uri: http://www.mayikt.com/
  ###匹配规则
  predicates:
    - After=2017-01-20T17:42:47.789-07:00[America/Denver]

此路由与 2017 年 1 月 20 日 17:42 MountainTime(Denver)之后的所有请求相匹配。

2、匹配对应的host

- id: meite
  uri: http://www.mayikt.com/
  ###匹配规则
  predicates:
    - Host=meite.mayikt.com

访问 mete.mayikt.com 转发到http://www.mayikt.com/

3、权重谓词

- id: weight_high
  uri: http://www.mayikt.com/yushengjun
  predicates:
    - Weight=group1, 2
- id: weight_low
  uri: http://www.mayikt.com
  predicates:
    - Weight=group1, 1

根据权重比例实现转发

- id: weight_order
  uri: lb://meitemayikt-order
  predicates:
    - Weight=group1,2
- id: weight_member
  uri: lb://mayikt-member
  predicates:
    - Weight=group1,1

详细参考:
https://cloud.spring.io/spring-cloud-gateway/reference/html/#gatewayfilter-factories

GateWay解决跨域的问题

*/
@Component
public class CrossOriginFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
        headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
        return chain.filter(exchange);

    }
}

网关GateWay源码分析

1.客户端向网关发送Http请求,会到达DispatcherHandler接受请求,匹配到
RoutePredicateHandlerMapping。

  • 根据RoutePredicateHandlerMapping匹配到具体的路由策略。
  • FilteringWebHandler获取的路由的GatewayFilter数组,创建 GatewayFilterChain 处理过滤请求
  • 执行我们的代理业务逻辑访问。
    如何动态请求参数网关? | 带你读《Spring Cloud Alibaba(2019)》之十二

常用配置类说明:

  • GatewayClassPathWarningAutoConfiguration 检查是否有正确的配置webflux
  • GatewayAutoConfiguration 核心配置类
  • GatewayLoadBalancerClientAutoConfiguration 负载均衡策略处理
  • GatewayRedisAutoConfiguration Redis+lua整合限流

常见错误

Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' that could not be found.

原因就是
SpringCloud gateway基于webflux实现的,不是基于SpringBoot-web,所以应该删除
SpringBoot-web依赖组件。

SpringCloud gateway 常用名词

Route 路由 路由的id (唯一)、 转发uri (真实服务地址)、过滤器、谓词组成。
谓词 匹配的规则

源码分析:

SpringBoot项目源码的入口

  • GatewayClassPathWarningAutoConfiguration 作用检查是否配置我们webfux依赖。
  • GatewayAutoConfiguration加载了我们Gateway需要的注入的类。
  • GatewayLoadBalancerClientAutoConfiguration 网关需要使用的负载均衡
    Lb//mayikt-member// 根据服务名称查找真实地址
  • GatewayRedisAutoConfiguration 网关整合Redis整合Lua实现限流
  • GatewayDiscoveryClientAutoConfiguration 服务注册与发现功能

微服务中跨域的问题 不属于前端解决,jsonp 只能支持get请求。

核心点就是在我们后端。

解决跨域的问题

  • HttpClient转发
  • 使用过滤器允许接口可以跨域 响应头设置
  • Jsonp 不支持我们的post 属于前端解决
  • Nginx解决跨域的问题保持我们域名和端口号一致性
  • Nginx也是通过配置文件解决跨域的问题
  • 基于微服务网关解决跨域问题,需要保持域名和端口一致性
  • 使用网关代码允许所有的服务可以跨域的问题
  • 使用SpringBoot注解形式@CrossOrigin