一、starter实现统一配置微服务文档
- 把Swagger配置中的公共部分抽取出来
- Swagger与SpringBoot整合中,可能会由于版本问题出现各种问题
1、制作starter
参考:
- 【SpringBoot】自定义启动器 Starter【保姆级教程】
- 用starter实现Oauth2中资源服务的统一配置
- 用starter实现api接口的加密与日志功能
- 用spring-boot-starter实现事务的统一配置
1> 总体结构图
2> 外部引用模块
tuwer-swagger-spring-boot-starter
- 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.tuwer</groupId>
<artifactId>tuwer-swagger-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<description>swagger配置starter</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 自动配置模块 -->
<dependency>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-swagger-spring-boot-starter-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3> 自动配置模块
- 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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-swagger-spring-boot-starter-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
<description>swagger自动配置模块</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 基础启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- actuator完善监控信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
</project>
- 自动配置类
不要加
@EnableWebMvc
package com.tuwer.config;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author 土味儿
* Date 2023/4/21
* @version 1.0
* 这里不要加EnableWebMvc注解;否则响应数据会出现乱码
*/
@Configuration
//@EnableWebMvc
@EnableConfigurationProperties(SwaggerProperty.class)
public class SwaggerConfig {
@Resource
SwaggerProperty swaggerProperty;
/**
* 配置Swagger具体参数
*
* @return
*/
@Bean
public Docket docket(Environment environment) {
// 允许启用Swagger的环境
//Profiles profile = Profiles.of("dev","test");
// 判断当前环境与指定环境是否一致
//boolean b = environment.acceptsProfiles(profile);
//return new Docket(DocumentationType.OAS_30)
return new Docket(DocumentationType.SWAGGER_2)
//.apiInfo(apiInfo("tuwer", "搜索服务", "v3.0"))
.apiInfo(apiInfo(swaggerProperty.getAuthor(), swaggerProperty.getTitle(), swaggerProperty.getVersion()))
// 根据当前环境判断是否启用Swagger
//.enable(b)
//.enable(true)
//.groupName("A")
// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.select()
// 只扫描特定包下的接口
.apis(RequestHandlerSelectors.basePackage(swaggerProperty.getBasePackage()))
//.apis(RequestHandlerSelectors.any())
//.apis(RequestHandlerSelectors.none())
//.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
//.apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
//.paths(PathSelectors.)
.build();
}
/**
* 自定义API文档信息
*
* @return
*/
private ApiInfo apiInfo(String author, String title, String version) {
// 作者信息
Contact contact = new Contact(author, "", "");
return new ApiInfo(
title + " API 文档",
"",
version,
"",
contact,
"",
"",
new ArrayList());
}
/**
* 增加如下配置可解决Spring Boot 2.6.x 与 Swagger 3.0.0 不兼容问题
* ------------------------------------------
* 还需要在yml中作如下配置
* mvc:
* pathmatch:
* matching-strategy: ant_path_matcher
**/
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier,
ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties,
Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
}
- 配置属性类
package com.tuwer.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* <p>配置属性类</p>
*
* @author 土味儿
* @version 1.0
* @Date 2023/4/22
*/
@Data
@ConfigurationProperties(prefix = "tuwer-swagger")
public class SwaggerProperty {
String author;
String title;
String version;
String basePackage;
}
2、微服务使用starter
1> 注入依赖
需要使用swagger的服务注入
<!-- Swagger -->
<dependency>
<groupId>com.tuwer</groupId>
<artifactId>tuwer-swagger-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2> 添加配置
spring:
mvc:
pathmatch:
# swagger3.0需要:ant_path_matcher
# springboot2.6后默认为:path_pattern_parser
matching-strategy: ant_path_matcher
# swagger配置
tuwer-swagger:
author: 作者
title: 服务名称
version: 版本
base-package: 基础包
3> 接口中添加API文档信息
API接口中添加
// 示例
@RestController
@Api(tags = "接口类描述")
public class TestApi {
@PostMapping("/...")
@ApiOperation("接口方法描述")
public String test(
@ApiParam("参数描述")
@RequestParam("id") Long id
) {
// ...
}
}
至此,用starter实现微服务的swagger配置完成!
二、网关聚合文档
使用
spring-cloud-gateway
网关把各个微服务的文档聚合起来,通过网关统一访问。参考:聚合微服务中的 Swagger API 文档
1、注入依赖
在网关服务中加入Swagger依赖
更加细致的设计思路:(网上有介绍)微服务中不需要Swagger的ui包,只要能生成api的json数据包,供网关抓取就可以。同时网关中也不用自已成生api的json数据包,也可以去掉一些相关的依赖包。
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
2、配置网关
不是所有的服务都通过网关转发。有些内部服务可以通过docker的内部网络直接访问,可以单独配置这类内部服务的API文档。
spring:
# 配置 Spring Cloud 相关属性
cloud:
# 配置 Spring Cloud Gateway 相关属性
gateway:
# 配置网关发现机制
discovery:
# 配置处理机制
locator:
enabled: false
# 开启服务名称小写转换(默认为false)
lower-case-service-id: true
# 路由配置
routes:
# 正常服务1
- id: server-1
uri: 转发地址
predicates:
- Path=/server_1/**
- Method=GET,POST
filters:
- name: StripPrefix
args:
# 过滤掉前1节
parts: 1
# 正常服务2
- id: server-2
uri: 转发地址
predicates:
- Path=/server_2/**
- Method=GET,POST
filters:
- name: StripPrefix
args:
# 过滤掉前1节
parts: 1
# swagger文档1
- id: swagger-docs-1
uri: 转发地址
predicates:
- Path=/swagger_1/**
- Method=GET,POST
filters:
- name: StripPrefix
args:
# 过滤掉前1节
parts: 1
# swagger文档2
- id: swagger-docs-2
uri: 转发地址
predicates:
- Path=/swagger_2/**
- Method=GET,POST
filters:
- name: StripPrefix
args:
# 过滤掉前1节
parts: 1
3、Swagger资源抓取类
从网关配置中过滤出Swagger的API文档配置信息
package com.tuwer.config;
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* <p>Swagger资源类</p>
*
* @author 土味儿
* @version 1.0
* @Date 2023/4/22
*/
@Component
@Primary
@AllArgsConstructor
@SuppressWarnings("all")
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
// 获取所有swagger文档路由的ID
routeLocator.getRoutes().subscribe(route -> {
String routeId = route.getId().toLowerCase(Locale.ROOT);
// swagger文档的路由ID中包含有:swagger-docs
if (routeId.contains("swagger-docs")) {
routes.add(route.getId());
}
});
//过滤出配置文件中定义的路由->过滤出Path Route Predicate->根据路径拼接成api-docs路径->生成SwaggerResource
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition ->
resources.add(
swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
// 把路径中后面的 ** 替换成文档地址:v2/api-docs
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
//log.info("name:{},location:{}", name, location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(SwaggerModel.getByName(name).cnName);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
/**
* 枚举类:中英文映射
*/
enum SwaggerModel {
DEFAULT("default", "默认"),
S_1("swagger-docs-1", "文档1"),
S_2("swagger-docs-2", "文档2");
private String name;
private String cnName;
SwaggerModel(String name, String cnName) {
this.name = name;
this.cnName = cnName;
}
public static SwaggerModel getByName(String name) {
for (SwaggerModel m : SwaggerModel.values()) {
if (Objects.equals(m.name, name)) {
//if(m.code == code){
return m;
}
}
return DEFAULT;
}
}
}