【SpringCloud分布式框架搭建】gateway网关,动态路由配置,使用Mysql,存储路由,实现集群gateway动态路由【三】

前两篇,主要写了 实体类和表的直接操作

【SpringCloud分布式框架搭建】gateway网关,动态路由配置,使用Mysql,存储路由,实现集群gateway动态路由【二】

【SpringCloud分布式框架搭建】gateway网关,动态路由配置,使用Mysql,存储路由,实现集群gateway动态路由【一】

接下来,主要说明 gateway的动态加载的配置。

通过分析 spring Cloud gateway源码可以发现,默认的 RouteDefinitionWriter 实现类的是 InMemoryRouteDefinitionRepository ,如图:

RouteDefinitionRepository 继承了RouteDefinitionWriter,是 Spring Cloud Gateway官方预留的接口。从而可以通过下面两种方式来实现集群下的动态路由修改:RouteDefinitionWriter 接口和 RouteDefinitionRepository 接口。在这里推荐实现RouteDefinitionRepository 这个接口,从数据库或者从配置中心获取路由进行动态配置。

这里直接贴上我的mysql 实现类:MysqlRouteDefinitionRepository


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.keda.gateway.entity.DynamicRouteVo;
import com.keda.gateway.entity.GatewayDynamicRoute;
import com.keda.gateway.service.IGatewayDynamicRouteService;
import com.keda.gateway.struts.DynamicRouteStruts;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author: seowen
 * @Date: 2019/12/19 15:27
 * @Version 1.0
 */
@Component
public class MysqlRouteDefinitionRepository implements RouteDefinitionRepository {

    @Autowired
    private IGatewayDynamicRouteService routeService;

    /**
     * Gateway启动的时候,会加载这个方法
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        //从数据库中,获取我们自定义的 路由信息数据
        List<GatewayDynamicRoute> listByEnable = routeService.getListByEnable(true);

        if (CollectionUtils.isNotEmpty(listByEnable)){
            //转换成 RouteDefinition 集合后,返回
            return Flux.fromIterable(this.toRouteList(listByEnable));
        }
        //如果 数据库为 空,则返回一个 空的集合
        return Flux.fromIterable(new ArrayList<RouteDefinition>());
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return null;
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return null;
    }

    /**
     * 转换成 List<RouteDefinition>
     * @param listByEnable
     * @return
     */
    private List<RouteDefinition> toRouteList(List<GatewayDynamicRoute> listByEnable){
        List<RouteDefinition> routeList = new ArrayList<>();
        /**
         * 循环转换:
         * 因为数据库中,Predicates 和 Filters 存储的 json字符串。所以,得先转换成 对应的 vo.
         * 然后在转换成 List<PredicateDefinition>和 List<FilterDefinition>
         */

        listByEnable.stream().forEach(gw->{
            RouteDefinition r = new RouteDefinition();
            r.setUri(DynamicUtil.getUri(gw.getUri()));
            r.setOrder(gw.getOrder());
            r.setId(gw.getRouteId());
            r.setPredicates(DynamicRouteStruts.INSTANCES.toPredicateDefinition(JSONArray.parseArray(gw.getPredicates(), DynamicRouteVo.PredicateDefinitionVo.class)));
            r.setFilters(DynamicRouteStruts.INSTANCES.toFilterDefinition(JSONArray.parseArray(gw.getFilters(),DynamicRouteVo.FilterDefinitionVo.class)));
            routeList.add(r);
        });
        return routeList;
    }

}
MysqlRouteDefinitionRepository 类写好后,还不行。 我们需要当每次操作 动态有路由表,都希望 gateway网关,重新加载 路由数据。 这个时候,我们就需要一个spring事件来支持。


DynamicRouteServiceImpl 类,就是我的 事件事件

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

/**
 * @Author: seowen
 * @Date: 2019/12/19 15:49
 * @Version 1.0
 */
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;


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

    /**
     * 增加路由
     * @param routeDefinition
     * @return
     */
    public String add(RouteDefinition routeDefinition){
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.doLoad();
        return "success";
    }

    /**
     * 更新路由
     */
    public String update(RouteDefinition definition) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            return "update fail,not find route  routeId: " + definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.doLoad();
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }
    }


    /**
     * 删除路由
     *
     */
    public String delete(String id) {
//        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(()->Mono.just(ResponseEntity.ok().build())))
//                .onErrorResume(t -> t instanceof NotFoundException,t -> Mono.just(ResponseEntity.notFound().build()));
        try {
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            this.doLoad();
        } catch (Exception e) {
            e.printStackTrace();
            return "delete fail,not find route  routeId: " + id;
        }
        return "delete success";
    }

    /**
     * 重新刷新 路由
     */
    public String doLoad() {
        try {
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }catch (Exception e){
            e.printStackTrace();
            return "load fail";
        }
        return "load success";
    }


}

 然后,我们每次操作 数据库后,都需要调用 这个 doLoad()方法,来刷新路由:
如 GatewayDynamicRouteController 的 add方法

    /**
     * 增加路由
     * @param routeBean
     * @return
     */
    @PostMapping("/add")
    public String add(@RequestBody DynamicRouteVo.RouteBean routeBean){

        service.saveOne(DynamicRouteStruts.INSTANCES.toGatewayDynamicRoute(routeBean));
        return this.dynamicRouteService.doLoad();
    }

特别说明:

{
	"routeId":"keda-creditcard",
	"uri":"lb://KEDA-CREDITCARD",
	"order":"1",
	"enable":"true",
	"predicates":[ 
		{
	        "name": "Path",
	        "args": {
	            "_genkey_0": "/main/**"
	        }
	    }
    ],
	"filters":[
	    {
            "name": "StripPrefix",
            "args": {
                "_genkey_0": "1"
            }
        },
        {
            "name": "Retry",
            "args": {
                "retries": "3",
                "series.0": "SERVER_ERROR",
                "methods.0": "GET",
                "methods.1": "POST"
            }
        }	
	]
	
}

以上,请求参数说明:
在配置 路由信息时。 一般是  name=value 的形式。

上面的 Path 和 StripPrefix 就是常用的  name属性名。特别需要注意的是,对应的值,"/main/**"“1”。在gateway 静态路由配置的时候,我们会直接在 yml文件中,配置路由信息,如下图



当 predicates filters 节点下,配置的是简单的  name=value 时。 gateway 在加载的时候,就会 将其按 “=”(即char 61)分割。 然后在将 value 以 “,”分割成 String 数组。然后,循环数组,以 “_genkey_” 为前缀 + 数组下标后,为key. value为value。

存到 args Map中

源码如下:

因此,我们在 添加 路由的时候,也要遵守这个的规则。如:

	        "name": "Path",
	        "args": {
	            "_genkey_0": "/main/**"
	        }

而,当添加的路由,有 args属性,并且 对应的 为 key:value 形式时,如果 key是复数(即集合类型)。
那 对应的 key 将以 “key.小标” 来表示。 如:

 "name": "Retry",
 "args": {
     "retries": "3",
     "series.0": "SERVER_ERROR",
     "methods.0": "GET",
     "methods.1": "POST"
 }

 

至此, 分布式架构下的,集群网关服务的  动态路功能,搭建完毕!!!!

发布了111 篇原创文章 · 获赞 28 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_42697074/article/details/103821946