4.0.0 com.gateway code 0.0.1-SNAPSHOT gateway gateway UTF-8 1.7 1.7 2.4.1 2020.0.0 2021.1 org.springframework.boot spring-boot-starter-webflux org.springframework.boot spring-boot-starter-test org.springframework.cloud spring-cloud-starter-gateway org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.projectlombok lombok org.springframework.cloud spring-cloud-loadbalancer org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 UTF-8 org.springframework.boot spring-boot-maven-plugin 2.4.1 com.gateway.code.GatewayApplication repackage repackage
server: #此处的8085端口号,就好像以前外置的tomcat的8080,让我们通过浏览器进行访问 #但此服务只是做了一个路由,它会将请求路由到其它微服务(一般是消费者)进行处理 port: 8085 spring: application: #微服务名 name: gateway cloud: nacos: discovery: #指定nacos注册中心的地址 server-addr: 127.0.0.1:8848 gateway: discovery: locator: #localhost:8085/provider/user/aa #localhost:8085/consumer/user/aa #是否与服务发现组件进行结合,通过service-id(必须设置成大写)转发到具体的服务实例。默认false #为true代表开启基于服务发现的路由规则。 enabled: true #配置之后访问时service-id无需大写 lower-case-service-id: true # routes: # # 路由标识(id:标识,具有唯一性) # - id: user-consumer-api # #目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死 # uri: lb://consumer # #优先级,越小越优先 # #order: 999 # #路由条件(predicates:断言) # predicates: # # 路径匹配, # - Path=/aa/** # filters: # #路径前缀删除示例:请求/name/bar/foo,StripPrefix=2,去除掉前面两个前缀之后,最后转发到目标服务的路径为/foo # #前缀过滤,请求地址:http://localhost:8084/usr/hello # #此处配置去掉1个路径前缀,再配置上面的 Path=/usr/**,就将**转发到指定的微服务 # #因为这个api相当于是服务名,只是为了方便以后nginx的代码加上去的,对于服务提供者service-client来说,不需要这段地址,所以需要去掉 # - StripPrefix=1 # # 路由标识(id:标识,具有唯一性) # - id: user-provider-api # #目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死 # uri: lb://provider # #优先级,越小越优先 # #order: 999 # #路由条件(predicates:断言) # predicates: # # 路径匹配, # - Path=/bb/cc/** # filters: # #路径前缀删除示例:请求/name/bar/foo,StripPrefix=2,去除掉前面两个前缀之后,最后转发到目标服务的路径为/foo # #前缀过滤,请求地址:http://localhost:8084/usr/hello # #此处配置去掉1个路径前缀,再配置上面的 Path=/usr/**,就将**转发到指定的微服务 # #因为这个api相当于是服务名,只是为了方便以后nginx的代码加上去的,对于服务提供者service-client来说,不需要这段地址,所以需要去掉 # - StripPrefix=2 logging: level: #开启spring-Cloud-gateway的日志级别为debug,方便debug调试 org.springframework.cloud.gateway: trace org.springframework.http.server.reactive: debug org.springframework.web.reactive: debug reactor.ipc.netty: debug #springboot监控Actuator,暴露所有端点 management: endpoints: web: exposure: include: '*' #自定义配置 gateway: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} # namespace: xxx-xx-xx-xx data-id: dynamic-routing.json group: DEFAULT_GROUP
server: #此处的8085端口号,就好像以前外置的tomcat的8080,让我们通过浏览器进行访问 #但此服务只是做了一个路由,它会将请求路由到其它微服务(一般是消费者)进行处理 port: 8085 spring: application: #微服务名 name: gateway cloud: nacos: discovery: #指定nacos注册中心的地址 server-addr: 127.0.0.1:8848 gateway: discovery: locator: #localhost:8085/provider/user/aa #localhost:8085/consumer/user/aa #是否与服务发现组件进行结合,通过service-id(必须设置成大写)转发到具体的服务实例。默认false #为true代表开启基于服务发现的路由规则。 enabled: false #配置之后访问时service-id无需大写 lower-case-service-id: true routes: # 路由标识(id:标识,具有唯一性) - id: user-consumer-api #目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死 uri: lb://consumer #优先级,越小越优先 #order: 999 #路由条件(predicates:断言) predicates: # 路径匹配, #localhost:8085/user/aa 不能进 #localhost:8085/cum/user/aa 能进 - Path=/cum/** filters: #路径前缀删除示例:请求/name/bar/foo,StripPrefix=2,去除掉前面两个前缀之后,最后转发到目标服务的路径为/foo #前缀过滤,请求地址:http://localhost:8084/usr/hello #此处配置去掉1个路径前缀,再配置上面的 Path=/usr/**,就将**转发到指定的微服务 #因为这个api相当于是服务名,只是为了方便以后nginx的代码加上去的,对于服务提供者service-client来说,不需要这段地址,所以需要去掉 - StripPrefix=1 # 路由标识(id:标识,具有唯一性) - id: user-provider-api #目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死 uri: lb://provider #优先级,越小越优先 #order: 999 #路由条件(predicates:断言) predicates: # 路径匹配, #localhost:8085/api/prv/user/aa - Path=/api/prv/** filters: #路径前缀删除示例:请求/name/bar/foo,StripPrefix=2,去除掉前面两个前缀之后,最后转发到目标服务的路径为/foo #前缀过滤,请求地址:http://localhost:8084/usr/hello #此处配置去掉1个路径前缀,再配置上面的 Path=/usr/**,就将**转发到指定的微服务 #因为这个api相当于是服务名,只是为了方便以后nginx的代码加上去的,对于服务提供者service-client来说,不需要这段地址,所以需要去掉 - StripPrefix=2 logging: level: #开启spring-Cloud-gateway的日志级别为debug,方便debug调试 org.springframework.cloud.gateway: trace org.springframework.http.server.reactive: debug org.springframework.web.reactive: debug reactor.ipc.netty: debug #springboot监控Actuator,暴露所有端点 management: endpoints: web: exposure: include: '*' #自定义配置 gateway: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} # namespace: xxx-xx-xx-xx data-id: dynamic-routing.json group: DEFAULT_GROUP
gateway :nacos :server-addr : $ { spring.cloud.nacos.discovery.server-addr }# namespace: xxx-xx-xx-xxdata-id : dynamic-routing.jsongroup : DEFAULT_GROUP配合读取类使用
{
"refreshGatewayRoute": true,
"routeList": [
{
"id": "consumer-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/cum/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://consumer",
"order": 0
},
{
"id": "provider-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/api/pvr/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "2"
}
}
],
"uri": "lb://provider",
"order": 0
}
]
}
com.alibaba fastjson 1.2.35
package com.gateway.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ConfigurationProperties(prefix = "gateway.nacos")
@Component
public class GatewayNacosProperties {
private String serverAddr;
private String dataId;
private String namespace;
private String group;
}
(实体类中的属性对应配置内容中的json数据)
package com.gateway.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class RouteEntity {
//路由id
private String id;
//路由断言集合
private List predicates = new ArrayList<>();
//路由过滤器集合
private List filters = new ArrayList<>();
//路由转发的目标uri
private String uri;
//路由执行的顺序
private int order = 0;
}
package com.gateway.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class FilterEntity {
//过滤器对应的Name
private String name;
//路由规则
private Map args = new LinkedHashMap<>();
}
package com.gateway.code.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PredicateEntity {
//断言对应的Name
private String name;
//断言规则
private Map args = new LinkedHashMap<>();
}
package com.gateway.code;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gateway.code.pojo.FilterEntity;
import com.gateway.code.pojo.GatewayNacosProperties;
import com.gateway.code.pojo.PredicateEntity;
import com.gateway.code.pojo.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 此类实现了Spring Cloud Gateway + nacos 的动态路由,
* 它实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
*/
@SuppressWarnings("all")
@Slf4j
@Component
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private GatewayNacosProperties gatewayProperties;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 这个方法主要负责监听Nacos的配置变化,这里先使用参数构建一个ConfigService,
* 再使用ConfigService开启一个监听,
* 并且在监听的方法中刷新路由信息。
*/
@Bean
public void refreshRouting() throws NacosException {
//创建Properties配置类
Properties properties = new Properties();
System.out.println(gatewayProperties);
//设置nacos的服务器地址,从配置类GatewayProperties中获取
properties.put(PropertyKeyConst.SERVER_ADDR, gatewayProperties.getServerAddr());
//设置nacos的命名空间,表示从具体的命名空间中获取配置信息,不填代表默认从public获得
if (gatewayProperties.getNamespace() != null) {
properties.put(PropertyKeyConst.NAMESPACE, gatewayProperties.getNamespace());
}
//根据Properties配置创建ConfigService类
ConfigService configService = NacosFactory.createConfigService(properties);
//获得nacos中已有的路由配置
String json = configService.getConfig(gatewayProperties.getDataId(), gatewayProperties.getGroup(), 5000);
if(json == null){
return;
}
this.parseJson(json);
//添加监听器,监听nacos中的数据修改事件
configService.addListener(gatewayProperties.getDataId(), gatewayProperties.getGroup(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
/**
* 用于接收远端nacos中数据修改后的回调方法
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.warn(configInfo);
//获取nacos中修改的数据并进行转换
parseJson(configInfo);
}
});
}
/**
* 解析从nacos读取的路由配置信息(json格式)
*/
public void parseJson(String json) {
log.warn("从Nacos返回的路由配置(JSON格式):" + json);
boolean refreshGatewayRoute = JSONObject.parseObject(json).getBoolean("refreshGatewayRoute");
if (refreshGatewayRoute) {
List list = JSON.parseArray(JSONObject.parseObject(json).getString("routeList")).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
} else {
log.warn("路由未发生变更");
}
}
/**
* 路由更新
*/
public void update(RouteDefinition routeDefinition) {
try {
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
log.warn("路由删除成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.warn("路由更新成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 根据自己的路由实体类生成gateway的路由定义对象
*/
public RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates
List pdList = new ArrayList<>();
for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters
List fdList = new ArrayList<>();
for (FilterEntity filterEntity : routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
运行效果如下: