app开发定制公司springdoc与spring cloud gateway整合经验分享

springdoc与spring cloud gatewayapp开发定制公司app开发定制公司整合经验分享

springdoc与spring cloud gateway整合经验分享

app开发定制公司最近对系统的架构进行了升级,从spring boot 2.1.x升级到了2.7.0.app开发定制公司原先使用的是swagger2app开发定制公司进行文档管理。app开发定制公司升级后出现了不少兼容性问题,索性将swagger升级到了springdoc.

项目配置:

  • spring boot: 2.7.0
  • spring cloud: 2021.0.3
  • springdoc: 1.16.10

一、app开发定制公司升级子服务

首先,app开发定制公司将各业务子系统进行升级。app开发定制公司这个升级过程基本与网app开发定制公司上相关的教程差不多。

1. 引入SpringDoc的依赖

先在主pom中,加入springdocapp开发定制公司相关的依赖管理:

    <dependencyManagement>        <dependencies>		    <!-- springDoc -->		    <dependency>		        <groupId>org.springdoc</groupId>		        <artifactId>springdoc-openapi-ui</artifactId>		        <version>${springdoc.version}</version>		    </dependency>		    <dependency>		        <groupId>org.springdoc</groupId>		        <artifactId>springdoc-openapi-webflux-ui</artifactId>		        <version>${springdoc.version}</version>		    </dependency>		    <dependency>		        <groupId>org.springdoc</groupId>		        <artifactId>springdoc-openapi-common</artifactId>		        <version>${springdoc.version}</version>		    </dependency>        </dependencies>    </dependencyManagement>    ...    <properties>        ...        <springdoc.version>1.6.10</springdoc.version>    </properties>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

接下来,app开发定制公司在子项目中,去除swagger2的依赖,换上springdoc的依赖。

注意,app开发定制公司这里有个坑。一开始为了方便,大家可能会想保留旧的注解,先把springdoc运行起来,看看效果,这样可以减少一些工作量。可问题在于springdoc虽然是基于swagger3开发,但是并不兼容swagger2,而且会导致对swagger相关包依赖的版本冲突,导致springdoc无法正常加载。因此,一定要把swagger2的包清理干净

			<dependency>		        <groupId>org.springdoc</groupId>		        <artifactId>springdoc-openapi-ui</artifactId>		    </dependency>
  • 1
  • 2
  • 3
  • 4

还有一点要注意的是,一般稍大一些的项目,会开发很多的二方包,这些二方包中因为会封装很多共用 的POJO,因此一般也会对swagger2有依赖。这些依赖也会传递到各子系统中,引起版本冲突。因此,如果有二方包时,建议先将二方包中的swagger依赖改为可选。效果如下。

 			<dependency>                <groupId>io.springfox</groupId>                <artifactId>springfox-swagger2</artifactId>                <optional>true</optional>            </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2. 调整配置

springdoc是开箱即用的,理论上只要删除旧的swaager配置类就可以了。但是,从安全方面考虑,最好限制在生产环境下启用。springdoc这方面比swagger好的一点,是默认提供了一个开关配置。建议在application.yml中将这个配置默认关闭。

2.1 生产环境默认关闭API文档服务

springdoc:  api-docs:    # 默认生产环境关闭文档功能。    enabled: false
  • 1
  • 2
  • 3
  • 4

在开发或者测试环境中开启,比如application-dev.yml中。

springdoc:  api-docs:    enabled: true
  • 1
  • 2
  • 3

2.2 文档说明信息配置(可选)

如果需要配置文档的说明信息,可以增加一个配置类。

/** * SpringDoc配置。 * * @author 马翼超 * @since 1.0 */@Configuration@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")public class SpringDocConfig {    @Bean    @ConfigurationProperties(prefix = "springdoc.api-docs.info")    public Info springDocInfo() {        return new Info();    }    @Bean    public OpenAPI openAPI(Info infoConfig) {        return new OpenAPI().info(infoConfig);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这里springdoc.api-docs.info的配置项位置并不是springdoc默认的,可以自定义。

配置范例。

springdoc:  api-docs:    info:      title: 服务Api文档      description: 文档说明      contact:      	name: Myc      	email: mycsoft@qq.com      	url: http://mycsoft.cn/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

具体的配置项可以查看Info的源码。

3. 替换Swagger2的注解

接下来就是更换注解的工作了。如果POJO与Controller类很多,那这就是一个比较辛苦的工作了。我的经验是使用正则表达式进行全局替换,这样速度要快很多。比如:将@ApiModelProperty\(\s*value\s*=\s*(".+")\)替换为@Schema(description = &1).

3.1 Swagger与Springdoc注解对照表

swagger 2spring doc描述
@Api@Tag修饰 controller 类,类的说明
@ApiOperation@Operation修饰 controller 中的接口方法,接口的说明
@ApiModel@Schema修饰实体类,该实体的说明
@ApiModelProperty@Schema修饰实体类的属性,实体类中属性的说明
@ApiImplicitParams@Parameters接口参数集合
@ApiImplicitParam@Parameter接口参数
@ApiParam@Parameter接口参数

网上有些文章中,将@ApiModel@ApiModelPropertyvalue替换为@Schematitle。我感觉这样展示的效果并不好,建议替换为description。有兴趣的同学可以试验一下两者的区别。

3.2 Controller范例

@Slf4j@RestController@RequestMapping("/sample")@Tag(name = "sample接口")@CrossOriginpublic class HelloController {    @Autowired    private AuthService service;    @Operation(summary = "问候")    @GetMapping("/hello")    public String hello(            @Parameter(description = "名称")             @RequestParam String name    ) {        return "hello " + name;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意,如果未来有网关聚合文档的需求,controller上需要增加@CrossOrigin注解,解决跨域问题。

3.3 POJO范例

@Data@Schema(description ="个人信息")public class PersonalInfo {    @Schema(description = "姓名")    private String name;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.4 预览效果

替换完之后,就可以运行一下看看效果了。地址是 。

二、升级网关(Spring Cloud Gateway)

建议先将所有子系统都升级完之后再升级网关应用。不然很难进行文档聚合。

Spring Cloud Gateway采用的是响应式,因此,要使用对应的WebFlux组件。

		<!-- springdoc -->        <dependency>            <groupId>org.springdoc</groupId>            <artifactId>springdoc-openapi-webflux-ui</artifactId>        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

参考子系统的升级的其它步骤,如果在网关上也有实现接口,那么这样就可以直接运行了。

1. 聚合子服务API文档

通常我们希望网关可以聚合所有前端接口的文档,这样前端的同学就不需要频繁的切换服务了。虽然springdoc不能自动进行聚合处理,不过相比swagger,提供了一些便捷的手段。

一般聚合文档的模式都是按服务分组。这里和大家分享一下我整理的三种方案。

1.1 聚合方案一:手工配置

范例如下:

spring:  cloud:    gateway:      discovery:        locator:          enabled: true      routes:      	...        # ==============================================================        # apidocs资源路由配置        - id: hello-api-doc          uri: lb://sample-hello/          predicates:            ## 转发地址格式为 uri/archive            - Path=/sample-hello/v3/api-docs/**          filters:            - StripPrefix=1springdoc:  ...  swagger-ui:    urls:      - name: 网关服务接口        url: /v3/api-docs      - name: Hello服务      	# url前缀要与路由配置中的Patch呼应。        url: /sample-hello/v3/api-docs
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这个方法简单粗暴。可以快速验证。而且可以灵活的控制需要暴露的服务接口。不过有两个问题。

  1. 如果子服务太多,配置量会比较大;
  2. apidocs的路由配置需要与其它正常业务路由写死在配置中,在生产环境下不方便剥离。会有安全隐患。

1.2 聚合方案二:自动配置

通过编写自动配置类的方式,进行自动解析。这种方案的案例网上很多,方式不一,这里我就是不一一累述了。有兴趣的同学要查看相关的文档。给大家推荐几篇。

看似方便,但是比较难灵活的控制需要暴露的接口。适用于网关路由规则相对单纯,一个服务一个路由的情况。
不过在安全性要求较高的生产环境中,或者权限控制比较复杂的场景中,这中“一刀切”的路由配置显然是不合适的。

我将前两种方案进行了整合为一种半自动的方案。

1.3 聚合方案三:半自动配置

对于子服务的路由采用自动化配置的方式。不过绑定上springdoc的开关。这样就可以防止资源在生产环境下被过度开放的风险。

1.3.1 自动开启子服务的API文档聚合资源路由

/** * Springdoc子服务apidocs资源路由。 * * 本配置用于聚合子服务api文档时,自动开通相关子服务在网关的/v3/apidocs/**路由的场景。 * * 仅用于springdoc功能打开的场景。 * * @author 马翼超 * @since 1.0 */@Slf4j@RequiredArgsConstructor@Configuration@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")public class SpringDocSubApiDocsRouteAutoConfiguration implements ApplicationEventPublisherAware {    private static final String DISCOVERY_CLIENT_ID_PRE = "ReactiveCompositeDiscoveryClient_";    @Value("${spring.application.name}")    private String selfServiceName;    @Autowired    private RouteDefinitionWriter routeDefinitionWriter;    @Autowired    private RouteDefinitionLocator locator;    @Setter    private ApplicationEventPublisher applicationEventPublisher;    @SneakyThrows    @PostConstruct    public void init() {        installAllSubServiceApiDocsRoutes();    }    /**     * 加载所有子服务的apidocs的路由配置。     */    private void installAllSubServiceApiDocsRoutes() {        List<RouteDefinition> definitions = ofNullable(locator.getRouteDefinitions().collectList().block())                .orElseGet(ArrayList::new);        final String selfServiceId = DISCOVERY_CLIENT_ID_PRE + selfServiceName;        //解析出所有子服务名。        List<String> services = definitions.stream()                .filter(routeDefinition -> ofNullable(routeDefinition.getId())                //只保留服务级别的路由。                .filter(id -> id.startsWith(DISCOVERY_CLIENT_ID_PRE))                //排除本系统。                .filter(id -> !selfServiceId.equalsIgnoreCase(id))                .isPresent())                .map(routeDefinition -> routeDefinition.getUri().toString().replace("lb://", "").toLowerCase())                .collect(toList());        services.forEach(this::installRoute);        if (CollectionUtils.isNotEmpty(services) && log.isInfoEnabled()) {            log.info("自动安装了{}个子服务的apidocs路由:{}", services.size(),                    services.stream().collect(joining(",")));        }    }    /**     * 安装一个子服务的apidoc路由。     *     * @param serviceName     */    private void installRoute(String serviceName) {        RouteDefinition routeDefinition = new RouteDefinition();        routeDefinition.setId(serviceName + "-apidocs");        routeDefinition.setUri(URI.create("lb://" + serviceName));        routeDefinition.setPredicates(Arrays.asList(new PredicateDefinition("Path=/" + serviceName + "/v3/api-docs/**")));        routeDefinition.setFilters(Arrays.asList(new FilterDefinition("StripPrefix=1")));        routeDefinition.setOrder(-1);        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

这个配置类,简单来说,就是当springdoc被启用时,会在启动后自动加载所有子服务的apidocs路由。生成的内容与方案一中的路由配置一样。每个子系统以自己的服务名为标识,生成一个{服务名}-apidocs的路由,Path的判定规则是Path=/{服务名}/v3/apidocs/**

这里虽然也开放了所有子服务的/v3/api-docs/的资源,不过受springdoc开关的控制。一般只会在开发环境启用,安全风险可忽略。

1.3.2 子服务文档聚合UI配置

然后对于需要暴露的接口采用手工配置的方式url/{服务名}/v3/apidocs/的约束进行配置。这样就可以手动指定只需要暴露给前端同学的服务。

springdoc:  ...  swagger-ui:    urls:      - name: 网关服务接口        url: /v3/api-docs      - name: Hello服务      	# url前缀要与路由配置中的Patch呼应。        url: /sample-hello/v3/api-docs
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

三、扩展:子服务接口分包

再和大家分享一个接口分类的经验。一般每个子系统的接口会分这么几类。

  1. 面向前端调用的接口:这类接口受前端需求的影响比较大,需要暴露给前端应用;
  2. 内部协调接口:这类接口仅用于内部系统之间进行业务协作用,一般不暴露给前端;
  3. 面向第三方接口:这类接口用于与外部系统对接,比如开放平台接口等,需要暴露给第三方的开发者。

这几类接口我们都需要提供完备的文档供调用者进行对接(一人全栈开发模式可以滑走了),但是对于不同类的阅读者,把所有接口都一并暴露出来不合适,有时也会触犯保密规定。那么,我们可以利用springdoc的分组机制对这些接口进行自动化分类。
基于刚刚的半自动配置方案,我已经可以做到针对服务进行接口分类,还能不能对接口的分类管理再细化呢?
答案是可以的。springdoc继承了swagger的分组机制,并提供了默认的分组配置规则。比如,我们可以将一个服务的接口按路径进行分组。如:

springdoc:  group-configs:    - group: front      display-name: 前端接口      paths-to-match: /front/**    - group: inner      display-name: 内部接口      paths-to-match: /inner/**    - group: openapi      display-name: 第三方开放接口      paths-to-match: /openapi/**
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然后在Web网关中只配置前端接口

springdoc:  ...  swagger-ui:    urls:      - name: 网关服务接口        url: /v3/api-docs      - name: Hello服务        url: /sample-hello/v3/api-docs/front
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

开放平台接口网关中只配置第三方开放接口

spring:  cloud:    gateway:      discovery:        locator:          enabled: true      routes:      	...        # ==============================================================        # apidocs资源路由配置        - id: hello-api-doc          uri: lb://sample-hello/          predicates:            ## 转发地址格式为 uri/archive            - Path=/sample-hello/v3/api-docs/openapi          filters:            - StripPrefix=1springdoc:  ...  swagger-ui:    urls:      - name: Hello接口服务        url: /sample-hello/v3/api-docs/openapi
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

开放平台的网关api文档可能会在生产环境中启用,因此,这里的路由配置建议也是手动配置

附录:参考与引用

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发