SpringCloud GateWay动态路由用法

 更新时间:2024年10月31日 14:22:05   作者:Xiao_zuo_ya  
网关作为所有项目的入口,不希望重启,因此动态路由是必须的,动态路由主要通过RouteDefinitionRepository接口实现,其默认的实现是InMemoryRouteDefinitionRepository,即在内存中存储路由配置,可基于这个map对象操作,动态路由的实现方案有两种

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对象操作。

3.动态路由设计以及实现

  • 方案一:知道动态路由的原理以后,我们可以基于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);
    }
}

4.网关中聚合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;
    }

}

5.测试一下

swagger中目前只有这一个路由,调用路由新增一个

再次刷新swagger,OK 已经看到新的路由了

redis中也已经看到了路由的配置

6.写在最后

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

最新评论