前言

Nacos: 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。–>Nacos
Spring Cloud Gateway: 该项目提供了一个用于在Spring WebFlux之上构建API网关的库。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/度量和弹性–>Srping Cloud Gateway

概念介绍

这一模块留待后续补充,目前先介绍项目搭建过程。

Nacos

Nacos配置见–>Nacos-server,可以参考阿里官方文档–>Nacos手册。这里我在本地使用单机模式启动并连接本地数据库。

在nacos中,与gateway相关的文件有三个:

  1. Application-dev.properties
  2. Gateway-dev.properties
  3. gateway-router

第一个文件是所有服务通用的配置,例如:redis,knife4j等等可以通用的配置。第二个文件作为有Gateway服务特有的配置文件,例如:端口号,数据库地址等等。第三个文件是Gateway路由文件,通过这个文件可以实现动态路由的功能。为了方便,这三个文件我都创建在了默认分组下。

前两个文件的内容就不贴出来了,gateway-router内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[{
"id": "api",
"order": 0,
"predicates": [{
"args": {
"pattern": "/api/**"
},
"name": "Path"
}],
"filters": [{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://shiming-gateway"
},{
"id": "auth-router",
"order": 1,
"predicates": [{
"args": {
"_genkey_0": "/shiming-auth/**"
},
"name": "Path"
}],
"filters": [{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://shiming-auth"
},{
"id": "system",
"order": 2,
"predicates": [{
"args": {
"pattern": "/community-system/**"
},
"name": "Path"
}],
"filters": [{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://community-system"
}]

其中,api部分是Gateway服务的路由,auth部分是权限服务,以后再介绍,这里忽略。system部分是系统服务即服务提供者。

具体解释可以看这篇文章–>SpringCloud gateway (史上最全)
Gateway yml格式配置转为json格式见–>gateway yml配置文件转成json

Gateway

接下来步入正题,开始Gateway模块的搭建:

导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- nacos组件 -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- gateway依赖,注意:Gateway基于WebFlux引擎,不需要导入SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

创建配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 从pom.xml中获取名称和环境
spring.application.name=@project.artifactId@
spring.profiles.active=@profiles.active@
spring.main.allow-bean-definition-overriding=true
# nacos
spring.cloud.nacos.discovery.server-addr=@profiles.nacos@
spring.cloud.nacos.config.server-addr=@profiles.nacos@
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.shared-dataids=application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
spring.cloud.nacos.config.refreshable-dataids=application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# gateway-router
nacos.gateway.route.config.data-id=gateway-router
# cors
spring.cloud.gateway.globalcors.corsConfigurations.'[/**]'.allowedOrigins=*
spring.cloud.gateway.globalcors.corsConfigurations.'[/**]'.allowedMethods=[GET,POST,PUT,DELETE]

编写代码

路由配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
**
* 路由类配置
*/
@Configuration
public class GatewayConfig {
public static final long DEFAULT_TIMEOUT = 30000;

public static String NACOS_SERVER_ADDR;

public static String NACOS_NAMESPACE;

public static String NACOS_ROUTE_DATA_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_DATA_ID = nacosRouteDataId;
}

@Value("${nacos.gateway.route.config.group:DEFAULT_GROUP}")
public void setNacosRouteGroup(String nacosRouteGroup){
NACOS_ROUTE_GROUP = nacosRouteGroup;
}

}

动态路由实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* 动态更新路由网关service
* 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
* 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
*/
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;

/**
* 发布事件
*/
@Autowired
private ApplicationEventPublisher publisher;

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

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

/**
* 更新路由
* @param definitions
* @return
*/
public String updateList(List<RouteDefinition> definitions) {
log.info("gateway update route {}",definitions);
// 删除缓存routerDefinition
List<RouteDefinition> routeDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
routeDefinitionsExits.forEach(routeDefinition -> {
log.info("delete routeDefinition:{}", routeDefinition);
delete(routeDefinition.getId());
});
}
definitions.forEach(definition -> {
updateById(definition);
});
return "success";
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
**
*
* 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
*
*/
@Component
@Slf4j
@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {

@Autowired
private DynamicRouteServiceImpl dynamicRouteService;


private ConfigService configService;

@PostConstruct
public void init() {
log.info("gateway route init...");
try{
configService = initConfigService();
if(configService == null){
log.warn("initConfigService fail");
return;
}
String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
log.info("获取网关当前配置:\r\n{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
for(RouteDefinition definition : definitionList){
log.info("update route : {}",definition.toString());
dynamicRouteService.add(definition);
}
} catch (Exception e) {
log.error("初始化网关路由时发生错误",e);
}
dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
}

/**
* 监听Nacos下发的动态路由配置
* @param dataId
* @param group
*/
public void dynamicRouteByNacosListener (String dataId, String group){
try {
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("进行网关更新:\n\r{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("update route : {}",definitionList.toString());
dynamicRouteService.updateList(definitionList);
}
@Override
public Executor getExecutor() {
log.info("getExecutor\n\r");
return null;
}
});
} catch (NacosException e) {
log.error("从nacos接收动态路由配置出错!!!",e);
}
}

/**
* 初始化网关路由 nacos config
* @return
*/
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 (Exception e) {
log.error("初始化网关路由时发生错误",e);
return null;
}
}
}

最后在启动类上添加两个注解

1
2
@SpringBootApplication
@SpringCloudApplication

就可以通过网关访问其他模块微服务了

20210123214854

在上图中,我的网关端口为18080,网关路由为api,服务提供者路由为commmunity-sytem。网关访问服务后会自动减去路由访问下一层。

所以通过http://localhost:18080/api/community-system,这个请求被转发到18082端口的系统管理服务,并最终调用getUserInfo接口返回结果。

参考

Nacos+Spring Cloud Gateway动态路由配置