前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gateway如何使用多个源来达成动态路由

Gateway如何使用多个源来达成动态路由

原创
作者头像
半月无霜
发布2024-08-07 14:41:33
1800
发布2024-08-07 14:41:33
举报
文章被收录于专栏:半月无霜

Gateway如何使用多个源来达成动态路由

一、介绍

在前面的文章,我介绍了如何从Nacos读取json文件来动态生成路由

随着文件的变更,同时刷新路由

但在文章的结尾,我并不满足于仅仅只在Nacos配置动态路由,我想要在多个源上配置信息,任何一处地方修改了配置,Gateway照样能够刷新路由。

那么如何使用多个源来达成动态路由?本篇文章使用了Nacosjson文件,和MySQL数据表,两个配置源来达成动态路由

二、代码

首先,分析了上篇文章的RouteDefinitionRepository.java接口,之前的Nacos配置源也是实现了这个接口

主要是里面的这个方法,获取到所有的RouteDefinition对象,每一个对象就是一个路由

1)BaseDynamicRouter

那么这样就好办了,我们先定义一个接口,BaseDynamicRouter.java,里面有个方法获取到RouteDefinition对象列表

代码语言:java
复制
 package com.banmoon.route;
 ​
 import org.springframework.cloud.gateway.route.RouteDefinition;
 ​
 import java.util.List;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:17:43
  */
 @FunctionalInterface
 public interface BaseDynamicRouter {
 ​
     /**
      * 获取所有的路由信息
      */
     List<RouteDefinition> getAllRouterDefinitions();
 ​
 }

实现这个接口BaseDynamicRouter.java,代表着不同的配置源,我们这边有NacosMySQL,代码如下

代码语言:java
复制
 package com.banmoon.route.impl;
 ​
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import com.alibaba.cloud.nacos.NacosConfigManager;
 import com.alibaba.cloud.nacos.NacosConfigProperties;
 import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
 import com.alibaba.nacos.api.exception.NacosException;
 import com.banmoon.route.BaseDynamicRouter;
 import com.banmoon.route.DynamicRouterRepository;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
 import org.springframework.cloud.gateway.route.RouteDefinition;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Component;
 ​
 import javax.annotation.PostConstruct;
 import java.util.Collections;
 import java.util.List;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:22:05
  */
 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class NacosDynamicRouter implements BaseDynamicRouter {
 ​
     private final ApplicationEventPublisher publisher;
     private final NacosConfigProperties nacosConfigProperties;
     private final NacosConfigManager nacosConfigManager;
 ​
     /**
      * Nacos DATA_ID
      */
     private static final String DATA_ID = "gateway-router.json";
 ​
     /**
      * json序列化
      */
     private static final ObjectMapper objectMapper = new ObjectMapper();
 ​
     @PostConstruct
     public void init() throws NacosException {
         nacosConfigManager.getConfigService().addListener(DATA_ID, nacosConfigProperties.getGroup(), new AbstractSharedListener() {
             @Override
             public void innerReceive(String dataId, String group, String configInfo) {
                 DynamicRouterRepository dynamicRouterRepository = SpringUtil.getBean(DynamicRouterRepository.class);
                 publisher.publishEvent(new RefreshRoutesEvent(dynamicRouterRepository));
             }
         });
     }
 ​
     @Override
     public List<RouteDefinition> getAllRouterDefinitions() {
         try {
             String routeConfig = nacosConfigManager.getConfigService().getConfig(DATA_ID, nacosConfigProperties.getGroup(), 3000);
             if (StrUtil.isNotBlank(routeConfig)) {
                 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                 return objectMapper.readValue(routeConfig, new TypeReference<List<RouteDefinition>>() {
                 });
             }
         } catch (Exception e) {
             log.error("nacos获取路由配置异常", e);
         }
         return Collections.emptyList();
     }
 ​
 }
代码语言:java
复制
 package com.banmoon.route.impl;
 ​
 import cn.hutool.json.JSONUtil;
 import com.banmoon.route.BaseDynamicRouter;
 import com.banmoon.service.SysGatewayRouteService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cloud.gateway.filter.FilterDefinition;
 import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
 import org.springframework.cloud.gateway.route.RouteDefinition;
 import org.springframework.stereotype.Component;
 ​
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:21:13
  */
 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class DatabaseDynamicRouter implements BaseDynamicRouter {
 ​
     private final SysGatewayRouteService sysGatewayRouteService;
 ​
     @Override
     public List<RouteDefinition> getAllRouterDefinitions() {
         return sysGatewayRouteService
                        .list()
                        .stream()
                        .map(entity -> {
                            try {
                                List<PredicateDefinition> predicates = JSONUtil.toList(entity.getPredicates(), PredicateDefinition.class);
                                List<FilterDefinition> filters = JSONUtil.toList(entity.getFilters(), FilterDefinition.class);
                                RouteDefinition routeDefinition = new RouteDefinition();
                                routeDefinition.setId(entity.getRouteId());
                                routeDefinition.setPredicates(predicates);
                                routeDefinition.setFilters(filters);
                                routeDefinition.setUri(new URI(entity.getUri()));
                                return routeDefinition;
                            } catch (URISyntaxException e) {
                                log.error("路由:{},uri:{},解析失败", entity.getRouteId(), entity.getUri());
                                return null;
                            }
                        }).filter(Objects::nonNull)
                        .collect(Collectors.toList());
     }
 }

2)配置源

Nacos的配置就不再贴出来了,和前面一篇文章是一样的,主要讲讲MySQL这个配置源

首先,添加一张表

代码语言:sql
复制
 CREATE TABLE `sys_gateway_route` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
   `route_id` varchar(32) NOT NULL COMMENT '路由ID',
   `uri` varchar(128) DEFAULT NULL COMMENT 'uri',
   `predicates` varchar(1024) DEFAULT NULL COMMENT '断言',
   `filters` varchar(1024) DEFAULT NULL COMMENT '过滤',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统-网关路由'

对应的实体、Mapper

代码语言:java
复制
 package com.banmoon.entity;
 ​
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:43:06
  */
 @Data
 @NoArgsConstructor
 @EqualsAndHashCode(callSuper = false)
 @Accessors(chain = true)
 @TableName("sys_gateway_route")
 public class SysGatewayRouteEntity {
 ​
     /**
      * 主键ID
      */
     @TableId(type = IdType.AUTO)
     private Integer id;
 ​
     /**
      * 路由ID
      */
     private String routeId;
 ​
     /**
      * uri
      */
     private String uri;
 ​
     /**
      * 断言
      */
     private String predicates;
 ​
     /**
      * 过滤
      */
     private String filters;
 ​
 }
代码语言:java
复制
 package com.banmoon.mapper;
 ​
 import com.banmoon.business.mybatis.BanmoonMapper;
 import com.banmoon.entity.SysGatewayRouteEntity;
 import org.apache.ibatis.annotations.Mapper;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:46:06
  */
 @Mapper
 public interface SysGatewayRouteMapper extends BanmoonMapper<SysGatewayRouteEntity> {
 }

对应的service服务层

代码语言:java
复制
 package com.banmoon.service;
 ​
 import com.banmoon.business.obj.dto.ResultData;
 import com.banmoon.entity.SysGatewayRouteEntity;
 import com.banmoon.obj.request.GatewayRouteAddRequest;
 import com.banmoon.obj.request.GatewayRouteUpdateRequest;
 import com.baomidou.mybatisplus.extension.service.IService;
 import reactor.core.publisher.Mono;
 ​
 import java.util.List;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:47:17
  */
 public interface SysGatewayRouteService extends IService<SysGatewayRouteEntity> {
 ​
     Mono<ResultData<Object>> add(GatewayRouteAddRequest request);
 ​
     Mono<ResultData<Object>> update(GatewayRouteUpdateRequest request);
 ​
     Mono<ResultData<Object>> delete(List<Integer> idList);
 }
代码语言:java
复制
 package com.banmoon.service.impl;
 ​
 import com.banmoon.business.obj.dto.ResultData;
 import com.banmoon.entity.SysGatewayRouteEntity;
 import com.banmoon.mapper.SysGatewayRouteMapper;
 import com.banmoon.obj.request.GatewayRouteAddRequest;
 import com.banmoon.obj.request.GatewayRouteUpdateRequest;
 import com.banmoon.service.SysGatewayRouteService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
 import reactor.core.publisher.Mono;
 ​
 import java.util.List;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:47:33
  */
 @Slf4j
 @Service
 @RequiredArgsConstructor
 public class SysGatewayRouteServiceImpl extends ServiceImpl<SysGatewayRouteMapper, SysGatewayRouteEntity> implements SysGatewayRouteService {
 ​
     private final ApplicationEventPublisher publisher;
 ​
     @Override
     public Mono<ResultData<Object>> add(GatewayRouteAddRequest request) {
         return Mono.fromCallable(() -> {
             SysGatewayRouteEntity entity = new SysGatewayRouteEntity();
             BeanUtils.copyProperties(request, entity);
             baseMapper.insertOnDuplicateKeyUpdate(entity);
             return ResultData.success();
         }).onErrorReturn(ResultData.fail());
     }
 ​
     @Override
     public Mono<ResultData<Object>> update(GatewayRouteUpdateRequest request) {
         return Mono.fromCallable(() -> {
             SysGatewayRouteEntity entity = new SysGatewayRouteEntity();
             BeanUtils.copyProperties(request, entity);
             baseMapper.updateById(entity);
             return ResultData.success();
         }).onErrorReturn(ResultData.fail());
     }
 ​
     @Override
     public Mono<ResultData<Object>> delete(List<Integer> idList) {
         return Mono.fromCallable(() -> {
             baseMapper.deleteBatchIds(idList);
             return ResultData.success();
         }).onErrorReturn(ResultData.fail());
     }
 }

对应的controller

代码语言:java
复制
 package com.banmoon.controller;
 ​
 import com.banmoon.business.obj.dto.ResultData;
 import com.banmoon.obj.request.GatewayRouteAddRequest;
 import com.banmoon.obj.request.GatewayRouteUpdateRequest;
 import com.banmoon.service.SysGatewayRouteService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Mono;
 ​
 import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 ​
 /**
  * @author banmoon
  * @date 2024/08/01 10:02:24
  */
 @Slf4j
 @Api(tags = "系统-网关路由")
 @Validated
 @RestController
 @RequestMapping("/sys/gateway/route")
 @RequiredArgsConstructor
 public class SysGatewayRouteController {
 ​
     private final SysGatewayRouteService sysGatewayRouteService;
 ​
     @ApiOperation("新增")
     @PostMapping("add")
     public Mono<ResultData<Object>> add(@RequestBody @Valid GatewayRouteAddRequest request) {
         return sysGatewayRouteService.add(request);
     }
 ​
     @ApiOperation("修改")
     @PutMapping("update")
     public Mono<ResultData<Object>> update(@RequestBody @Valid GatewayRouteUpdateRequest request) {
         return sysGatewayRouteService.update(request);
     }
 ​
     @ApiOperation("删除")
     @DeleteMapping("delete")
     public Mono<ResultData<Object>> delete(@RequestBody @NotEmpty List<@NotNull Integer> idList) {
         return sysGatewayRouteService.delete(idList);
     }
 ​
 }

请求实体

代码语言:java
复制
 package com.banmoon.obj.request;
 ​
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 ​
 import javax.validation.constraints.NotBlank;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:59:46
  */
 @Data
 @ApiModel("新增路由-入参")
 public class GatewayRouteAddRequest {
 ​
     @NotBlank
     @ApiModelProperty("路由ID")
     private String routeId;
 ​
     @NotBlank
     @ApiModelProperty("uri")
     private String uri;
 ​
     @ApiModelProperty("断言")
     private String predicates;
 ​
     @ApiModelProperty("过滤")
     private String filters;
 ​
 }
代码语言:java
复制
 package com.banmoon.obj.request;
 ​
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 ​
 import javax.validation.constraints.NotBlank;
 ​
 /**
  * @author banmoon
  * @date 2024/08/05 19:59:46
  */
 @Data
 @ApiModel("修改路由-入参")
 public class GatewayRouteUpdateRequest {
 ​
     @ApiModelProperty("主键ID")
     private Integer id;
 ​
     @NotBlank
     @ApiModelProperty("路由ID")
     private String routeId;
 ​
     @NotBlank
     @ApiModelProperty("uri")
     private String uri;
 ​
     @ApiModelProperty("断言")
     private String predicates;
 ​
     @ApiModelProperty("过滤")
     private String filters;
 ​
 }

3)RouteDefinitionRepository

我们回来看看这个RouteDefinitionRepository.java接口,只需要实现它,注入上面提到的BaseDynamicRouter.java,实现getRouteDefinitions()方法

代码语言:java
复制
 package com.banmoon.route;
 ​
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.cloud.gateway.route.RouteDefinition;
 import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 ​
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 ​
 /**
  * 路由动态更新实现
  *
  * @author banmoon
  * @date 2024/08/01 10:12:22
  */
 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class DynamicRouterRepository implements RouteDefinitionRepository {
 ​
     private final Map<String, BaseDynamicRouter> dynamicRouterMap;
 ​
     /**
      * 存储路由信息
      */
     private static final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>());
 ​
     @Override
     public Flux<RouteDefinition> getRouteDefinitions() {
         return Flux.fromStream(dynamicRouterMap
                                        .entrySet()
                                        .stream()
                                        .flatMap(entry -> entry.getValue().getAllRouterDefinitions().stream()))
                        .doOnNext(routeDefinition -> routes.put(routeDefinition.getId(), routeDefinition));
     }
 ​
     @Override
     public Mono<Void> save(Mono<RouteDefinition> route) {
         return route.flatMap(routeDefinition -> {
             log.info("新增路由信息:{}", routeDefinition);
             routes.put(routeDefinition.getId(), routeDefinition);
             return Mono.empty();
         });
     }
 ​
     @Override
     public Mono<Void> delete(Mono<String> routeId) {
         return routeId.flatMap(id -> {
             log.info("删除路由ID:{}", id);
             routes.remove(id);
             return Mono.empty();
         });
     }
 }

这样就能实现多个源来配置Gateway的动态路由了

三、添加配置测试

在数据库插入一条路由信息,当然调用上面的接口也是可以的

代码语言:sql
复制
 INSERT INTO test.sys_gateway_route (id, route_id, uri, predicates, filters) VALUES (1, 'route_baidu', 'https://www.baidu.com', '[{"name":"Query","args":{"_genkey_0":"url","_genkey_1":"baidu"}}]', '[]');

打开浏览器,输入http://localhost:8080/?url=baidu,可以看到百度的页面了

四、最后

其实,这个还是有点不太对,因为我发现每隔一段时间就发起一次请求更新路由

问题倒不是很大,但这么查询数据库总是一种消耗,后续看看还有没有更好的方法

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Gateway如何使用多个源来达成动态路由
    • 一、介绍
      • 二、代码
        • 1)BaseDynamicRouter
        • 2)配置源
        • 3)RouteDefinitionRepository
      • 三、添加配置测试
        • 四、最后
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档