SpringCloud-GateWay-动态路由

  1. 网关为什么需要动态路由?
    网关的核心功能就是通过配置不同路由策略在配合注册中心访问不同的微服务,而默认是在yaml文件中配置路由策略,而在项目上线后,网关作为所有项目的入口肯定不希望重启,所以动态路由是必须的,我们在增加一个服务,在不希望服务重新启动的前提下能路由到该服务,以及是基于代码实现的网关动态路由
  2. 动态路由原理
public interface RouteDefinitionRepository
		extends RouteDefinitionLocator, RouteDefinitionWriter {
}

RouteDefinitionRepository是网关路由的存储接口,RouteDefinitionLocator 是获取存储中的所有路由,RouteDefinitionWriter主要操作路由的存储和删除。

public interface RouteDefinitionLocator {
	Flux<RouteDefinition> getRouteDefinitions();

}
public interface RouteDefinitionWriter {
	Mono<Void> save(Mono<RouteDefinition> route);
	Mono<Void> delete(Mono<String> routeId);
}

而gateway中RouteDefinitionRepository接口的默认的实现是InMemoryRouteDefinitionRepository,即在内存中存储路由配置,而且在 GatewayAutoConfiguration 配置中也激活了InMemoryRouteDefinitionRepository这个Bean,代码如下。

	@Bean
	@ConditionalOnMissingBean(RouteDefinitionRepository.class)
	public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
		return new InMemoryRouteDefinitionRepository();
	}
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

	private final Map<String, RouteDefinition> routes = synchronizedMap(
			new LinkedHashMap<String, RouteDefinition>());

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			routes.put(r.getId(), r);
			return Mono.empty();
		});
	}
	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		return routeId.flatMap(id -> {
			if (routes.containsKey(id)) {
				routes.remove(id);
				return Mono.empty();
			}
			return Mono.defer(() -> Mono.error(
					new NotFoundException("RouteDefinition not found: " + routeId)));
		});
	}
	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		return Flux.fromIterable(routes.values());
	}

}

InMemoryRouteDefinitionRepository 中可见存储路由的是一个带同步锁的LinkedHashMap,而存储删除都是基于这个map对象操作。

  1. 动态路由设计以及实现
    方案一:知道动态路由的原理以后,我们可以基于redis设计一个InRedisRouteDefinitionRepository 实现 RouteDefinitionRepository 接口即可,即网关部署多个也能动态解决路由问题
    方案二:可以基于nacos 配置动态修改路由(理论上,待验证)nacos的配置也是可以热加载的。
@Slf4j
@Configuration("redisRouteDefinition")
@AllArgsConstructor
public class InRedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private RedisTemplate redisTemplate;

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {

        return route.flatMap(r -> {
            redisTemplate.opsForHash().put(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, r.getId(), new Gson().toJson(r));
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            Object router = redisTemplate.opsForHash().get(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id);
            if (!Objects.isNull(router)) {
                redisTemplate.opsForHash().delete(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(
                new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<String> values = redisTemplate.opsForHash().values(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG);
        if (CollUtil.isNotEmpty(values)) {
            List<RouteDefinition> definitions = values.stream()
                .map(s -> new Gson().fromJson(s, RouteDefinition.class))
                .collect(Collectors.toList());
            return Flux.fromIterable(definitions);
        } else {
            return Flux.fromIterable(new ArrayList<>());
        }
    }

}

暂时在网关中提供接口实现路由的动态增加和修改Controller

@RestController
@RequestMapping("/route")
@AllArgsConstructor
public class RouteController {

    private DynamicRouteService dynamicRouteService;

    @PostMapping
    public void saveRouteDefinition(@RequestBody GatewayRouteDefinition routeDefinition) {
        dynamicRouteService.saveRouteDefinition(routeDefinition);
    }

    @DeleteMapping("/{id}")
    public void deleteRouteDefinition(@PathVariable String id) {
        dynamicRouteService.deleteRouteDefinition(id);
    }

    @PutMapping
    public void update(@RequestBody GatewayRouteDefinition routeDefinition) {
        dynamicRouteService.updateRouteDefinition(routeDefinition);
    }

    @GetMapping
    public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) {
        return dynamicRouteService.getRouterConfigByPage(params);
    }

}

路由参数

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class GatewayRouteDefinition {

    /**
     * 路由的Id
     */
    private String id;
    /**
     * 路由断言集合配置
     */

    private List<GatewayPredicateDefinition> predicates;
    /**
     * 路由过滤器集合配置
     */

    private List<GatewayFilterDefinition> filters;
    /**
     * 路由规则转发的目标uri
     */
    private String uri;

    /**
     * 路由执行的顺序
     */
    private int order;
}
@Data
public class GatewayPredicateDefinition implements Serializable {

    /**
     * 断言对应的Name
     */
    private String name;

    /**
     * 配置的断言规则
     */
    private Map<String, String> args = new LinkedHashMap<>();

}
@Data
public class GatewayFilterDefinition implements Serializable {

    /**
     * Filter Name
     */
    private String name;
    /**
     * 对应的路由规则
     */
    private Map<String, String> args = new LinkedHashMap<>();

}

业务层代码 DynamicRouteService,最主要的是注入RouteDefinitionWriter 我们自己的实现类,替换默认的配置

@Service
public class DynamicRouteServiceImpl implements DynamicRouteService {

    @Resource(name = "redisRouteDefinition")
    private RouteDefinitionWriter routeDefinitionWriter;

    @Autowired
    private IRouterConfigService routerConfigService;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void saveRouteDefinition(GatewayRouteDefinition definition) {
        // 判定当前路由以及路径是否存在
        LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery()
            .eq(RouterConfig::getRouterName, definition.getId())
            .eq(RouterConfig::getRouterPath, definition.getUri());
        List<RouterConfig> list = routerConfigService.list(wrapper);
        BizVerify.verify(CollUtil.isEmpty(list), "路由已经存在");
        routerConfigService.save(paramsConvert(definition));
        RouteDefinition routerDefinition = DynamicRouteUtils.convertToRouteDefinition(definition);
        routeDefinitionWriter.save(Mono.just(routerDefinition)).subscribe();
    }

    @Override
    public void updateRouteDefinition(GatewayRouteDefinition routeDefinition) {
        routerConfigService.updateById(paramsConvert(routeDefinition));
        RouteDefinition definition = DynamicRouteUtils.convertToRouteDefinition(routeDefinition);
        deleteRouteDefinition(definition.getId());
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
    }

    @Override
    public void deleteRouteDefinition(String routerId) {
        routerConfigService.removeById(routerId);
        routeDefinitionWriter
            .delete(Mono.just(routerId))
            .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
            .onErrorResume((t) -> t instanceof NotFoundException, (t) -> Mono.just(ResponseEntity.notFound().build()));
    }


    private RouterConfig paramsConvert(GatewayRouteDefinition routeDefinition) {
        String filterJson = null;
        String PredicatesJson = null;
        try {
            filterJson = objectMapper.writeValueAsString(routeDefinition.getFilters());
            PredicatesJson = objectMapper.writeValueAsString(routeDefinition.getPredicates());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return new RouterConfig()
            .setRouterName(routeDefinition.getId())
            .setRouterPath(routeDefinition.getUri())
            .setRouterOrder(routeDefinition.getOrder())
            .setRouterFilters(filterJson)
            .setRouterPredicates(PredicatesJson);
    }

    @Override
    public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) {
        LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery()
            .like(StrUtil.isNotEmpty(params.getRouterName()), RouterConfig::getRouterName, params.getRouterName());
        return routerConfigService.page(new Page<>(params.getPageNum(), params.getPageSize()), wrapper);
    }
}
  1. 网关中聚合swagger由于动态路由引发不展示的问题,聚合swagger聚合核心代码
package com.kill.core.provider;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

/**
 * <pre>
 * +--------+---------+-----------+---------+
 * |                                        |
 * +--------+---------+-----------+---------+
 * </pre>
 *
 * @author wangjian
 * @since 1019/11/01 11:58:32
 */
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceProvider implements SwaggerResourcesProvider {

    private static final String SWAGGER2URL = "/v2/api-docs";

    private RouteDefinitionRepository repository;

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        repository.getRouteDefinitions().subscribe(
            route -> {
                if (CollUtil.isNotEmpty(route.getPredicates())) {
                    route.getPredicates().forEach(
                        predicateDefinition -> {
                            if (CollUtil.isNotEmpty(predicateDefinition.getArgs())) {
                                if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get("pattern"))) {
                                    resources.add(
                                        swaggerResource(route.getId(),
                                            predicateDefinition.getArgs().get("pattern").replace("/**", SWAGGER2URL)));
                                }
                                if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0"))) {
                                    resources.add(
                                        swaggerResource(route.getId(),
                                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL)));
                                }
                            }

                        });
                }
            });
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("2.0");
        return swaggerResource;
    }

}

  1. 测试一下
    在这里插入图片描述
    swagger中目前只有这一个路由,调用路由新增一个
    在这里插入图片描述
    再次刷新swagger,OK 已经看到新的路由了
    在这里插入图片描述
    redis中也已经看到了路由的配置
    在这里插入图片描述

  2. 写在最后的,不可能所有的代码拿过来就能用,每个人的理解也不尽相同,记录在这里希望能提供一个思路,能解决到自己遇到的问题,而不是希望大家看到后,说拷贝过来的东西都是垃圾,你可以看,如果没有帮助到你我也很遗憾。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringCloud Gateway是一个基于Spring Cloud的全新项目,它是基于Spring 5.0、Spring Boot 2.0和Project Reactor等技术开发的网关。它的主要目标是为微服务架构提供一种简单有效的统一API路由管理方式。SpringCloud Gateway具有许多特性,包括动态路由、断路器功能、服务发现功能、请求限流功能和支持路径重写等。它是基于WebFlux框架实现的,底层使用了高性能的Reactor模式通信框架Netty。总之,SpringCloud Gateway是一个功能强大且易于使用的网关,可用于构建和管理微服务架构中的API路由。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [springcloud入门——gateway](https://blog.csdn.net/tang_seven/article/details/118523647)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [springcloudGateWay](https://blog.csdn.net/qq_35512802/article/details/122049808)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值