文章目录

【Spring Cloud Alibaba】Swagger 聚合接口文档

1、Swagger

Swagger 没有什么好介绍的了,在 Spring Boot 中是最常用的生成接口文档的工具,这里主要说在为服务中怎么使用 Swagger 生成聚合文档

2、单个服务集成 Swagger

在需要的服务中增加依赖,swagger-spring-boot-starter,后续可以改主题,这里先用默认的

${swagger-spring-boot-starter.version}



<swagger-spring-boot-starter.version>1.9.0.RELEASE</swagger-spring-boot-starter.version>
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>${swagger-spring-boot-starter.version}</version>
</dependency>

然后增加一个文档配置

package cn.tellsea.config;

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;

/**
* 接口文档
*
* @author Tellsea
* @date 2022/01/04
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {

/**
* 创建API
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 详细定制
.apiInfo(apiInfo("1.0.0"))
.select()
// 指定当前包路径
.apis(RequestHandlerSelectors.basePackage("cn.tellsea.controller"))
// 扫描所有
.paths(PathSelectors.any())
.build();
}

/**
* 添加摘要信息
*
* @param version
* @return
*/
private ApiInfo apiInfo(String version) {
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
.title("Seata订单服务")
.version(version)
.build();
}
}

启动服务,访问链接,我这里使用的是上一次的三个服务,分别都增加了依赖和配置

http://localhost:8007/swagger-ui.html

【Spring Cloud Alibaba】Swagger 聚合接口文档_ide

http://localhost:8008/swagger-ui.html

【Spring Cloud Alibaba】Swagger 聚合接口文档_java_02

http://localhost:8009/swagger-ui.html

【Spring Cloud Alibaba】Swagger 聚合接口文档_spring_03

看后台日志,发现一个报错

java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_181]
at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_181]
at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_181]
at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:689) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728) [jackson-databind-2.11.3.jar:2.11.3]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755) [jackson-databind-2.11.3.jar:2.11.3]

是因为 Swagger2 中 @ApiModelProperties 如果为数字类型,但添加注解后,又不指定 example 的值,会默认为"",swagger 在后续处理的时候强行转化空字符串就会抛出异常。这也是 Swagger2 的 bug

第 1 种:增加配置

logging.level.io.swagger.models.parameters.AbstractSerializableParameter=ERROR

或者

# 解决Swagger报错:For input string: ""
logging:
level:
io:
swagger:
models:
parameters:
AbstractSerializableParameter: ERROR

第二种:降低 Swagger 版本,不推荐

3、Gateway 网关聚合 Swagger

创建一个 spring-cloud-alibaba-seata-gateway 网关服务,依赖如下

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>${swagger-spring-boot-starter.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

修改 application.properties 配置文件

server.port=8010
spring.application.name=spring-cloud-alibaba-seata-gateway
management.server.port=9010
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

# Nacos注册信息
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.namespace=sandbox-configuration

启动类增加

@EnableDiscoveryClient

创建聚合 Swagger 文档的配置类

package cn.tellsea.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* 聚合Swagger文档
*
* @author Tellsea
* @date 2022/01/04
*/
@Component
public class MySwaggerResourceProvider implements SwaggerResourcesProvider {

/**
* swagger2默认的url后缀
*/
private static final String SWAGGER2URL = "/v2/api-docs";

/**
* 网关路由
*/
private final RouteLocator routeLocator;

/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String self;

@Autowired
public MySwaggerResourceProvider(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}

@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routeHosts = new ArrayList<>();
// 由于我的网关采用的是负载均衡的方式,因此我需要拿到所有应用的serviceId
// 获取所有可用的host:serviceId
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !self.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));

// 记录已经添加过的server,存在同一个应用注册了多个服务在nacos上
Set<String> dealed = new HashSet<>();
routeHosts.forEach(instance -> {
// 拼接url,样式为/serviceId/v2/api-info,当网关调用这个接口时,会自动通过负载均衡寻找对应的主机
String url = "/" + instance + SWAGGER2URL;
if (!dealed.contains(url)) {
dealed.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(instance);
resources.add(swaggerResource);
}
});
return resources;
}
}

重写 Swagger 访问地址

package cn.tellsea.controller;

import cn.tellsea.config.MySwaggerResourceProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger.web.*;

import java.util.List;

/**
* 重写Swagger访问地址
*
* @author Tellsea
* @date 2022/01/04
*/
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerResourceController {
private MySwaggerResourceProvider swaggerResourceProvider;

@Autowired
public SwaggerResourceController(MySwaggerResourceProvider swaggerResourceProvider) {
this.swaggerResourceProvider = swaggerResourceProvider;
}

@RequestMapping(value = "/configuration/security")
public ResponseEntity<SecurityConfiguration> securityConfiguration() {
return new ResponseEntity<>(SecurityConfigurationBuilder.builder().build(), HttpStatus.OK);
}

@RequestMapping(value = "/configuration/ui")
public ResponseEntity<UiConfiguration> uiConfiguration() {
return new ResponseEntity<>(UiConfigurationBuilder.builder().build(), HttpStatus.OK);
}

@RequestMapping
public ResponseEntity<List<SwaggerResource>> swaggerResources() {
return new ResponseEntity<>(swaggerResourceProvider.get(), HttpStatus.OK);
}
}

启动服务访问网关地址

http://localhost:8010/swagger-ui.html

右上角已经读取出了各个服务的应用名称,直接切换即可查看文档,下面是账号服务的接口文档

【Spring Cloud Alibaba】Swagger 聚合接口文档_spring_04

订单服务文档

【Spring Cloud Alibaba】Swagger 聚合接口文档_ide_05

库存服务文档

【Spring Cloud Alibaba】Swagger 聚合接口文档_java_06

到此,Swagger 聚合接口文档完成

微信公众号

【Spring Cloud Alibaba】Swagger 聚合接口文档_ide_07