SpringCloudAlibaba项目搭建nacos+gateway
前言
记录一下SpringCloudAlibaba搭建过程,避免经常忘记!因为懒,记录下来以后自己抄自己 为了此结构长期可扩展性这里就集成nacos+gateway的基本结构就算完工,其余组件用的时候再说吧~nacos可以动态配置,负载均衡!gateway可以路由,过滤,拦截,限流,降级,溶断等各种功能,其实也不算半成品了。很多公司就是这么用的,如果集成一大堆组件,反而把人弄得晕呼呼的!
关于nacos
我这里直接使用docker部署了一个nacos-server!当然也可以直接clone项目直接启动nacos,或者直接把代码集成到自己项目,想怎么玩都行
关于nacos在docker下的部署请看:docker安装nacos并配置mysql数据库(docker系列六)
SpringCloudAlibaba项目搭建
创建父工程配置pom
创建一个maven项目作为父工程
大概就是下图的样子,删除src目录(因为父工程不需要启动,只是作为maven全局版本控制器使用)
父工程pom文件配置如下
注释都写在pom文件里面了,自己看吧
<?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>
<!-- 描述此pom打包方式为父类型,如果不配置默认jar(所以其他子工程不需要设置,默认即可),如果需要外部部署子工程可设置war-->
<packaging>pom</packaging>
<!-- 全局Springboot版本(包括test,web,starter等)与SpringCloud Greenwich兼容(SpringCloud版本在2020.0.X之前使用姓名作为版本,从A,B,C这样的顺序排列),Greenwich版本以上集成gateway有问题,目前没解决,所有退回来了哈哈 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
<!-- maven坐标配置及此项目版本号 -->
<groupId>com.zbdemo</groupId>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 项目名称(使用坐标id或者自定义),描述,地址 -->
<name>${project.artifactId}</name>
<description>这是一个简单的项目描述</description>
<url>http://www.zbdemo.com</url>
<!-- 引入jar包版本控制 -->
<properties>
<java.version>1.8</java.version>
<!-- 与Springboot2.1.x兼容(这个版本以上集成gateway有问题,目前没解决,所有退回来了哈哈) -->
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
<lombok.version>1.18.22</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<!--SpringCloud版本全局控制(即使使用SpringCloudAlibaba也要加这个)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloudAlibaba版本全局控制-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 受管理的子依赖(下面三个是后续要增加的子项目) -->
<modules>
<module>commons</module>
<module>user</module>
<module>gateway</module>
</modules>
<!-- 描述打包方式 必须加在父工程而不是commons,否则其他工程引用不到,当然你也可以每个需要打包的项目都引一次-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建公共工程commons
创建一个maven新的
在父工程右键->new->modul创建一个子maven工程,命名为commons(意为公共工程)
**
创建好之后结构修改成如下格式:如果要在commons包配置相关公共bean,那么所有引用commons的的子项目包名请跟commons保持一致!如:com.zbdemo.commons的包名那么引用他的子模块包名也得是这,否则bean引用会报错!我这里演示没有配置公共bean,所以就用项目名来命名包名了!!!!!!!别跟我学,包名最好一样
**
commons工程pom配置如下
注释都写在pom文件里面了,自己看吧?
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<groupId>com.zbdemo</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>commons</artifactId>
<name>${project.artifactId}</name>
<url>http://www.zbdemo.com</url>
<!-- 在公用项目引入依赖,在父工程全局控制版本 -->
<dependencies>
<!-- springboot版本父工程已控制不用写版本号 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringCloudAlibaba版本父工程已控制不用写版本号 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- lombook 插件(为了演示父工程与公共子工程之间的版本控制关系,引入一个lombok作为示例)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- swagger2依赖(为了演示公共工程如何做公共配置引入swagger2做演示) -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 使用bootstrap-ui -->
<!-- <dependency>-->
<!-- <groupId>io.springfox</groupId>-->
<!-- <artifactId>springfox-swagger-ui</artifactId>-->
<!-- <version>2.9.2</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</project>
修改commons工程的pom文件后继续配置公共的swagger2
Swagger2Config文件内容如下:
import io.swagger.annotations.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2Config {
/**
* 通过 createRestApi函数来构建一个DocketBean
* 函数名,可以随意命名,喜欢什么命名就什么命名
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())//调用apiInfo方法,创建一个ApiInfo实例,里面是展示在文档页面信息内容
.select()
//控制暴露出去的路径下的实例
//如果某个接口不想暴露,可以使用以下注解
//@ApiIgnore 这样,该接口就不会暴露在 swagger2 的页面下
// .apis(RequestHandlerSelectors.basePackage("com.zbdemo"))
// 这里不配置具体路径,扫描所有带有swagger注解的controller
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
.build();
}
//构建 api文档的详细信息函数
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//页面标题
.title(" Swagger2 构建RESTful API")
//条款地址
.termsOfServiceUrl("http://www.zbdemo.com")
.version("1.0")
//描述
.description("spring-cloud-alibaba-demo")
.build();
}
}
创建普通服务工程user
按照commons工程创建的方式再创建一个user子工程作为普通的client服务测试一下效果
创建完成后结构修改成如下:
user工程pom配置如下
注释都写在pom文件里面了,自己看吧?
user工程依赖commons之后就会继承commons的依赖:如lombook,swagger2,springboot,springcloud等依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<groupId>com.zbdemo</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user</artifactId>
<name>${project.artifactId}</name>
<url>http://www.zbdemo.com</url>
<dependencies>
<!--依赖commons-->
<dependency>
<groupId>com.zbdemo</groupId>
<artifactId>commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
接下来创建一个resources文件夹,在里面创建yml配置文件
application.yml配置文件内容如下:
# 指定user工程端口号
server:
port: 8001
# 配置挂载的子配置文件,比如我这里创建两,一个是dev开发使用,一个是pro生产环境使用
spring:
profiles:
active: dev
application-dev.yml配置文件内容如下:
Spring:
cloud:
nacos:
discovery:
# 指定你的nacos服务注册中心地址和端口,更多配置自行查询文档
server-addr: 192.168.101.1:8848
# 指定服务名称
application:
name: user
关于配置文件可以把其他配置内容放在nacos,然后通过这里引入配置,为了演示方便我就不做了
UserApplication启动类如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
// SpringBoot启动类注解
@SpringBootApplication
// SpringCloud客户端注解
@EnableDiscoveryClient
// 这个注解开启swagger2,swagger2配置已在commons工程做好,这里直接开启就行
@EnableSwagger2
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
接下来在user工程写个controller测试一下效果:
UserController内容如下:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class UserController {
@GetMapping(value = "/hello")
public String hello() {
return "hello word";
}
}
最后启动user工程,测试效果
启动成功效果图:
查看注册中心,user已经成功注册到nacos:
调用我们刚刚写的接口测试效果:localhost:8001/hello/hello 调用成功
然后调用swagger-ui测试集成效果:localhost:8001/doc.html 效果完美
创建路由网关工程gateway
创建过程与前面相似,这里不重复了,创建完成后结构修改如下:
修改gateway工程pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<groupId>com.zbdemo</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<name>gateway</name>
<url>http://www.zbdemo.com</url>
<!--这里不要引用commons模块,否则spring-boot-starter-web包和gateway中引用的spring-boot-starter-web会冲突报错-->
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloudAlibaba版本父工程已控制不用写版本号 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- swagger2依赖 (网关不写业务,但是需要聚合所有服务的接口方便查看)-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</project>
老规矩,创建resources文件夹,配置yml
application.yml配置文件内容如下:
server:
port: 8000
spring:
profiles:
active: dev
端口定为8000
application-dev.yml配置文件内容如下:
Spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 192.168.101.1:8848
gateway:
discovery:
locator:
enabled: true #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
lower-case-service-id: true #是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了
routes:
-id: user #自定义id,无意义但不能重复,最好跟服务名保持一致
uri: lb://user # 注册中心中的服务吗
predicates:
- Path=/user/** # 转发该路径
filters:
- StripPrefix=1 #必须加上StripPrefix=1,否则访问服务时会带上user
# 经过gateWay网关时,需要在网关统一配置跨域请求,全部通过
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
- DELETE
- PUT
- OPTION
GatewayApplication启动类如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
启动Gateway服务测试路由效果
成功注册到nacos
访问:localhost:8000/user/hello/hello 测试路由转发效果
gateway聚合swagger2接口文档
在gateway项目中创建文件如下:
SwaggerProvider内容:
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
/**
* swagger的api json文档路径
*/
public static final String API_URI = "/v2/api-docs";
/**
* Eureka发现功能的方法的名字,注册的服务会加入这个前缀
*/
public static final String EUREKA_SUB_PFIX = "CompositeDiscoveryClient_";
/**
* 服务发现的路由处理器
*/
private final DiscoveryClientRouteDefinitionLocator routeLocator;
public SwaggerProvider(DiscoveryClientRouteDefinitionLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//从DiscoveryClientRouteDefinitionLocator 中取出routes,构造成swaggerResource
routeLocator.getRouteDefinitions().subscribe(routeDefinition -> {
resources.add(swaggerResource(
//获取id(服务注册的id)
routeDefinition.getId()
//去除CompositeDiscoveryClient_前缀
.substring(EUREKA_SUB_PFIX.length()),
//获取路由定义信息列表
routeDefinition.getPredicates()
//获取路径信息PredicateDefinition{name='Path', args={pattern=/byb-provider2/**}}
.get(0)
.getArgs()
//将pattern中的/**替换为服务swagger文档路径
.get("pattern")
.replace("/**", API_URI)));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
SwaggerHandler内容:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
// 请使用更新后的的方法
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
2023-10-22更新:原来的的方式在刷新页面后第二次无法正常获取接口资源目录,所以这里人工保存一下资源目录
private Mono<ResponseEntity> mono;
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
// 存储返回的资源目录,因为下一次进入资源目录获取不到
if (mono == null){
mono = Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
return mono;
}
重启gateway测试接口聚合效果
访问:http://localhost:8000/doc.html
可以看到已经成功检索到user服务接口内容,后续添加其他服务也会检索进gateway的swagger文档
其他内容后续可能会另起一篇作为增量,目前这篇就到此为止,太多太杂容易让人迷失
演示代码拉取地址:我知道你想要