SpringCloud整合Swagger3

前言

现在微服务框架已经整合到Swagger接口文档这一块了,记录一下整合中碰到的一些问题吧!我这整合的环境是SpringCloud+SpringCloudAlibaba+SpringCloudGateway+SpringSecurityOAuth2+Nacos+Swagger3,这些框架整合的时候版本适配是一个大问题,然后就是不同版本的细节问题,再就是加了安全框架后请求拦截问题,然后还有SpringCloudGateway做聚合文档的时候一些列问题,那么本文就给大家把这几个问题一一道来,整合的流程是这样的,想完成单服SpringBoot集成Swagger3,然后再在网关成做一次聚合即可!

版本适配问题

起初这个版本问题我只在SpringCloud整合SpringSecurityOAuth2中做过适配,当时版本如下

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
	<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
	<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>

这里SpringCloud整合SpringSecurityOAuth2确实是没什么问题的,但是后面整合Swagger3的时候问题就来了,然后果断将版本切换到跟高的版本!

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>

这里细心的朋友就会发现我这里还把SpringCloudAlibaba的版本也审计了一下,在后面SpringCloudGateway做聚合的时候讲,这里的依赖版本和我的一致后,那么下面整合起来就轻松很多了,不会出现各种找不到类呀什么的报错!

Swagger3整合

1.导入依赖
Swagger3这个版本的整合和Swagger2的整合是不一样的,首先引入的依赖就有变化,下面这个是常规Swagger3的导法

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.6</version>
</dependency>

升级后Swagger3导入其实只需要一个依赖搞定!

<dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-boot-starter</artifactId>
     <version>3.0.0</version>
</dependency>

2.编写相应代码

说白了其实也就一个配置类,但是定义一套规范,我们还是定义一个Properties类,用来映射配置文件,这里需要注意一下,需要使用一个配置文件处理器的依赖,如果不需要这个Properties类的话可以忽略

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

SwaggerProperties类

/**
 * @description: 映射配置文件Swagger基本属性
 * @author TAO
 * @date 2021/4/25 13:18
*/
@Data
@Configuration
@ConfigurationProperties("swagger")
public class SwaggerProperties {

    /**
     * 是否开启swagger
     */
    private Boolean enabled;

    /**
     * swagger会解析的包路径
     **/
    private String basePackage = "";

    /**
     * swagger会解析的url规则
     **/
    private List<String> basePath = new ArrayList<>();

    /**
     * 在basePath基础上需要排除的url规则
     **/
    private List<String> excludePath = new ArrayList<>();

    /**
     * 需要排除的服务
     */
    private List<String> ignoreProviders = new ArrayList<>();

    /**
     * 标题
     **/
    private String title = "";

    /**
     * 描述
     **/
    private String description = "";

    /**
     * 版本
     **/
    private String version = "";

    /**
     * 许可证
     **/
    private String license = "";

    /**
     * 许可证URL
     **/
    private String licenseUrl = "";

    /**
     * 服务条款URL
     **/
    private String termsOfServiceUrl = "";

    /**
     * host信息
     **/
    private String host = "";

    /**
     * 联系人信息
     */
    private Contact contact = new Contact();

    /**
     * 全局统一鉴权配置
     **/
    private Authorization authorization = new Authorization();

    @Data
    @NoArgsConstructor
    public static class Contact {

        /**
         * 联系人
         **/
        private String name = "";

        /**
         * 联系人url
         **/
        private String url = "";

        /**
         * 联系人email
         **/
        private String email = "";

    }

    @Data
    @NoArgsConstructor
    public static class Authorization {

        /**
         * 鉴权策略ID,需要和SecurityReferences ID保持一致
         */
        private String name = "";

        /**
         * 需要开启鉴权URL的正则
         */
        private String authRegex = "^.*$";

        /**
         * 鉴权作用域列表
         */
        private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();

        private List<String> tokenUrlList = new ArrayList<>();

    }

    @Data
    @NoArgsConstructor
    public static class AuthorizationScope {

        /**
         * 作用域名称
         */
        private String scope = "";

        /**
         * 作用域描述
         */
        private String description = "";

    }
}

SwaggerAutoConfig类

@Slf4j
@Configuration
@EnableAutoConfiguration
@EnableOpenApi//开启swagger,当前版本为3 所以注解和2@EnableSwagger2的版本不同
//或者直接省略prefix,那么直接写为swagger.enabled,当配置中存在swagger.enabled生效,matchIfMissing = true默认生效
@ConditionalOnProperty(prefix = "swagger",name = "enabled", matchIfMissing = true)
public class SwaggerAutoConfig {

    //默认的排除路径,排除Spring Boot默认的错误处理路径和端点(在解析的url规则之上)  /*/error,由于服务通常加前缀,所以前面/*忽略前缀
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error","/actuator/**","/*/error");

    //swagger会解析的url规则
    private static final String BASE_PATH = "/**";

    @Autowired
    private SwaggerProperties swaggerProperties;

    @Bean
    public Docket createRestApi() {

        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }

        //需要排除的url
        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        // 版本请求头处理
        List<RequestParameter> pars = new ArrayList<>();

        RequestParameterBuilder versionPar = new RequestParameterBuilder().description("灰度路由版本信息")
                .in(ParameterType.HEADER).name("VERSION").required(false)
                .query(param -> param.model(model -> model.scalarModel(ScalarType.STRING)));

        pars.add(versionPar.build());
        ApiSelectorBuilder builder = new Docket(DocumentationType.OAS_30)
                .host(swaggerProperties.getHost())
                .apiInfo(apiInfo()).globalRequestParameters(pars)
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));

        swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));

        swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));

        return builder.build().securitySchemes(Collections.singletonList(securitySchema()))
                .securityContexts(Collections.singletonList(securityContext())).pathMapping("/");

    }

    /**
     * 配置默认的全局鉴权策略的开关,通过正则表达式进行匹配;默认匹配所有URL
     * @return
     */
    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth()).build();
    }

    /**
     * 默认的全局鉴权策略
     * @return
     */
    private List<SecurityReference> defaultAuth() {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization().getAuthorizationScopeList()
                .forEach(authorizationScope -> authorizationScopeList.add(
                        new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()];
        return Collections
                .singletonList(SecurityReference.builder().reference(swaggerProperties.getAuthorization().getName())
                        .scopes(authorizationScopeList.toArray(authorizationScopes)).build());
    }


    private OAuth securitySchema() {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swaggerProperties.getAuthorization().getAuthorizationScopeList()
                .forEach(authorizationScope -> authorizationScopeList.add(
                        new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
        ArrayList<GrantType> grantTypes = new ArrayList<>();
        swaggerProperties.getAuthorization().getTokenUrlList()
                .forEach(tokenUrl -> grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(tokenUrl)));
        return new OAuth(swaggerProperties.getAuthorization().getName(), authorizationScopeList, grantTypes);
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                    .title(swaggerProperties.getTitle())
                    .description(swaggerProperties.getDescription())
                    .version(swaggerProperties.getVersion())
                    .license(swaggerProperties.getLicense())
                    .licenseUrl(swaggerProperties.getLicenseUrl())
                    .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                    .contact(new Contact(
                            swaggerProperties.getContact().getName(),
                            swaggerProperties.getContact().getUrl(),
                            swaggerProperties.getContact().getEmail()
                    ))
                    .build();
    }

}

照着copy即可,说明都写在代码注释里面了!
这两个类最好还是直接新起一个Swagger的项目,单独写Swagger的代码,方便后期维护
在这里插入图片描述

这两个类编写完后,那么Swagger3就已经整合完成了!那么我们现在用yy-test服务使用一下Swagger3,

其他服务使用Swagger3
在这里插入图片描述
在Controller上添加如下代码,方便测试

@Api(value = "r", tags = "权限测试接口")

yml添加如下配置

swagger:
  title: yy-test-api
  description: "test服"
  version: 1.0
  license: "xxx"
  licenseUrl: http://baidu.com
  terms-of-service-url: http://baidu.com
  contact:
    name: tao
    email: 11111
    url: http://baidu.com
  authorization:
    name: pig4cloud OAuth
    auth-regex: ^.*$
    authorization-scope-list:
      - scope: server
        description: server all
    token-url-list:
      - http://${GATEWAY_HOST:yy-gateway}:${GATEWAY-PORT:5000}/auth/oauth/token

启动访问测试!
在这里插入图片描述

那么单服就整合完毕了!这里有几个问题说一下,我这里别看三两下就搞好了,有些细节还是没有体现的,如我这里整合了SpringSecurityOAuth2,这个地方在yy-test服务中导入的yy-security这个依赖,如果项目中使用了安全框架,那么请放开这些路径,否者会出一些问题

.antMatchers( "/doc.html",
                        "/v3/**",//此请求不放开会导致 error api-docs无法正常显示  https://songzixian.com/javalog/905.html
                        "/swagger-ui**",
                        "/swagger-ui/**",//此请求不放开没有权限请求一直失败,处于轮询接口
                        "/swagger-resources/**",//此请求不放开导致访问出现Unable to infer base url. This is common when using dynamic servlet registration or when the API is   https://blog.csdn.net/just_now_and_future/article/details/89343680
                        "/webjars/**"
                        ).permitAll()

在然后就是我们这整合的是Swagger3的版本,访问路径已经发生了变更,已经不在是Swagger2的swagger.html了,是swagger-ui

聚合文档

做聚合的前提是确保单服的Swagger能正常使用,然后才能做网关的聚合,因为这里做聚合就是在上面没问题的基础之上的!下面这些代码是写在网关服里的注意
在这里插入图片描述

1.依赖导入

		<!--swagger 整合好的依赖,就是文章开头那篇文章里整合好的-->
		<dependency>
            <groupId>com.tao</groupId>
            <artifactId>yy-swagger</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

         <!--webflux 相关包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
        </dependency>
        <!--网关 swagger 聚合依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>


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

编写Gateway和Swagger的配置

@Slf4j
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//当Spring为web服务时,才使注解的类生效;通常是配置类;
public class GatewaySwaggerAutoConfiguration {

    @Autowired
    private SwaggerResourceHandler swaggerResourceHandler;

    @Autowired
    private SwaggerSecurityHandler swaggerSecurityHandler;

    @Autowired
    private SwaggerUiHandler swaggerUiHandler;

    /**
     * 路由映射,将Swagger相关URL映射到相应处理器上
     */
    @Bean
    public RouterFunction swaggerRouterFunction() {
        return RouterFunctions
                .route(RequestPredicates.GET("/swagger-resources")
                        .and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler)
                .andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui")
                        .and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler)
                .andRoute(RequestPredicates.GET("/swagger-resources/configuration/security")
                        .and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler);
    }

}

2.编写一下几个处理器

SwaggerResourceHandler

/**
 * @author TAO
 * @description: 提供Swagger3 获取注册中心的服务资源  http://localhost:5000/swagger-resources
 * @date 2021/4/28 22:33
 */
@Slf4j
@Component
public class SwaggerResourceHandler implements HandlerFunction<ServerResponse> {

    @Autowired
    private SwaggerResourcesProvider swaggerResources;

    @Override
    public Mono<ServerResponse> handle(ServerRequest request) {
        return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(swaggerResources.get()));
    }

}

SwaggerSecurityHandler

/**
 * @author TAO
 * @description: 提供Swagger3 安全相关处理器
 * @date 2021/4/28 22:30
 */

@Slf4j
@Component
public class SwaggerSecurityHandler implements HandlerFunction<ServerResponse> {

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Override
    public Mono<ServerResponse> handle(ServerRequest request) {
        return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(Optional.ofNullable(securityConfiguration)
                        .orElse(SecurityConfigurationBuilder.builder().build())));
    }

}

SwaggerUiHandler

/**
 * @author TAO
 * @description: 提供Swagger3 UI层配置数据  http://localhost:5000/swagger-resources/configuration/ui
 * @date 2021/4/28 22:27
 */

@Slf4j
@Component
public class SwaggerUiHandler implements HandlerFunction<ServerResponse> {

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    @Override
    public Mono<ServerResponse> handle(ServerRequest request) {
        return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(BodyInserters
                .fromValue(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build())));
    }

}

3.编写SwaggerProvider

/**
 * @author TAO
 * @description: 提供Swagger3 获取注册中心服务资源处理器
 * @date 2021/4/28 22:35
 */

@Slf4j
@Primary
@Component
public class SwaggerProvider implements SwaggerResourcesProvider {

    private static final String API_URI = "/v3/api-docs";

    @Autowired
    private SwaggerProperties swaggerProperties;

    @Autowired
    private GatewayProperties gatewayProperties;

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        Set<String> dealed = new HashSet<>();// 记录已经添加过的server
        gatewayProperties.getRoutes().stream()
                .filter(routeDefinition -> !swaggerProperties.getIgnoreProviders().contains(routeDefinition.getUri().getHost()))
                .forEach(routeDefinition -> {
                            String url = "/" + routeDefinition.getUri().getHost().toLowerCase() + "/" + routeDefinition.getId() + API_URI;// 拼接url
                            if (!dealed.contains(url)) {
                                dealed.add(url);
                                SwaggerResource swaggerResource = new SwaggerResource();
                                swaggerResource.setUrl(url);///设置服务文档yy-user/v3/api-docs
                                swaggerResource.setName(routeDefinition.getUri().getHost());//设置服务名yy-user
                                swaggerResource.setSwaggerVersion("3.0");
                                resources.add(swaggerResource);
                            }
                        }
                );
        return resources;
    }
}


4.编写配置文件

spring:
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过serviceId转发到具体服务实例
          enabled: true #是否开启基于服务发现的路由规则
          lower-case-service-id: true #是否将服务名称转换小写
      routes:
        - id: test_route
          uri: https://www.baidu.com
          predicates: #断言如果请求路径中符合下面规则那么将请求交给uri中的服务处理
            - Query=url,baidu
        - id: auth  #中央授权服
          uri: lb://yy-auth
          predicates:
            - Path=/auth/**
          filters:
            #5000/auth/oauth/token==>5000/oauth/token
            - RewritePath=/auth(?<segment>.*), $\{segment}
        - id: user #APP端用户服务
          uri: lb://yy-user
          predicates:
            - Path=/user/**
          filters:
            # 验证码处理
            - SmsValidateCodeGatewayFilter
        - id: admin #管理后台服务
          uri: lb://yy-admin
          predicates:
            - Path=/admin/**
        - id: test  #测试服
          uri: lb://yy-test
          predicates:
            - Path=/test/**



swagger:
  title: yy-gateway-api
  description: "网关服"
  version: 1.0
  license: "执照"
  licenseUrl: http://baidu.com
  terms-of-service-url: http://baidu.com
  contact:
    name: tao
    email: 111111
    url: http://baidu.com
  authorization:
    name: http://baidu.com
    auth-regex: ^.*$
    authorization-scope-list:
      - scope: server
        description: server all
    token-url-list:
      - http://${GATEWAY_HOST:yy-gateway}:${GATEWAY-PORT:5000}/auth/oauth/token
  ignore-providers:
    - www.baidu.com
    - yy-auth
    - yy-gateway

5.启动测试
在这里插入图片描述

注意:

1

	discovery:
        locator:
          # 是否与服务发现组件进行结合,通过serviceId转发到具体服务实例
          enabled: true #是否开启基于服务发现的路由规则
          lower-case-service-id: true #是否将服务名称转换小写

这段配置是一个比较核心的,在做聚合的时候其实这里用到了SpringCloudGateway通过服务名访问服务,在文章开通处有一个切换版本的地方,切换了一下<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>,这是个坑,原因就是版本低了,导致SpringCloudGateway通过服务名无法访问其他服务,

2
可能我们每个人搭建微服务的习惯不同,比如服务配置项目名,这个问题会导致我们访问请求是都会带上统一的项目名,那么这里在整合聚合文档的时候需要注意!

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员劝退师-TAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值