方式一:通过nacos中gateway的配置文件实时更新路由
步骤零、搭建好gateway环境,参考gateway环境搭建的例子
步骤一、在nacos中配置gateway参数,类型为text,格式如下:
[
{
“id”: “provider2”,
“uri”: “lb://provider2/”,
“predicates”: [
{
“name”: “Path”,
“args”: {
“pattern”: “/provider2/**”
}
}
],
“filters”:[
{
“name”: “StripPrefix”,
“args”: {
“parts”: 1
}
}
]
}
]
步骤二、创建GatewayConfig配置类
/**
* @author hslypd
*/
@Configuration
@SuppressWarnings(value = "all")
public class GatewayConfig {
/* 读取配置的超时时间 */
public static final long DEFAULT_TIMEOUT = 30000;
/* nacos服务器地址 */
public static String NACOS_SERVER_ADDR;
/* 命名空间 */
public static String NACOS_NAMESPACE;
/* date-id */
public static String NACOS_ROUTE_DATE_ID;
/* 分组ID */
public static String NACOS_ROUTE_GROUP;
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr){
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosNamespace(String nacosNamespace){
NACOS_NAMESPACE = nacosNamespace;
}
@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId){
NACOS_ROUTE_DATE_ID = nacosRouteDataId;
}
@Value("${nacos.gateway.route.config.group}")
public void setNacosRouteGroup(String nacosRouteGroup){
NACOS_ROUTE_GROUP = nacosRouteGroup;
}
}
步骤三、创建DynamicRouteService类
/**
* @author hslypd
*/
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteService implements ApplicationEventPublisherAware/*spring提供的事件推送接口*/ {
/* 写gateway中的路由定义 */
private final RouteDefinitionWriter routeDefinitionWriter;
/* 获取路由定义 */
private final RouteDefinitionLocator routeDefinitionLocator;
/* 事件发布对象 */
private ApplicationEventPublisher publisher;
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteDefinitionLocator routeDefinitionLocator) {
this.routeDefinitionWriter = routeDefinitionWriter;
this.routeDefinitionLocator = routeDefinitionLocator;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
//完成时间推送句柄的初始化
this.publisher = applicationEventPublisher;
}
/**
* @Description 添加路由定义
* @Params [definition]
* @Return java.lang.String
* @Author JiaChaoYang
* @Date 2022/9/12 9:24
*/
public String addRouteDefinition(RouteDefinition/*读取出来的配置会到这里,网关*/ definition){
log.info("gateway add route: {}", JSON.toJSONString(definition));
//先删初原来的路由再新增
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
/* 保存路由配置并发布 */
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
/* 发布事件通知给Gateway 同步新增路由定义 */
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/**
* @Description 根据路由id去删除路由配置
* @Params [id]
* @Return java.lang.String
* @Author JiaChaoYang
* @Date 2022/9/12 9:29
*/
private String deleteById(String id){
try {
log.info("gateway delete route id : {}",id);
this.routeDefinitionWriter.delete(Mono.just(id));
//发布事件通知给gateway 更新路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "delete success";
}catch (Exception e) {
log.error("gateway delete route fail: {}",e.getMessage(),e);
return "delete fail";
}
}
/**
* @Description 更新路由
* @Params [routeDefinitionList]
* @Return java.lang.String
* @Author JiaChaoYang
* @Date 2022/9/12 9:36
*/
public String updateList(List<RouteDefinition> routeDefinitionList){
log.info("gateway update route: {}",routeDefinitionList);
//拿到当前gateway 中存储的路由定义
List<RouteDefinition> routeDefinitions = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtils.isEmpty(routeDefinitions)){
//清除掉之前所有的旧的路由定义
routeDefinitions.forEach(rd ->{
log.info("delete route definition:",rd);
deleteById(rd.getId());
});
}
// 把更新的路由定义同步到gateway中
routeDefinitionList.forEach(definition -> updateByRouteDefinition(definition));
return "success";
}
/**
* @Description 更新路由,更新的实现策略比较简单:删除 + 新增 = 更新
* @Params [definition]
* @Return java.lang.String
* @Author JiaChaoYang
* @Date 2022/9/12 9:33
*/
private String updateByRouteDefinition(RouteDefinition definition){
try {
log.info("gateway update route : {}",definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
}catch (Exception e) {
return "update fail , not find route routeId:"+ definition.getId();
}
try {
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}catch (Exception e) {
return "update route fail";
}
}
}
步骤四、创建DynamicRouteServiceImplByNacos自动执行更新路由
/**
* @author hslypd
*/
@Slf4j
@Component
@DependsOn({"gatewayConfig"}) /*在另一个bean初始化后再初始化此bean*/
@SuppressWarnings("all")
public class DynamicRouteServiceImplByNacos {
/* Nacos配置服务客户端 */
private ConfigService configService;
@Resource
private DynamicRouteService dynamicRouteService;
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String nacosServerAddr;
@Value("${spring.cloud.nacos.discovery.namespace}")
private String nacosNamespace;
/**
* @Description 第一种方式:通过定时从nacos中获取实时的已启动服务列表,从而动态新增并更新路由
* @Params []
* @Return void
* @Author manman.wu
* @Date 2023/12/11 16:25
*/
// @PostConstruct //bean构造完成后,会立即执行
public void init() throws NacosException {
List<RouteDefinition> routeDefinitionList = new ArrayList<>();
List<JsonObject> child1List = new ArrayList<>();
log.info("gateway route init ......");
try {
//获取nacos上启动的服务自动生成gateway转发配置
NamingService namingService = createNacosInstance();
List<String> nacosServiceList = getServiceList(namingService);
JsonObject jsonObjectChild1 = new JsonObject();
jsonObjectChild1.addProperty("StripPrefix",1);
child1List.add(jsonObjectChild1);
List<FilterDefinition> filterDefinitionList = new ArrayList<>();
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setName("StripPrefix");
filterDefinition.addArg("parts","1");
filterDefinitionList.add(filterDefinition);
for(String serviceName : nacosServiceList) {
RouteDefinition routeDefinition = new RouteDefinition();
PredicateDefinition predicateDefinition = new PredicateDefinition();
List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
predicateDefinition.setName("Path");
predicateDefinition.addArg("pattern","/"+serviceName+"/**");
predicateDefinitionList.add(predicateDefinition);
routeDefinition.setId(serviceName);
URI uri = UriComponentsBuilder.fromUriString("lb://"+serviceName+"/").build().toUri();
routeDefinition.setUri(uri);
routeDefinition.setFilters(filterDefinitionList);
routeDefinition.setPredicates(predicateDefinitionList);
routeDefinitionList.add(routeDefinition);
}
if (routeDefinitionList.size()>0){
for (RouteDefinition definitionDefinition : routeDefinitionList){
log.info("init gateway config {}",definitionDefinition.toString());
dynamicRouteService.addRouteDefinition(definitionDefinition);
}
}
}catch (Exception e) {
log.error("gateway route init has some error: {}",e.getMessage(),e);
}
}
/**
* @Description 第二种方式,从nacos中拿到最新的gateway配置信息,实时更新路由
* @Params []
* @Return void
* @Author manman.wu
* @Date 2023/12/11 16:25
* @return
*/
@PostConstruct //bean构造完成后,会立即执行
private void fromNacosConfigUpdate() {
try {
if(StringUtils.isEmpty(GatewayConfig.NACOS_ROUTE_GROUP))
GatewayConfig.NACOS_ROUTE_GROUP = "";
configService = initConfigService();
if(configService == null){
log.warn("initConfigService 失败!");
return ;
}
String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATE_ID, GatewayConfig.NACOS_ROUTE_GROUP, 3000);
log.info("获取网关当前配置:\r\n{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
for(RouteDefinition definition : definitionList){
log.info("addRoute when startup : {}",definition.toString());
dynamicRouteService.addRouteDefinition(definition);
}
} catch (NacosException e) {
log.error("发生错误!", e);
}
//监听gateway配置信息,实时更新路由
dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATE_ID,GatewayConfig.NACOS_ROUTE_GROUP);
}
/**
* @Description 初始化Nacos Config
* @Params []
* @Return com.alibaba.nacos.api.config.ConfigService
*/
private ConfigService initConfigService() {
try {
//****如果配置开启了权限,需要在这里配置账号密码
Properties properties = new Properties();
properties.setProperty("serverAddr",GatewayConfig.NACOS_SERVER_ADDR); //地址
properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE); //命名空间
return configService = NacosFactory.createConfigService(properties); //创建配置服务
}catch (NacosException e) {
log.error("init gateway nacos config error: {}",e.getMessage(),e);
return null;
}
}
/**
* @Description 创建nacos客户端实例
* @Params []
* @Return List<String>
* @Author manman.wu
* @Date 2023/12/8 16:44
*/
private NamingService createNacosInstance() {
try {
Properties properties = new Properties();
properties.put("serverAddr", nacosServerAddr);
properties.put("namespace", nacosNamespace);
NamingService namingService = NamingFactory.createNamingService(properties);
return namingService; //创建配置服务
}catch (NacosException e) {
log.error("init nacos error: {}",e.getMessage(),e);
return null;
}
}
/**
* @Description 返回nacos服务列表
* @Params NamingService namingService 服务名
* @Return List<String>
* @Author manman.wu
* @Date 2023/12/8 16:44
*/
private List<String> getServiceList(NamingService namingService) {
try {
List<String> serviceList = namingService.getServicesOfServer(1,10000).getData();
return serviceList;
}catch (NacosException e) {
log.error("init nacos error: {}",e.getMessage(),e);
return null;
}
}
/**
* @Description 实现对nacos的监听,nacos下发的动态路由配置信息
* @Params [dataId, group]
* @Return void
*/
private void dynamicRouteByNacosListener(String dataId , String group){
try {
//给nacosconfig客户端增加一个监听器
configService.addListener(dataId, group, new Listener() {
//自己提供线程池执行操作
@Override
public Executor getExecutor() {
//为null是默认的线程池
return null;
}
/**
* @Description 监听器收到配置更新
* @Params [s] nacos中最新的配置定义
* @Return void
* @Author JiaChaoYang
* @Date 2022/9/13 11:21
*/
@Override
public void receiveConfigInfo(String s) {
log.info("start to update config : ",s);
//接收最新的路由定义配置
List definitionList = JSON.parseArray(s,RouteDefinition.class);
log.info("update route : {}",definitionList.toString());
//更新路由配置
dynamicRouteService.updateList(definitionList);
}
});
}catch (NacosException e) {
log.error("dynamic update gateway config error: {}",e.getMessage(),e);
}
}
}
步骤五、gateway配置文件yml中以下配置
server:
port: 9010
spring:
application:
name: demoGateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: d7baf882-5cdd-410c-a784-0a2ee160e826
group: DEFAULT_GROUP
enabled: true
gateway:
discovery:
locator:
enable: true
步骤六、执行项目,通过路由访问接口
方式二:通过定时识别nacos上的服务列表,给各个服务自动配置路由
上面的DynamicRouteServiceImplByNacos类中@PostConstruct注解改写到init()方法中即可