SpringCloud版本:2021.0.1     SpringBoot版本:2.6.3      Swagger版本:2.9.2

系列文章

SpringCloud学习(一)----- Eureka搭建SpringCloud学习(一)----- Eureka搭建SpringCloud学习(一)----- Eureka搭建

SpringCloud学习(二)----- SpringBoot Admin搭建(与Eureka整合)

SpringCloud学习(三)----- Gatewayw网关搭建

SpringCloud学习(四)----- Gatewayw网关完善(限流)

SpringCloud学习(五)----- Gatewayw网关完善(Resilience4j断路器)

SpringCloud学习(六)----- Gatewayw网关完善(防止SQL注入)

SpringCloud学习(七)----- 使用Feign调用别的微服务的方法

SpringCloud学习(八)----- Gateway网关及其他微服务接入Swagger接口文档

参考文章:

Spring Cloud Gateway整合Swagger聚合微服务系统API文档(非Zuul)

swagger-ui升级版swagger-bootstrap-ui

Swagger的界面太丑,试试knife4j的接口文档吧

今天,我们要学习的是对接Swagger,为的是解决我们写接口的时候,能更好以及更快更方便的对接口进行测试,也能更简单的在编写代码的时候就顺便把接口文档也写了,而不用在写完接口后还得重新去看接口来写接口文档。

一、什么是Swagger?

        Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

        Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。

二、Swagger 的优势        

  • 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。

  • 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。

三、Swagger如何集成到SpringCloud框架

        (1)、Gateway网关配置

                首先,先加上pom依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

                再在项目里新建个swagger文件夹,用于存放swagger配置的相关文件。

                 SwaggerHandler文件:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;


@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;
    @Autowired(required = false)
    private UiConfiguration uiConfiguration;
    private final SwaggerResourcesProvider swaggerResources;
    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }

    @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> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }

}

                 SwaggerProvider文件:

import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;

@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
    public static final String API_URI = "/v2/api-docs";
    public static final String EUREKA_SUB_PRIX = "ReactiveCompositeDiscoveryClient_";
    private final DiscoveryClientRouteDefinitionLocator routeLocator;
    public SwaggerProvider(DiscoveryClientRouteDefinitionLocator routeLocator) {
        this.routeLocator = routeLocator;
    }
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        //从DiscoveryClientRouteDefinitionLocator 中取出routes,构造成swaggerResource
        routeLocator.getRouteDefinitions().subscribe(routeDefinition -> {
            resources.add(swaggerResource(routeDefinition.getId().substring(EUREKA_SUB_PRIX.length()),routeDefinition.getPredicates().get(0).getArgs().get("pattern").replace("/**", API_URI)));
        });
        return resources;

    }

    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("2.0");
        return swaggerResource;
    }

}

 这样在Gateway网关所需要的配置也就已经完成了,后面就是对服务的配置了。

                (2)、服务的配置

                         首先,依旧是先加上pom依赖 

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

再在服务配置文件application.yml上加上控制swagger是否开启的配置已经spring的路径匹配规则,不然会出现问题,因为我用的Swagger版本是2.9.2,但SpringBoot的版本比较新,所以路径匹配的规则会不一样

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
swagger:
  enabled: true

                          之后,再新建个Swagger的接口配置文件SwaggerConfig

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @description: 配置描述swagger的描述
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    //是否开启swagger,正式环境一般是需要关闭的,可根据springboot的多环境配置进行设置
    @Value(value = "${swagger.enabled}")
    Boolean swaggerEnabled;
    @Bean
    public Docket createRestApi() {
        //给header添加参数token
        ParameterBuilder tokenPar = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<>();
        tokenPar.name("JSESSIONID")
                .description("令牌")
                .modelRef(new ModelRef("string"))
                .parameterType("header").required(false).build();
        pars.add(tokenPar.build());
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title("接口列表")
                        .description("数据相关")
                        .contact(new Contact("chenai", "http://www.chenai.cn","cjwzyd@163.com"))
                        .version("1.0.0").build())
                        // 是否开启
                        .enable(swaggerEnabled).select()
                        // 扫描的路径包 -- 路径记得改成自己的
                        .apis(RequestHandlerSelectors.basePackage("com.*.controller"))
                        // 指定路径处理 PathSelectors.any()
                        // 代表所有的路径
                        .paths(PathSelectors.any())
                        .build().globalOperationParameters(pars)
                        .ignoredParameterTypes(HttpSession.class, HttpServletRequest.class, HttpServletResponse.class);
    }
    @Bean
    public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
        List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
        allEndpoints.addAll(webEndpoints);
        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
        String basePath = webEndpointProperties.getBasePath();
        EndpointMapping endpointMapping = new EndpointMapping(basePath);
        boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
        return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
    }
    private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
    }
}

好了,服务这边的配置就完成了,接下来就启动网关、服务以及Eureka就可以啦。

访问的地址是{gateway-ip}:{gateway-port}/swagger-ui.html

优化:

虽然已经把Swagger集成到SpringCLoud里的,但也许有的人会觉得当前这个Swagger的界面不好看,对接口的测试也不是很方便,那么,其实我们是可以对当前的Swagger的UI界面进行升级的,也就是用Boostrap的框架来进行优化,不过也不难,导几个包,和加个注解就可以解决的。(一开始是用knife4j的,也就是swagger-bootstrap-ui的进阶版,但个人觉得不是很习惯knife4j的操作,所以就用回swagger-bootstrap-ui,具体用啥看个人喜好)

首先,导入pom依赖,这个是在Gateway网关和对应的服务项目里都得引入的。

<!--swaggerui  几个自定义界面方便查看接口-->
<!--访问路径:http://localhost:8080/doc.html-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>
<!-- # 增加两个配置解决 NumberFormatException -->
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.5.22</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.5.22</version>
</dependency>

配置的话在Gateway网关里就不用修改,但在微服务里的SwaggerConfig文件就得加上@EnableSwaggerBootstrapUI这个注解,然后就可以重启项目了,没错,就是这么简单,引包,加注解就可以了。

 然后,这个访问地址就和之前的不一样了,当然,之前的地址也还是可以用的,新的访问地址为:{gateway-ip}:{gateway-port}/doc.html。

界面的大部分功能都一样,但会比原生的swagger界面好看并排版更加明显,容易理解。 

进入地址后看到这个页面就说明成功啦。

 至于knife4j的配置的话,只要把依赖换成knife4j的依赖就可以了,其他的都不用改,也不用加@EnableSwaggerBootstrapUI这个注解,不过一样是Gateway和微服务都得加上依赖,然后重启就可以了。

至于knife4j里其实还有其他的功能,不过就不在这里叙述了,有兴趣的同学可以去我上面参考的那篇knife4j的文章里看,里面比较详细。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐