微服务项目网关中集成swagger并使用knife4j进行增强
前言
本文场景为:
使用SpringCloud框架,MyBatisPlus持久层框架;注册中心:nacos,配置中心:nacos;主要模块有:业务模块、网关模块、common模块(共享);网关路由从配置中心动态拉取;其他情况动态调整。
- spring-boot-start版本:2.7.12(3版本以上只支持OpenApi3规范,差距较大)
- 使用OpenApi2规范
- spring-cloud版本:2021.0.3
- spring-cloud-alibaba版本:2021.0.4.0
- knife4j版本:4.1.0
- knife4j-gateway版本:4.4.0
配置完成后使用以下网址打开接口文档:
网关服务ip:网关接口/doc.html
common模块
引入依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-gateway-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
编写配置文件
在resources目录下,新建文件夹META-INF,新建文件spring.factories,配置文件中加入以下内容
此为SpringBoot项目中的自动装配文件,需要确保能扫描到SwaggerConfig类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.bbober.common.config.SwaggerConfig
编写配置类
配置文件读取类:
@Data
@ConfigurationProperties(prefix = "bbober.swagger")
public class SwaggerProperties {
private String title;
private String desc;
private String packagePath;
private String version;
}
配置类:
@ConditionalOnClass注解控制此类在只有加载了MyBatisPlus相关类时才进行加载,目的为让该配置类在网关服务中不生效
@Configuration
@ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})
@EnableConfigurationProperties(SwaggerProperties.class)
@RequiredArgsConstructor
public class SwaggerConfig {
private final SwaggerProperties swaggerProperties;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// api过滤器,设置哪些接口会加载创建接口文档
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getPackagePath()))
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDesc())
//个人信息分别为创建人、网址、邮箱
.contact(new Contact("Bbober", "", "tgu_bbober@outlook.com"))
.version(swaggerProperties.getVersion())
.build();
}
}
业务模块
本文中使用用户模块作为示例:
引入依赖
common为上文中的模块,其中包含了使用swagger的两个相关依赖
nacos以及bootstrap依赖为使用nacos进行服务中心和配置中心的依赖
注:mybatis依赖为控制common模块中是否加载配置类的条件,如没有使用该框架则对其他的只存在于业务模块且不存在于网关模块的Bean对象进行控制
<!--common-->
<dependency>
<groupId>com.bbober</groupId>
<artifactId>strategy-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--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>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
编写配置文件
在resources目录下创建bootstrap.yaml文件,该文件为使用nacos作为配置中心的配置文件
bootstrap配置文件如下:
spring:
application:
name: user # 服务名称
profiles:
active: dev
cloud:
nacos:
discovery:
group: STRATEGY #nacos组名
server-addr: nacos的ip地址:端口 # nacos地址
config:
server-addr:nacos的ip地址:端口 # nacos地址
group: STRATEGY # nacos组名
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- data-id: shared-swagger.yaml # swagger接口文档配置
group: STRATEGY
application-dev.yml文件如下:
server:
port: 5081 #服务端口号
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
bbober:
swagger:
title: 用户服务
desc: 用户服务
packagePath: com.bbober.user.controller
version: 1.0
在业务中使用注解
具体注解使用方法本文不再赘述
@RestController
@RequestMapping("/user")
@Api(tags = "用户服务")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@ApiOperation("用户登录")
@PostMapping("/login")
public Result login(@RequestBody UserLoginDTO userLoginDTO) throws Exception {
return userService.login(userLoginDTO);
}
@ApiOperation("用户注册")
@PostMapping("/register")
public Result register(@RequestBody UserRegisterDTO userRegisterDTO){
return userService.register(userRegisterDTO);
}
@ApiOperation("用户修改")
@PutMapping("/edit")
public Result edit(@RequestBody UserEditDTO userEditDTO){
return userService.edit(userEditDTO);
}
}
网关模块
引入依赖
其中重要的为swagger2、swagger-ui与common依赖,其余均为nacos与网关依赖
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger-ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<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>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--common-->
<dependency>
<groupId>com.bbober</groupId>
<artifactId>strategy-common</artifactId>
<version>1.0.0</version>
</dependency>
编写配置文件
bootstrap配置文件如下:
spring:
application:
name: gateway # 服务名称
profiles:
active: dev
cloud:
nacos:
discovery:
group: STRATEGY # nacos组名
server-addr: nacos的ip地址:端口 # nacos地址
config:
group: STRATEGY # nacos组名
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- data-id: gateway-swagger.yaml
group: STRATEGY
application-dev.yml文件如下:
server:
port: 5080 # 网关服务端口
application:
name: gateway #服务名称
gateway:
enable: true
编写配置类
swagger配置类:
@Configuration
public class SwaggerConfig {
@Bean
public SecurityConfiguration securityConfiguration(){
return SecurityConfigurationBuilder.builder().build();
}
@Bean
public UiConfiguration uiConfiguration(){
return UiConfigurationBuilder.builder().build();
}
}
聚合swagger配置类:
其中RouteDefinitionLocator为路由信息Bean对象,其为监听nacos中配置发生变化时动态读取,动态读取代码详见下文
@Primary
@Component
@AllArgsConstructor
public class Swagger2ResourceProvider implements SwaggerResourcesProvider {
/**
* swagger默认的url后缀
*/
@Value("${knife4j.api-uri}")
private static final String API_URI = "/v2/api-docs";
@Value("${knife4j.version}")
private static final String VERSION = "V1.0";
private final RouteDefinitionLocator routeDefinitionLocator;
@Override
public List<SwaggerResource> get() {
//创建swagger源对象
List<SwaggerResource> res = new ArrayList<>();
List<RouteDefinition> routeList = new ArrayList<>();
//去重集合
Map<String,Integer> map = new HashMap<>();
routeDefinitionLocator.getRouteDefinitions().subscribe(routeDefinition -> {
if (!map.containsKey(routeDefinition.getId())){
routeList.add(routeDefinition);
}
});
for (RouteDefinition route : routeList) {
//填入Swagger配置对象
res.add(swaggerResource(route));
}
return res;
}
private SwaggerResource swaggerResource(RouteDefinition route) {
SwaggerResource res = new SwaggerResource();
res.setName(route.getId());
res.setUrl(route.getUri().getHost() + API_URI);
res.setSwaggerVersion(VERSION);
return res;
}
}
swagger的数据接口类:
@RestController
@RequestMapping("/swagger-resources")
@AllArgsConstructor
public class SwaggerHandler {
private final SecurityConfiguration securityConfiguration;
private final UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResourcesProvider;
@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<List<SwaggerResource>>> swaggerResources(){
return Mono.just((new ResponseEntity<>(swaggerResourcesProvider.get(),HttpStatus.OK)));
}
}
动态读取nacos中路由信息的RouterConfig类:
@Component
@RequiredArgsConstructor
@Slf4j
public class RouterConfig {
private final NacosConfigManager nacosConfigManager;
//nacos中路由信息的配置文件
private final String dataId = "gateway-routes.json";
//配置所在组名
private final String group = "STRATEGY";
private final Set<String> routeIds;
private final RouteDefinitionWriter writer;
//在Bean初始化后执行
@PostConstruct
public void initRouteConfigListener() throws NacosException {
//1.项目启动,先拉取一次配置,并且添加配置监听器
String configInfo = nacosConfigManager.getConfigService()
.getConfigAndSignListener(dataId, group, 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
//更新路由表
updateConfigInfo(configInfo);
}
});
//首次加载路由表
updateConfigInfo(configInfo);
}
private void updateConfigInfo(String configInfo) {
log.debug("监听到路由配置信息:{}",configInfo);
//更新路由表
//1.解析配置信息,转为RouteDefinition
List<RouteDefinition> routeDefinitions = JSONUtil
.toList(configInfo,RouteDefinition.class);
//2.删除旧的路由表
for (String routeId : routeIds) {
writer.delete(Mono.just(routeId)).subscribe();
}
//清空路由表
routeIds.clear();
//3.更新路由表
for (RouteDefinition routeDefinition : routeDefinitions) {
//3.1更新路由表
writer.save(Mono.just(routeDefinition)).subscribe();
//3.2记录路由id,便于下次更新时删除
routeIds.add(routeDefinition.getId());
}
}
}
nacos中用到的配置文件
下文中用到的配置文件组名均为STRATEGY
gateway-routes.json
[
{
"id": "user",
"uri": "lb://user",
"predicates": [{
"name": "Path",
"args": {
"_genkey_0":"/user/**"
}
}],
"filters": []
}
]
shared-swagger.yaml
# springdoc-openapi项目配置
springfox:
documentation:
swagger:
v2:
path: /${spring.application.name}/v2/api-docs
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
openapi:
title: ${bbober.swagger.title:接口文档}
description: ${bbober.swagger.desc:接口文档}
email: tgu_bbober@outlook.com
concat: Bbober
version: ${bbober.swagger.version}
gateway-swagger.yaml
knife4j:
enable-aggregation: true
api-uri: /v2/api-docs
version: 1.0