Spring Boot整合Nacos与Gateway实现前后端分离微服务
架构选择nacos作为服务注册,GateWay作为网关两者都是ali。那nacos咱们都知道可作为配置中心来使用且可动态,那做一个动态路由我觉得还是有些必要性的,不至于每次增加路由都要重新部署服务。
Spring Boot整合Gateway与阿里系微服务集成Maven配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bins.springboot.java</groupId>
<artifactId>gongfu</artifactId>
<version>0.0.1</version>
<name>gongfu</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--gateway-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<!--nacos dicovery-->
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependencies>
<dependencyManagement>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.2.RELEASE</version>
</dependencyManagement>
<build>
<plugins>
<plugin>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
一、代码整合
@Data
@Component
@ConfigurationProperties(prefix = "broad.dynamic.route")//yml配置信息,下文给出
@ConditionalOnBean(DynamicRouteConfiguration.class)
public class DynamicRouteProperties {
/**
* nacos 配置管理 dataId
*/
private String dataId;
* nacos 配置管理 group
private String group;
* nacos 服务地址
private String ipAddr;
* 启动动态路由的标志,默认关闭
private boolean enabled = false;
}
DynamicRouteConfiguration:
@Configuration
@ConditionalOnProperty(name = "broad.dynamic.route.enabled", matchIfMissing = true)
public class DynamicRouteConfiguration implements ApplicationEventPublisherAware {
Logger log= LoggerFactory.getLogger(DynamicRouteConfiguration.class);
//namespace名称
private final static String NAMESPACE="8359a8d9-03ab-4574-9627-9456351c0c01";
//nacos账号密码
private final static String USERNAME_PASSWORD="nacos";
//截取字符
private final static String REPLACE="routes:";
private DynamicRouteProperties bean;
private RouteDefinitionWriter writer;
private ApplicationEventPublisher publisher;
public DynamicRouteConfiguration(DynamicRouteProperties bean, RouteDefinitionWriter writer, ApplicationEventPublisher publisher) {
this.bean = bean;
this.writer = writer;
this.publisher = publisher;
}
@PostConstruct
private void init() {
Assert.notNull(bean.getDataId(), "broad.dynamic.route.dataId null异常");
Assert.notNull(bean.getGroup(), "broad.dynamic.route.group is null异常");
Assert.notNull(bean.getIpAddr(), "broad.dynamic.route.ipAddr is null异常");
dynamicRouteByNacosListener();
private void dynamicRouteByNacosListener() {
try {
Properties properties = new Properties();
properties.put(PropertyKeyConst.NAMESPACE, NAMESPACE);
properties.put(PropertyKeyConst.SERVER_ADDR, bean.getIpAddr());
properties.put("username",USERNAME_PASSWORD);
properties.put("password",USERNAME_PASSWORD);
ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfigAndSignListener(
bean.getDataId(),
bean.getGroup(),
5000,
new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
updateConfig(configInfo);
});
updateConfig(content);
} catch (NacosException e) {
log.error("nacos 获取动态路由配置和监听异常", e);
private void updateConfig(String content) {
String contentReally=replaceContext(content);
log.info("nacos 动态路由更新: {}", contentReally);
getRouteDefinitions(contentReally).forEach(routeDefinition -> {
log.info("动态路由配置: {}", routeDefinition);
writer.delete(Mono.just(routeDefinition.getId()));
writer.save(Mono.just(routeDefinition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception e) {
log.error("更新动态路由配置异常: ", e);
* 因在nacos配置路由携带 spring前缀,会导致yaml解析失败,因此需截取
* @return
private String replaceContext(String content){
String contentDelete=content.replaceAll("\r|\n", "").substring(0, content.indexOf(REPLACE)+1);
String contentReally=content.substring(contentDelete.length()+7, content.length());
return contentReally;
* yaml转换
* @param content
private List<RouteDefinition> getRouteDefinitions(String content) {
return JSON.parseArray(JSON.toJSONString(
YamlHelper.getInstance().load(content)
), RouteDefinition.class);
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
二、配置文件参数
server:
port: 8085
spring:
application:
name:gongfu
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: apiconsumer ##路由 ID(不是网关ID),保持唯一
#目标服务地址 (加 lb 根据注册中心服务名匹配)
uri: lb://provider1
predicates:
# #路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
- Path=/provider/**
#必须加上StripPrefix=1,否则访问服务时会带上provider
filters:
- StripPrefix=1
locator:
enabled: true #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
lower-case-service-id: true #是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了
三、启动类
启动类增加@EnableDiscoverClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GongfuApplication {
public static void main(String[] args) {
SpringApplication.run(GongfuApplication.class, args);
四、前后端分离解决跨域问题
(1)利用nginx反向代理,解决跨域问题
所有的请求都通过nginx,让 nginx 来区别请求是发送到前端还是网关。发送到网关的请求,让网关转发到相应的微服务。
(2)添加响应头
因为所有的请求都会经过 gateway 网关,在请求响应的过程中,经 gateway 网关给请求添加响应头,解决跨域问题。
public class GongfuCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource ubcs = new UrlBasedCorsConfigurationSource();
// 对于任意路径(/**)都要配置跨域
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 配置跨域
corsConfiguration.addAllowedHeader("*"); // Access-Control-Expose-Headers
corsConfiguration.addAllowedMethod("*"); // Access-Control-Allow-Methods
corsConfiguration.addAllowedOriginPattern("*"); // Access-Control-Allow-origin
corsConfiguration.setAllowCredentials(true); // Access-Control-Allow-Credentials
ubcs.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(ubcs);
这样就解决了Gateway+Nacos前后端分离中的问题了。