Spring Cloud Gateway Gateway Integration Knife4j


When we use Knife4j to manage service interface documents, it is very beautiful and comfortable; but when there are more and more microservices in the system, we need to access more than a dozen ports, which is very painful; is there
any One way is to display the interface documents of all microservices on the same visual page, so that we can manage them in a unified way; for this reason, we can manage the interface documents of all microservices through SpringCloudGateway gateway + registration center nacos+Knige4j

1: Environment preparation

The pre-basis of this article is that by default, everyone has already understood the Gateway gateway, Nacos registration center, swagger and Knife4j configuration. If you are not clear, read these articles in the environment:

Gateway-02-gateway routing rules and filters
Nacos-02-Nacos configuration center and service discovery
swagger-springboot detailed
swagger-beautiful Knife4j documentation

2: gateway service settings

1: guide package

 <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

2: yml configuration

server:
  port: 18080

spring:
  application:
    name: code-gateway
  cloud:
    nacos:
      discovery:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
      config:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
        name: dev.yml
        file-extension: yml

    gateway:
      discovery:
        locator:
          # enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
          enabled: true
          # lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
          lower-case-service-id: true

      routes:
        - id: code-order
          uri: lb://code-order
          predicates:
            - Path=/code-order/**
          filters:
            - StripPrefix=1
        - id: code-user
          uri: lb://code-user
          predicates:
            - Path=/code-user/**
          filters:
            - StripPrefix=1

3: Add a configuration class to get the service list from the gateway service

When integrating swagger with a single architecture such as SpringBoot, we group business based on the package path, and then display different modules on the front end. Under the microservice architecture, a service is similar to a business group we originally wrote. The grouping interface provided by springfox-swagger is swagger-resource, which returns information such as the name and address of the grouping interface. However, under the Spring Cloud microservice architecture, we need to rewrite the interface and dynamically discover all through the registry center of the gateway. microservices documentation,
insert image description here

package com.wkl.codegateway.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.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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;
 
/**
 * 使用Spring Boot单体架构集成swagger时,是通过包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,单个服务类似于原来业务组;
 * springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息;
 * 在Spring Cloud微服务架构下,需要swagger-resource重写接口,由网关的注册中心动态发现所有的微服务文档
 */
@Primary
@Configuration
public class SwaggerResourceConfig implements SwaggerResourcesProvider
{
    
    
    /**
     * swagger2默认的url后缀
     */
    private static final String SWAGGER2_URL = "/v2/api-docs";

    /**
     * 路由定位器
     */

    @Autowired
    private RouteLocator routeLocator;

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

    /**
     * 获取 Swagger 资源
     */
    @Override
    public List<SwaggerResource> get() {
    
    
        //接口资源列表
        List<SwaggerResource> resources = new ArrayList<>();
        //服务名称列表
        List<String> routeHosts = new ArrayList<>();

        // 1. 获取路由Uri 中的Host=> 服务注册则为服务名=》app-service001
        routeLocator.getRoutes()
                .filter(route -> route.getUri().getHost() != null)
                .filter(route -> !gatewayName.equals(route.getUri().getHost()))
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 2. 创建自定义资源
        Set<String> existsServer = new HashSet<>();     // 去重,多负载服务只添加一次
        for (String routeHost : routeHosts) {
    
    
            String serviceUrl = "/" + routeHost + SWAGGER2_URL; // 后台访问添加服务名前缀
            if(!existsServer.contains(serviceUrl)){
    
    
                existsServer.add(serviceUrl); //加过的放在set中
                SwaggerResource swaggerResource = new SwaggerResource(); // 创建Swagger 资源
                swaggerResource.setUrl(serviceUrl); // 设置访问地址
                swaggerResource.setName(routeHost); // 设置名称
                swaggerResource.setSwaggerVersion("2.0");
                resources.add(swaggerResource);
            }
        }
        return resources;
    }
}

4: Rewrite and override the /swagger-resources interface

  • When using the Spring Boot monolithic architecture to integrate swagger, the business is grouped through the package path, and then different modules are displayed on the front end, while under the microservice architecture, a single service is similar to the original business group;
  • The packet interface provided by springfox-swagger is swagger-resource, which returns information such as the packet interface name and address;
  • Under the Spring Cloud microservice architecture, swagger-resource needs to rewrite the interface, and all microservice documents are dynamically discovered by the registry of the gateway
package com.wkl.codegateway.handle;

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;
 
/**
 * 获取api接口信息
 */
@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)));
    }
}

So far, the configuration of the gateway service is over

3: Other business logic service settings

When we set up the gateway service, other configuration services are nothing more than configuring the original information of Knige4j. For example, I have two services, one is the user (personnel) module and the other is the order (order) module.

1: Other service guide packages


        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

2: Other service configuration yml

The yml configuration of other services has nothing to do with the gateway, and only needs to configure the normal nacos registration center; ensure that the service is registered to nacos and can be read by the gateway.

server:
  port: 18081
spring:
  application:
    name: code-order
  cloud:
    nacos:
      #注册中心
      discovery:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
      config:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
        name: dev.yml
        file-extension: yml

3: Other service settings swagger configuration class

package com.example.codeorder.config;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.util.ArrayList;

/**
 * @author Boss
 * ip:port/doc.html
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    

    @Value("${spring.application.name}")
    private String serviceName;

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket() {
    
    
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //注意basePackage改成自己每个项目的路径
                .apis(RequestHandlerSelectors.basePackage("com.example.codeorder.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    //配置swagger信息
    private ApiInfo apiInfo() {
    
    
        Contact contact = new Contact("作者姓名", "", "");
        return new ApiInfo(serviceName+"接口文档",
                "Api Documentation",
                "1.0",
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>()
        );
    }
}

4: Optimization: configure swagger as a microservice

According to the above method, I need to create a swaggerconfig class for each microservice, so when there are too many microservices, this is not appropriate;
we can extract the configuration of swagger into a microservice, and other services can be introduced into it. Can

1: Create code-swagger service

insert image description here

2: guide package

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.wkl</groupId>
        <artifactId>gateway-nacos-knife4j</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.wkl</groupId>
    <artifactId>code-swagger</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>code-swagger</name>
    <description>code-swagger</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

    </dependencies>

</project>

3: Write the configuration file

Configuration 1: The entity class of swagger, which is written in the corresponding configuration file

package com.wkl.codeswagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author Boss
 * ip:port/doc.html
 */
//@Configuration
@Configuration
//@EnableSwagger2
@EnableKnife4j
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerConfig {
    
    


    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
    
    
        return new SwaggerProperties();
    }

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket(SwaggerProperties swaggerProperties) {
    
    
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
    
    
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

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

        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(PathSelectors.any());

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

        return builder.build();
    }

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

Configuration 2: swagger configuration class

package com.wkl.codeswagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author Boss
 * ip:port/doc.html
 */
//@Configuration
@Configuration
//@EnableSwagger2
@EnableKnife4j
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerConfig {
    
    


    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
    
    
        return new SwaggerProperties();
    }

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket(SwaggerProperties swaggerProperties) {
    
    
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
    
    
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

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

        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(PathSelectors.any());

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

        return builder.build();
    }

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

4: Add spring configuration in META-INF

Because the code-swagger module is a public module and not a web project, if you want to make the configuration class in the module take effect and add it to the ioc container, you must first create the folder META-INF under the resources directory, and create spring under the folder. factories file, the content is as follows

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.wkl.codeswagger.config.SwaggerConfig

5: Other services refer to this swagger microservice

Other service modules can refer to the swagger service as a microservice in the pom

   <!--引入swagger模块配置代码-->
        <dependency>
            <groupId>com.wkl</groupId>
            <artifactId>code-swagger</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

6: Configuration parameters of each module

Because other business modules have introduced the configuration class of the swagger module, you only need to configure it in the yml file, or configure it directly in nacos, which is convenient for subsequent maintenance and modification

# swagger配置
swagger:
  enabled: true
  title: user模块接口文档
  license: Powered By ruoyi
  licenseUrl: https://ruoyi.vip
  basePackage: com.wkl.codeuser.controller

Note: Other configuration parameters can be configured by referring to the parameters in the entity class of Configuration 1 in 4.3, just ensure that the parameter name is the same as the name of the entity class;
insert image description here

Guess you like

Origin blog.csdn.net/qq_41694906/article/details/126666731