微服务项目网关中集成swagger并使用knife4j进行增强

820 阅读5分钟

微服务项目网关中集成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