1.创建SpringCloud GateWay ,并在yaml文件中配置好nacos
spring:
application:
name: e-commerce-gateway
cloud:
nacos:
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
namespace: ....................
server-addr: .....................
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:
gateway:
route:
config:
data-id: e-commerce-gateway-route
group: e-commerce
2.在nacos中为网关服务创建配置管理
3.在网关模块中编写一个实体类,以便从配置文件中读取配置信息
/**
* 1. @ClassDescription:nacos相关信息
* 2. @author: Dongkoer
* 3. @date: 2023年10月19日 11:07
*/
@Configuration
public class GatewayConfig {
//读取配置的超时时间
public static final long DEFAULT_TIMEOUT = 30000;
//命名空间
public static String namespace;
//服务器地址
public static String serverAddr;
//dataID
public static String nacosRouteDataId;
//分组ID
public static String nacosRouteGroup;
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNamespace(String namespace) {
GatewayConfig.namespace = namespace;
}
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setServerAddr(String serverAddr) {
GatewayConfig.serverAddr = serverAddr;
}
@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId) {
GatewayConfig.nacosRouteDataId = nacosRouteDataId;
}
@Value("${nacos.gateway.route.config.group}")
public void setNacosRouteGroup(String nacosRouteGroup) {
GatewayConfig.nacosRouteGroup = nacosRouteGroup;
}
}
4.编写动态更新路由网关相关工具类
@RequiredArgsConstructor
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
//写路由定义
private final RouteDefinitionWriter routeDefinitionWriter;
//获取路由定义
private final RouteDefinitionLocator routeDefinitionLocator;
//事件发布
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
//完成事件推送句柄的初始化
this.publisher=applicationEventPublisher;
}
/**
* 新增路由
*/
public String addRouteDefinition(RouteDefinition definition){
log.info("gateway add route : [{}]",definition);
//保存路由配置并发布
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
//发布事件通知给Gateway,同步新增的路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "add success";
}
/**
* 删除路由
*/
public String deleteRouteById(String id){
try {
log.info("gateway delete route id:[{}]",id);
routeDefinitionWriter.delete(Mono.just(id)).subscribe();
//发布事件通知给GateWay,同步更新路由定义
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "delete success";
}catch (Exception ex){
log.error("gateway delete route fail:[{}]", ex.getMessage(),ex);
return "delete fail";
}
}
/**
* 更新路由:删除+新增=更新
*/
public 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";
}
}
/**
* 批量更新
*/
public String updateList(List<RouteDefinition> definitions){
log.info("gateway update route:[{}]",definitions);
//先拿到当前gateway中存储的路由定义
List<RouteDefinition> routeDefinitions = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtil.isEmpty(routeDefinitions)){
//如果gateway中存在旧的路由定义,那么要清除掉
routeDefinitions.forEach(rd->{
log.info("delete route definition:[{}]",rd);
deleteRouteById(rd.getId());
});
}
//把更新的路由定义同步到gateway中
definitions.forEach(definition -> updateByRouteDefinition(definition));
return "success";
}
}
可以看到,此类中,都是对网关存储的配置信息进行增删改查,以便我们读取到nacos的信息之后,对网关中的“旧数据”进行操作;
5.编写网关与nacos连接类
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 1. @ClassDescription:
* 2. @author: Dongkoer
* 3. @date: 2023年10月19日 15:45
*/
@Slf4j
@Component
@DependsOn({"gatewayConfig"})//依赖,要先等gatewayConfig先注入之后,自己才能注入
public class DynamicRouteServiceImplByNacos {
//nacos配置服务
private ConfigService configService;
@Autowired
DynamicRouteServiceImpl dynamicRouteService;
@PostConstruct//bean初始化完成之后立马执行此方法
public void init() {
log.info("gateway route init...");
try {
//初始化Nacos配置客户端
configService=initConfigService();
if (null == configService){
log.error("init config service fail");
return;
}
//通过Nacos Config 并指定路由配置路径去获取路由配置
String config = configService.getConfig(
GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup,
GatewayConfig.DEFAULT_TIMEOUT
);
log.info("get current gateway config :[{}]",config);
List<RouteDefinition> definitionList = JSON.parseArray(config, RouteDefinition.class);
if (CollectionUtil.isNotEmpty(definitionList)){
for (RouteDefinition routeDefinition : definitionList) {
log.info("init gateWay config :[{}]",routeDefinition.toString());
dynamicRouteService.addRouteDefinition(routeDefinition);
}
}
} catch (Exception e) {
log.error("gateway route has some error:[{}]",e.getMessage(),e);
}
//设置监听器
dynamicRouteByNacosListener(GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup);
}
/**
* 连接nacos
* @return
*/
private ConfigService initConfigService() {
Properties properties = new Properties();
properties.setProperty("serverAddr", GatewayConfig.serverAddr);
properties.setProperty("namespace", GatewayConfig.namespace);
try {
return configService = NacosFactory.createConfigService(properties);
} catch (NacosException e) {
log.error("init gateway nacos config error:[{}]", e.getMessage(), e);
return null;
}
}
/**
* 监听Nacos下发的动态路由配置
*
* @param dataId
* @param group
*/
private void dynamicRouteByNacosListener(String dataId, String group) {
try {
configService.addListener(dataId, group, new Listener() {
//可以自定义线程池来操作
@Override
public Executor getExecutor() {
return null;
}
//此时传过来的s就是Nacos中最新的配置
@Override
public void receiveConfigInfo(String s) {
log.info("start to update config:[{}]", s);
List<RouteDefinition> 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);
}
}
}
6.对init方法的讲解
@PostConstruct//bean初始化完成之后立马执行此方法
public void init() {
log.info("gateway route init...");
try {
//初始化Nacos配置客户端
configService=initConfigService();
if (null == configService){
log.error("init config service fail");
return;
}
//通过Nacos Config 并指定路由配置路径去获取路由配置
String config = configService.getConfig(
GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup,
GatewayConfig.DEFAULT_TIMEOUT
);
log.info("get current gateway config :[{}]",config);
List<RouteDefinition> definitionList = JSON.parseArray(config, RouteDefinition.class);
if (CollectionUtil.isNotEmpty(definitionList)){
for (RouteDefinition routeDefinition : definitionList) {
log.info("init gateWay config :[{}]",routeDefinition.toString());
dynamicRouteService.addRouteDefinition(routeDefinition);
}
}
} catch (Exception e) {
log.error("gateway route has some error:[{}]",e.getMessage(),e);
}
//设置监听器
dynamicRouteByNacosListener(GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup);
}
在咱们的spring容器加载完bean之后,会立马执行此方法,其中,我们调用initConfigService()方法连接到Nacos,并获取到相应配置信息,完成configService的初始化
try {
//初始化Nacos配置客户端
configService=initConfigService();
if (null == configService){
log.error("init config service fail");
return;
}
然后我们通过congigservice中的本地nacos连接信息,获取到我们配置在nacos中对应的配置信息:
//通过Nacos Config 并指定路由配置路径去获取路由配置
String config = configService.getConfig(
GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup,
GatewayConfig.DEFAULT_TIMEOUT
);
随后,我们对字符串的数据进行解析,如果能解析到数据,就说明我们的nacos中有相关配置信息,我们需要在网关中设置信息其中的addRouteDefiniton方法,就是将拉取到的信息,设置在网关中
log.info("get current gateway config :[{}]",config);
List<RouteDefinition> definitionList = JSON.parseArray(config, RouteDefinition.class);
if (CollectionUtil.isNotEmpty(definitionList)){
for (RouteDefinition routeDefinition : definitionList) {
log.info("init gateWay config :[{}]",routeDefinition.toString());
dynamicRouteService.addRouteDefinition(routeDefinition);
}
}
} catch (Exception e) {
log.error("gateway route has some error:[{}]",e.getMessage(),e);
}
最后,我们配置监听器,如果此配置文件有改动,那么我们的网关配置信息跟着改动
//设置监听器
dynamicRouteByNacosListener(GatewayConfig.nacosRouteDataId,
GatewayConfig.nacosRouteGroup);
}