SpringCloud2022.x集成springDoc v2.x

SpringCloud2022.x集成springDoc v2.x

文档内容:

  • 整合gateway网关
  • 使用Nacos进行配置管理
  • 使用Nacos进行服务注册
  • 整合springDoc自动生成API文档

springboot升级为3.x时,swagger的迁移会出现一些问题,目前springboot3.x将 包javax下的所有内容都迁移到了jakarta下,比如HttpServletRequest, 而swagger还是使用的包javax, 导致出现不兼容的问题,因此可以使用springdoc来替代以前的swagger包。

注意:本次使用的SpringDoc版本为2.X对应的OpenAPI 3,如果使用1.x对应OpenAPI 2版本springboot版本需要降级,两个版本所对应的依赖不同。

官网文档:Springdoc-OpenAPI v2.0.4
代码地址:study-servies · java学习 - 码云 - 开源中国 (gitee.com)

1、环境依赖

  • JDK 17+
  • mysql 8.x
  • Nacos 2.2.0+
  • SpringBoot 3.x
  • SpringCloud 2022.0.0
  • SpringCloud Alibaba 2022.0.0.0-RC1
  • SpringDoc-OpenApi v2.0.4
  • knife4j 4.1.0

2、单个服务模块配置springDoc v2.x

2.1、添加pom.xml依赖

此处使用的2.0.4版本,可以在[maven][https://mvnrepository.com/]仓库查询最新版本,springboot3.x只能使用2.x以上版本,springboot2.x版本使用1.x版本,注意只需要加这个webmvc的依赖就可以了。

		
        <dependency>
            <groupId>org.springdocgroupId>
            <artifactId>springdoc-openapi-starter-webmvc-uiartifactId>
            <version>2.0.4version>
        dependency>

2.2、添加yaml配置文件

下面只需要修改一下你对应的扫描包路径springdoc.packages-to-scan: - com.hippo.liteflow.controller,其他的可以不动,这里的springdoc.swagger-ui.path:可以设置成自己喜欢的路径,最综接口文档访问地址就是https:host:ip/context-path/设置的路径

# swagger configuration
springdoc:
  packages-to-scan: ##需要扫描的包,可以配置多个
    - com.hippo.database.controller
  paths-to-exclude:  ##配置不包含在swagger文档中的api
    - /api/test/**
    - /api/mockito/data
  swagger-ui:
    enabled: true  #开启/禁止swagger,prod可以设置为false
    path: /swagger-ui.html  #swagger页面
  api-docs:
    enabled: true #开启/禁止api-docs, prod可以设置为false
    path: /v3/api-docs #api的json文档
    version: openapi_3_0
  use-management-port: false
  ### 设置为true时, management也需要设置
#  use-management-port: true
#  show-actuator: true

2.3、新建springdoc配置类

这里可以根据自定义设置,基本与之前其他版本的相同。

package com.hippo.database.config;

import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;

/**
 * @ClassName SpringDocConfiguration
 * @Description TODO springdoc配置
 * @Author tangxl
 * @create 2023-03-28 11:32
 **/
@Configuration
public class SpringDocConfiguration {
    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .info(apiInfo())
                .externalDocs(new ExternalDocumentation() // 外部文档
                        .description("SpringDoc Wiki Documentation")
                        .url("https://springdoc.org/v2"));
    }

    private Info apiInfo() {
        return new Info()
                .title("数据库操作")
                .description("对不同数据操作进行操作")
                .version("1.0.0")
                .contact(new Contact()
                        .name("mongodb官方文档")
                        .url("https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#reference")
                        .email("[email protected]")
                )
                .license(new License()
                        .name("Apache 2.0")
                        .url("http://www.apache.org/licenses/LICENSE-2.0.txt")
                );
    }
}

2.4、新增跨域配置

新建CorsConfig.class

package com.hippo.database.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @ClassName CorsConfig
 * @Description TODO 跨域配置
 * @Author tangxl
 * @create 2023-03-30 11:23
 **/
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    /**
    * 跨域配置
    * @param registry
    */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 允许跨域访问的路径
        registry.addMapping("/**")
                // 允许跨域访问的源
                .allowedOriginPatterns("*")
                // 允许请求方法
                .allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")
                // 是否允许证书 不再默认开启
                .allowCredentials(true)
                // 允许前端获取的响应头
                .maxAge(3600)
                // 允许前端获取的响应头
                .allowedHeaders("*");
    }
}

2.4、新建测试类

package com.hippo.database.controller.springdoc;

import com.hippo.database.controller.springdoc.model.BpmnXmlReq;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName Demo2Controller
 * @Description TODO springDoc演示例子2
 * @Author tangxl
 * @create 2023-03-28 18:37
 **/
@RestController
@RequestMapping("body")
@Tag(name = "body参数")
public class Demo2Controller {

    @Operation(summary = "普通body请求")
    @PostMapping("/body")
    public ResponseEntity<BpmnXmlReq> body(@RequestBody BpmnXmlReq fileResp){
        return ResponseEntity.ok(fileResp);
    }

    @Operation(summary = "普通body请求+Param+Header+Path")
    @Parameters({
            @Parameter(name = "id",description = "文件id",in = ParameterIn.PATH),
            @Parameter(name = "token",description = "请求token",required = true,in = ParameterIn.HEADER),
            @Parameter(name = "name",description = "文件名称",required = true,in=ParameterIn.QUERY)
    })
    @PostMapping("/bodyParamHeaderPath/{id}")
    public ResponseEntity<BpmnXmlReq> bodyParamHeaderPath(@PathVariable("id") String id, @RequestHeader("token") String token, @RequestParam("name")String name, @RequestBody BpmnXmlReq fileResp){
        fileResp.setName(fileResp.getName()+",receiveName:"+name+",token:"+token+",pathID:"+id);
        return ResponseEntity.ok(fileResp);
    }
}
package com.hippo.database.controller.springdoc.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * @ClassName BpmnXmlReq
 * @Description TODO 演示请求参数类
 * @Author tangxl
 * @create 2023-03-28 09:36
 **/
@Data
// Schema 注解设置这个类的描述
@Schema(description = "bpmn xml 请求参数")
public class BpmnXmlReq {
    // Schema 注解设置每个属性的描述和示例
    @Schema(description = "名称",example = "请假流程名称")
    private String name;
    @Schema(description = "bpmn文件的内容, 字符串格式", example = "")
    private String xml;
    @Schema(description = "流程部署名称", example = "请假流程")
    private String deployName;
}

2.5、访问单组的接口文档

默认访问地址:https:host:ip/context-path/swagger-ui/index.html

配置路径:https:host:ip/context-path/自己配置的地址,上面配置的是swagger-ui.html

注意:/context-path是上下文地址,没有配置需要去掉

3.网关gateway配置springDoc v2.x

gateway网关使用的是webflux,这里需要引入springdoc-openapi-starter-webflux-ui依赖

3.1、添加pom.xml依赖

		
		<dependency>
			<groupId>org.springframework.cloudgroupId>
			<artifactId>spring-cloud-starter-gatewayartifactId>
			<version>4.0.0version>
		dependency>		
		
		<dependency>
			<groupId>org.springdocgroupId>
			<artifactId>springdoc-openapi-starter-webflux-uiartifactId>
			<version>2.0.4version>
		dependency>

3.2、添加yaml配置文件

这里在基础路由的基础上添加了一个转发路由openai,注意各服务的api文档json地址不要修改,上面例子中写的默认api文档json地址,如果修改了需要修改转发路由的拦截filters中的地址。

spring: 
  cloud: 
    gateway: 
      # 全局跨域配置
      globalcors: 
        add-to-simple-url-handler-mapping: true
        cors-configurations: 
          '[/**]': 
            # 允许跨域的域名,可以用*表示允许任何域名使用
            allowed-origin-patterns: '*'
            #允许请求中携带的头信息
            allowed-headers: '*'
            # 允许请求的方法
            allowed-methods: '*'
            # 允许携带cookie
            allow-credentials: true
            # 允许暴露的头信息
            exposed-headers: Content-Disposition,Content-Type,Cache-Control
            # 预检请求的有效期
            max-age: 36000
    
      discovery: 
        locator: 
          # 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
          # 为true,自动创建路由,路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问
          # 让gateway通过服务发现组件找到其他的微服务,如果是true则自动,false才可以配置routes属性生效
          enabled: true 
          # 服务名默认必须大写,否则会抛404错误,如果服务名要用小写,可在属性配置文件中添加spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true配置解决
          lower-case-service-id: true
      # 路由配置        
      routes: 
        # 资源路由:转发各个服务的 /v3/api-docs 请求
        - id: openapi
          uri: lb://${spring.application.name}
          predicates:
            - Path=/v3/api-docs/**
          filters:
            - RewritePath=/v3/api-docs/(?>.*), /$\{path}/v3/api-docs

        # 路由id,自定义,只要唯一即可
        - id: database-server 
          # 路由的目的地址,lb就是负载均衡,后面跟服务名称
          uri: lb://database 
          # 路由断言,也就是判断请求是否符合路由规则的调价
          predicates: 
            - Path=/database/** 
          #default-filters:  #全局用于配置所有路由共享过滤器
          #  - StripPrefix=1 #去掉- Path=/auth 前缀
          #  - PreserveHostHeader #发送原主机头  
          filters: 
            - SpringDocHeaderFilter  
            - StripPrefix=1 

        # 路由id,自定义,只要唯一即可
        - id: liteflow-server 
          # 路由的目的地址,lb就是负载均衡,后面跟服务名称
          uri: lb://liteflow 
          # 路由断言,也就是判断请求是否符合路由规则的调价
          predicates: 
            - Path=/liteflow/** 
          #default-filters:  #全局用于配置所有路由共享过滤器
          #  - StripPrefix=1 #去掉- Path=/auth 前缀
          #  - PreserveHostHeader #发送原主机头  
          filters: 
            - RewritePath=/liteflow/(?>.*), /$\{path} 
            
      default-filters: 
        - AddRequestHeader=X-Request-red, green
        # 添加名为origin的请求头,值为gateway
        - AddRequestHeader=origin, gateway

# swagger configuration
springdoc: 
  swagger-ui: 
    # 禁止默认路径
    disable-swagger-default-url: true
  webjars: 
    # 设置为空,不要前缀,不配置这个就需要多加一层路径`/webjars`->`http:host:网关ip/webjars/swagger-ui/index.html`
    prefix: 

3.3、添加配置文件和服务监听

新建SpringDocConfiguration.class和InstancesChangeEventListener.class

这里可以采用的有两种方法,这里是从nacos服务中心动态获取所有服务来进行配置分组,你也手动添加配置分组

package com.hippo.gateway.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName SpringDocConfiguration
 * @Description TODO swagger配置服务:读取gateway的路由配置,截取服务前缀
 * @Author tangxl
 * @create 2023-03-27 18:38
 **/
@Configuration
public class SpringDocConfiguration {

    @Value(value = "${server.port:9000}")
    private int port;

    @Bean
    public OpenAPI springShopOpenApi() {
        final String loginToken = "BearerAuth";
        return new OpenAPI().info(new Info().title("API")
                        .description("gateway网关")
                        .version("v1.0.0")).externalDocs(new ExternalDocumentation()
                        .description("gateway网关")
                        .url("http://127.0.0.1:" + port))
                .components(new Components().addSecuritySchemes(loginToken, new SecurityScheme()
                        .type(SecurityScheme.Type.HTTP).scheme("Bearer").bearerFormat("JWT")
                        .in(SecurityScheme.In.HEADER)
                        .name(loginToken)))
                .addSecurityItem(new SecurityRequirement().addList(loginToken));
    }

    @Bean
    public GroupedOpenApi service2Api() {
        String[] paths = {"/_default/**"};
        return GroupedOpenApi.builder().group("_default")
                .pathsToMatch(paths)
                .build();
    }


}

package com.hippo.gateway.listener;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.utils.CollectionUtils;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.CachingRouteLocator;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * @ClassName InstancesChangeEventListener
 * @Description TODO 动态刷新Swagger下拉
 * @Author tangxl
 * @create 2023-03-30 10:20
 **/
@Component
public class InstancesChangeEventListener extends Subscriber<InstancesChangeEvent> implements InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(InstancesChangeEventListener.class);

    @Autowired
    private RouteLocator routeLocator;
    @Autowired
    private SwaggerUiConfigProperties swaggerUiConfigProperties;
    @Value("${spring.application.name}")
    private String applicationName;

    private Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls;

    @PostConstruct
    private void post() {
        NotifyCenter.registerSubscriber(this);
    }


    /**
     * Event callback.
     *
     * @param event {@link Event}
     */
    @Override
    public void onEvent(InstancesChangeEvent event) {
        logger.info("接收到 InstancesChangeEvent 订阅事件:{}", JSON.toJSONString(event));

        CachingRouteLocator cachingRouteLocator = (CachingRouteLocator) routeLocator;
        cachingRouteLocator.refresh();

        List<Route> routeList = routeLocator.getRoutes().collectList().block();

        System.out.println(routeList);

        LinkedHashSet<AbstractSwaggerUiConfigProperties.SwaggerUrl> set = new LinkedHashSet<>();

        if (CollectionUtils.isNotEmpty(this.urls)) {
            set.addAll(this.urls);
        }

        for (Route route : routeList) {
            if (route.getMetadata().size() > 0) {
                // ReactiveCompositeDiscoveryClient_sky-user
                String id = route.getId();
                String name = id.replaceAll("ReactiveCompositeDiscoveryClient_", "");
                if (applicationName.equals(name)) {
                    continue;
                }
                set.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl(name, "/"+name+"/v3/api-docs", ""));
            }
        }

        if (CollectionUtils.isEmpty(set)) {
            set.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl("_default", "/v3/api-docs/_default", ""));
        }

        swaggerUiConfigProperties.setUrls(set);

    }

    /**
     * Type of this subscriber's subscription.
     *
     * @return Class which extends {@link Event}
     */
    @Override
    public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
        return InstancesChangeEvent.class;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.urls = this.swaggerUiConfigProperties.getUrls();
    }
}

3.4、访问地址

默认访问地址:http:host:网关ip/webjars/swagger-ui/index.html

这里去掉了/webjar路径:http:host:网关ip/swagger-ui/index.html

这个界面不太好看可以使用knife4j增强,目前knife4j v4.x版本也已经集成了springdoc,支持springboot3.x版本

SpringCloud2022.x集成springDoc v2.x_第1张图片

4、knife4j 4.x实现增强配置

官网地址:升级到v4.0.0版本 | Knife4j (xiaominfo.com)

官方案例:knife4j-openapi3-gateway · 萧明/swagger-bootstrap-ui-demo - 码云 - 开源中国 (gitee.com)

4.1、单服务模块

添加xml依赖,去掉原来添加的springdoc-openapi-starter-webmvc-ui,注意版本不要冲突

			
         <dependency>
    		<groupId>com.github.xiaoymingroupId>
    		<artifactId>knife4j-openapi3-jakarta-spring-boot-starterartifactId>
    		<version>4.1.0version>
		dependency>

添加yaml配置文件

# knife4j的增强配置,不需要增强可以不配
knife4j:
  enable: true
  setting:
    language: zh_cn

4.2、网关服务

添加依赖,原springDoc依赖不要去掉

		 
      <dependency>
          <groupId>com.github.xiaoymingroupId>
          <artifactId>knife4j-gateway-spring-boot-starterartifactId>
          <version>4.1.0version>
      dependency>

添加yaml配置文件

# knife4j的增强配置,不需要增强可以不配
knife4j:
  # 聚合swagger文档
  gateway:
    enable: true
    routes:
      - name: database
        url: /database/v3/api-docs?group=default
        service-name: user-service
        order: 2
      - name: liteflow
        url: /liteflow/v2/api-docs?group=default
        service-name: order-service
        order: 3

4.3、访问地址

默认访问地址:http:host:网关ip/doc.html

5、问题汇总

5.1、用某一个服务的接口时,请求路径不会加上对应的服务名问题

SpringCloud2022.x集成springDoc v2.x_第2张图片

目前这里使用最新的网关还有一点问题,单服务访问没有问题,等待后期网关增强swagger解决了更新。

你可能感兴趣的:(#,swagger,spring,boot,java,spring)