天天看點

【SpringCloud】實作動态網關路由-Nacos

本文記錄一下如何使用 Gateway 搭建網關服務,實作動态路由的,幫助大家學習如何快速搭建網關服務,了解路由相關配置。

1、JSON路由配置

[
    {
      
   
        "predicates": [
            {
                "args": {
                    "pattern": "/csdn"
                },
                "name": "Path"
            }
        ]
    }
]      

如圖

【SpringCloud】實作動态網關路由-Nacos

2、配置路由存儲方式

實作動态路由的關鍵是 RouteDefinitionRepository 接口,該接口存在一個預設實作InMemoryRouteDefinitionRepository,通過名字我們應該也知道,該實作是将配置檔案中配置的資訊加載到記憶體中。

import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 配置路由存儲方式
 *
 * @author H.Yang
 * @date 2022/10/14
 */
@Component
public class LocalRouteDefinitionRepository implements RouteDefinitionRepository {
    public final Map<String, RouteDefinition> routes = new ConcurrentHashMap();

    /**
     * 請注意,此方法很重要,從redis取路由資訊的方法,官方核心包要用,核心路由功能都是從redis取的
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(routes.values());
    }

    @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("路由檔案沒有找到: " + routeId)));
        });
    }

    public void clear() {
        routes.clear();
    }
}      

3、動态路由實作

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * @author H.Yang
 * @date 2022/10/16
 */
@Slf4j
@Component
public class NacosDynamicRouteService implements ApplicationEventPublisherAware, CommandLineRunner {

    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private LocalRouteDefinitionRepository localRouteDefinitionRepository;
    @Autowired
    private NacosConfigProperties nacosConfigProperties;

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


    @Override
    public void run(String... args) throws Exception {
        log.debug("啟動時加載....");
        ConfigService configService = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());
        // 程式首次啟動, 并加載初始化路由配置
        String configInfo = configService.getConfig("gateway-router.json", nacosConfigProperties.getGroup(), 5000);

        this.addRouteConfig(configInfo);

        // 添加監聽器監聽nacos配置檔案内的路由變化
        configService.addListener("gateway-router.json", nacosConfigProperties.getGroup(), new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                log.debug("配置發生變化時加載....");
                
                refreshRouteConfig(configInfo);
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });

    }

    /**
     * 重新整理路由配置
     *
     * @param configInfo
     */
    private void refreshRouteConfig(String configInfo) {
        // 重新整理配置時需要先清空緩存
        localRouteDefinitionRepository.clear();

        this.addRouteConfig(configInfo);
    }


    /**
     * 添加路由
     *
     * @param configInfo
     */
    private void addRouteConfig(String configInfo) {
        log.debug("動态添加路由配置...");

        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        for (RouteDefinition routeDefinition : routeDefinitions) {
            // 添加路由
            localRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
        }

        this.publish();

        log.debug("動态配置路由加載完成 {}", JSONUtil.toJsonStr(routeDefinitions));
    }


    /**
     * 釋出
     */
    public void publish() {
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

}