在开发过程中, 多个微服务, 多个接口文档时,发现每次访问都很麻烦;所以根据网上的一些资料, 做了gateway聚合swagger2;spring cloud 版本: 2.1.3, 服务发现使用的是nacos;spring cloud项目新建就先略过;
目录如下:
spring-cloud-nooyoo
|- nooyoo-auth (服务1 端口:8080)
|- nooyoo-user (服务2 端口:8081)
|- nooyoo-gateway (网关 端口:7000)
项目pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nooyoo</groupId>
<artifactId>spring-cloud-nooyoo</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<name>spring-cloud-nooyoo</name>
<modules>
<module>nooyoo-auth</module>
<module>nooyoo-user</module>
<module>nooyoo-gateway</module>
</modules>
<properties>
<spring-boot.version>2.1.3.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<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.3</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<!--阿里云主仓库,代理了maven central和jcenter仓库-->
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--阿里云代理Spring 官方仓库-->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://maven.aliyun.com/repository/spring</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<!--阿里云代理Spring 插件仓库-->
<pluginRepository>
<id>spring-plugin</id>
<name>spring-plugin</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
首先是nooyoo-auth模块, 新增swagger2配置:
/**
* Created by NeeYoo.
* Created on 2019/7/29 下午9:50.
* Description: swagger文档配置
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean(name = "nooyoo-auth")
public Docket createRestApi() {
//=====添加head参数start============================
ParameterBuilder parameter1 = new ParameterBuilder();
ParameterBuilder parameter2 = new ParameterBuilder();
ParameterBuilder parameter3 = new ParameterBuilder();
List<Parameter> pars = new ArrayList<Parameter>();
parameter1.name("Authorization").description("Authorization令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
parameter2.name("userId").description("用户Id").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
parameter3.name("userPhone").description("用户电话").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(parameter1.build());
pars.add(parameter2.build());
pars.add(parameter3.build());
// =========添加head参数end===================
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.nooyoo"))
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(pars).groupName("nooyoo-auth"); // 分组
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("auth-接口文档")
.description("接口文档-鉴权服务")
.termsOfServiceUrl("http://localhost:8080/auth/doc.html")
.contact(new Contact("nooyoo", "", ""))
.version("v1.0")
.build();
}
}
nooyoo-auth模块的pom.xml
<?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-nooyoo</artifactId>
<groupId>com.nooyoo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>nooyoo.auth</groupId>
<artifactId>nooyoo-auth</artifactId>
<version>1.0</version>
<dependencies>
<!--Nacos-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerArguments>
<verbose />
<bootclasspath>${java.home}\lib\rt.jar;${java.home}\lib\jce.jar</bootclasspath>
</compilerArguments>
<compilerArgs>
<arg>-extdirs</arg>
<arg>${project.basedir}/src/lib/*.jar</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
写一个controller, 后面测试用
/**
* Created by NooYoo.
* Created on 2019/7/29 下午9:31.
* Description:
*/
@Slf4j
@RestController
@Api(tags = "auth-接口文档")
public class TestController {
@ApiOperation(value = "auth-测试接口", notes = "测试接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "a", paramType = "path", value = "数字a", required = true, dataType = "path"),
@ApiImplicitParam(name = "b", paramType = "path", value = "数字b", required = true, dataType = "path")
})
@GetMapping("/{a}/{b}")
public Integer get(@PathVariable Integer a, @PathVariable Integer b) {
log.info("auth文档访问");
return a + b;
}
}
同理操作neeyoo-user模块, 你也可以随便写2个微服务, 主要是通过网关能直接做不同微服务间的测试;
然后启动微服务, 先单独访问各自的接口文档(文档接口用了swgger的bootstrapUI, 访问地址是:localhost:8080/user/doc.html),记得把neeyoo-user模块中swagger配置改成这个groupName("nooyoo-user")
下面就是关键点, neeyoo-gateway模块中, 目录结构如下:
关键代码, 直接上代码:
# 网关配置
spring:
profiles: local
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
file-extension: yml
gateway:
locator:
enabled: true
routes:
- id: nooyoo-auth
uri: lb://nooyoo-auth
predicates:
- Path=/auth/**
filter:
- SwaggerHeaderFilter
- StripPrefix = 1
- id: nooyoo-user
uri: lb://nooyoo-user
predicates:
- Path=/user/**
filter:
- SwaggerHeaderFilter
- StripPrefix = 1
/**
* Created by NooYoo.
* Created on 2019/7/29 下午10:39.
* Description: Swagger-ui需要依赖的一些接口
*/
@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)));
}
}
/**
* Created by NooYoo.
* Created on 2019/7/30 下午10:09.
* Description: 拦截器
*/
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String HOST_NAME = "X-Forwarded-Host";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
/**
* Created by NooYoo.
* Created on 2019/7/29 下午10:33.
* Description: 因为Swagger暂不支持webflux项目,所以Gateway里不能配置SwaggerConfig,也就是说Gateway无法提供自身API。
* 配置SwaggerProvider,获取Api-doc,即SwaggerResources
*/
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = "/v2/api-docs";
public static final String NEW_API_URI = "/v2/api-docs?group="; // 这里访问设置的分组文档
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//取出gateway的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//结合配置的route-路径(Path),和route过滤,只获取有效的route节点
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", NEW_API_URI + routeDefinition.getId())))));
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;
}
}
我发现当我通过gateway访问文档时, 接口报404错误, 于是我看是什么接口导致的访问异常,后来看到这样一个接口:localhost:7000/auth/v2/api-docs?group=nooyoo-auth(直接访问该接口是有json数据返回的), 于是打断点调试, 在SwaggerProvider.java中返回的路径并没被替换掉,原来的写法是这样
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", API_URI)))));
于是我就将API_URI重新替换为: NEW_API_URI + routeDefinition.getId()
最后重新调试, 搞定, 在替换url那里, 取来一个巧, 文档的groupName跟gateway中routeid一样, 最终也达到来想要的效果;
最后上一个效果图: