SpringCloud Gateway基于Nacos配置中心动态路由

young 731 2021-10-18

环境:

Java:8

Spring Boot:2.3.3

Spring Cloud:Hoxton.SR9

Spring Cloud Alibaba:2.2.2

动态路由操作工具类

@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

	@Autowired
	private RouteDefinitionWriter routeDefinitionWriter;

	private ApplicationEventPublisher publisher;

	/**
	 * 增加路由
	 *
	 * @param definition
	 * @return
	 */
	public String add(RouteDefinition definition) {
		routeDefinitionWriter.save(Mono.just(definition)).subscribe();
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
		return "success";
	}


	/**
	 * 更新路由
	 *
	 * @param definition
	 * @return
	 */
	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.publisher.publishEvent(new RefreshRoutesEvent(this));
			return "success";
		} catch (Exception e) {
			return "update route  fail";
		}


	}

	/**
	 * 删除路由
	 *
	 * @param id
	 * @return
	 */
	public String delete(String id) {
		try {
			this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
			this.publisher.publishEvent(new RefreshRoutesEvent(this));
			return "delete success";
		} catch (Exception e) {
			e.printStackTrace();
			return "delete fail";
		}

	}

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

}

动态路由实现

@Slf4j
@Component
public class DynamicRouteServiceImplByNacos {

   @Autowired
   private DynamicRouteServiceImpl dynamicRouteService;
   @Autowired
   private GatewayConfig gatewayConfig;
   @Autowired
   private RouteDefinitionLocator routeDefinitionLocator;

   @PostConstruct
   public void init() {
      dynamicRouteByNacosListener(gatewayConfig.getNacosRouteDataId(), gatewayConfig.getNacosRouteGroup());
   }

   /**
    * 监听Nacos Server下发的动态路由配置
    *
    * @param dataId
    * @param group
    */
   public void dynamicRouteByNacosListener(String dataId, String group) {
      try {
         Properties properties = new Properties();
         properties.setProperty("serverAddr", gatewayConfig.getNacosServerAddr());
         properties.setProperty("namespace", gatewayConfig.getNacosNamespace());
         ConfigService configService = NacosFactory.createConfigService(properties);
         String configInfo = configService.getConfig(dataId, group, 5000);
         log.info("获取网关当前路由配置 gatewayConfig={} configInfo={}", gatewayConfig, configInfo);
         List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
         for (RouteDefinition definition : definitionList) {
                modifyMultiArgs(definition);
                log.info("update route : {}", definition.toString());
            dynamicRouteService.add(definition);
         }
         configService.addListener(dataId, group, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
               log.info("进行网关路由配置更新: {}", configInfo);
               List<RouteDefinition> runningDefinitionList =
                     Optional.ofNullable(routeDefinitionLocator.getRouteDefinitions()).map(Flux::collectList)
                           .map(Mono::block).orElse(Collections.emptyList());
               List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
               for (RouteDefinition definition : definitionList) {
                        modifyMultiArgs(definition);
                  log.info("update route : {}", definition.toString());
                  dynamicRouteService.update(definition);
               }
               List<String> newRunningDefinitionIds =
                     definitionList.stream().map(RouteDefinition::getId).collect(Collectors.toList());
               runningDefinitionList.stream().map(RouteDefinition::getId)
                     .filter(routId -> !newRunningDefinitionIds.contains(routId)).forEach(this::deleteRoute);
            }

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

            private void deleteRoute(String routeId) {
               log.info("delete route : {}", routeId);
               dynamicRouteService.delete(routeId);
            }
         });
      } catch (NacosException e) {
         log.error("网关动态路由配置出错", e);
      }
   }

    private void modifyMultiArgs(RouteDefinition definition) {
        List<PredicateDefinition> predicates = definition.getPredicates();
        for (PredicateDefinition predicate : predicates) {
            Map<String, String> predicateArgs = predicate.getArgs();
            if (predicateArgs == null || predicateArgs.size() != 1) {
                continue;
            }
            String[] args = StringUtils.tokenizeToStringArray(predicateArgs.values().iterator().next(), ",");
            predicateArgs.clear();
            for (int i = 0; i < args.length; i++) {
                predicateArgs.put(NameUtils.generateName(i), args[i]);
            }
        }
    }

}

modifyMultiArgs

直接使用fastjson将nacos传输的数据进行反序列化,发现官网中Path=/red/,/blue/这种多路径配置未生效,查看RouteDefinition及PredicateDefinition源码

public RouteDefinition() {
}

public RouteDefinition(String text) {
   int eqIdx = text.indexOf('=');
   if (eqIdx <= 0) {
      throw new ValidationException("Unable to parse RouteDefinition text '" + text
            + "'" + ", must be of the form name=value");
   }

   setId(text.substring(0, eqIdx));

   String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");

   setUri(URI.create(args[0]));

   for (int i = 1; i < args.length; i++) {
      this.predicates.add(new PredicateDefinition(args[i]));
   }
}
public PredicateDefinition() {
}

public PredicateDefinition(String text) {
   int eqIdx = text.indexOf('=');
   if (eqIdx <= 0) {
      throw new ValidationException("Unable to parse PredicateDefinition text '"
            + text + "'" + ", must be of the form name=value");
   }
   setName(text.substring(0, eqIdx));

   String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");

   for (int i = 0; i < args.length; i++) {
      this.args.put(NameUtils.generateName(i), args[i]);
   }
}

发现在该方法的含参构造函数中,进行了相关处理,故增加对应逻辑,使多路径配置生效