1. Add POM dependencies

1.1. Under the pom

Add unified version management

<properties>
   <knife4j.version>3.0.3</knife4j.version>
</properties>

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>com.github.xiaoymin</groupId>
         <artifactId>knife4j-micro-spring-boot-starter</artifactId>
         <version>${knife4j.version}</version>
      </dependency>
      <dependency>
         <groupId>com.github.xiaoymin</groupId>
         <artifactId>knife4j-spring-boot-starter</artifactId>
         <version>${knife4j.version}</version>
      </dependency>
   </dependencies>
</dependencyManagement>
Copy the code

1.2. The gateway of pom

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
Copy the code

1.3. Pom for other services

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-micro-spring-boot-starter</artifactId>
</dependency>
Copy the code

The difference from Gateway is that this package has less UI dependence on Knife4J-Spring-UI.

Configure the Gateway service

Swagger core configuration Docket

package com.holland.gateway.swagger;

import org.springframework.beans.factory.annotation.Value;
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.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

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

    @Bean
    public Docket defaultApi2(a) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(groupApiInfo(name))
                // Group name Do not open group if you want gateway to be recorded to Swagger
//.groupname (" version 2.x ")
                .select()
                // Specify the Controller scan package path
                .apis(RequestHandlerSelectors.basePackage("com.holland." + name + ".controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo groupApiInfo(String name) {
        return new ApiInfoBuilder()
                .title("Back-end interface Documentation -" + name)
                .description("
      
description
"
) .termsOfServiceUrl("N") .contact(new Contact("HollanZang"."https://juejin.cn/user/352263461681214"."[email protected]")) .version("1.0") .build(); }}Copy the code

2.2. Configure swagger interfaces

package com.holland.gateway.swagger;

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.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;

import java.util.Optional;


@RestController
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("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources")
    publicMono<ResponseEntity<? >> swaggerResources() {return Mono.just((newResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); }}Copy the code

2.3. Configure a filter

2.3.1 Official Demo practice

package com.xiaominfo.swagger.service.doc.config;

import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;


/ * * *@author fsl
 * @description: SwaggerHeaderFilter
 * @dateIn the 2019-06-03 s when * /
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String URI = "/v2/api-docs";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if(! StringUtils.endsWithIgnoreCase(path,URI )) {return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            returnchain.filter(newExchange); }; }}Copy the code

2.3.2 My project configuration

package com.holland.gateway.swagger;

import org.apache.commons.lang.StringUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;

public class SwaggerRouteFilter {
    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String URI = "/v2/api-docs";

    public static WebFilter getWebFilter(a) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if(! StringUtils.endsWithIgnoreCase(path, URI)) {return chain.filter(exchange);
            }

            String basePath = path.substring(0, path.lastIndexOf(URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            returnchain.filter(newExchange); }; }}Copy the code
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http
            .addFilterAfter(SwaggerRouteFilter.getWebFilter(), SecurityWebFiltersOrder.FIRST)
            .addFilterAfter(tokenFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
            .addFilterBefore(logFilter(), SecurityWebFiltersOrder.LAST)
            .csrf(ServerHttpSecurity.CsrfSpec::disable);
    return http.build();
}
Copy the code

2.4. Key points: Only by obtaining information of other services can the Swagger of other services be integrated

2.4.1. Service information obtained from the configuration file

Configuration files such as the following can integrate filesystem’s Swagger information

cloud:
  gateway:
    discovery:
      locator:
        enabled: true
    routes:
      - id: filesystem
        uri: http://localhost:8763
        predicates:
          - Path=/filesystem/**
        filters:
          - StripPrefix=1
Copy the code

The Config writing

package com.holland.gateway.swagger;

import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Component
@Primary
public class SwaggerResourceConfig implements SwaggerResourcesProvider {

    /** * This method can only obtain the routing rules in the configuration file */
    @Resource
    private RouteLocator routeLocator;
    @Resource
    private GatewayProperties gatewayProperties;

    @Override
    public List<SwaggerResource> get(a) {
        List<SwaggerResource> resources = new ArrayList<>();

        List<String> routes = newArrayList<>(); routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route  -> { route.getPredicates().stream() .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                    .forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                    .replace("* *"."v2/api-docs"))));
        });
        return resources;
    }

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

2.4.2. Service information obtained through Eureka

Just modify the public List

get() method

@Override
public List<SwaggerResource> get(a) {

    List<SwaggerResource> resources = new ArrayList<>();

    discoveryClient.getServices()
            .stream()
            // Exclude modules that don't need Swagger and their own modules
            .filter(s -> !"eureka".equals(s) && !"admin".equals(s) && !"gateway".equals(s))
            .forEach(appName -> resources.add(swaggerResource(appName, appName + "/swagger/v2/api-docs")));

    return resources;
}
Copy the code

2.4.3. Integrate Gateway itself into Swagger

You only need to change a little bit of code

List<SwaggerResource> resources = new ArrayList<>() {{
    add(swaggerResource("gateway"."/v2/api-docs"));
}};
Copy the code

2.5. Precautions

API /swagger-resources is called when accessing /doc.html. The underlying invokes SwaggerResourceConfig. Get () method.

So if you get an error here, be sure to consider the situation:

  • api/swagger-resourcesThe obtained array cannot be empty; That is, information about other services must be obtained.
  • At least the other servicesknife4j-micro-spring-boot-starterTo ensure that the interface is called/v2/api-docsNot 404.

/v2/api-docs 404 = /v2/api-docs 404

3. Configure other services

Swagger core configuration Docket

package com.holland.gateway.swagger;

import org.springframework.beans.factory.annotation.Value;
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.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

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

    @Bean
    public Docket defaultApi2(a) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(groupApiInfo(name))
                .select()
                // Specify the Controller scan package path
                .apis(RequestHandlerSelectors.basePackage("com.holland." + name + ".controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo groupApiInfo(String name) {
        return new ApiInfoBuilder()
                .title("Back-end interface Documentation -" + name)
                .description("
      
description
"
) .termsOfServiceUrl("N") .contact(new Contact("HollanZang"."https://juejin.cn/user/352263461681214"."[email protected]")) .version("1.0") .build(); }}Copy the code

3.2. Configure a Swagger forwarding Interface

The reason for adding a forwarding interface here is that the access path of the /doc. HTML page is not correct when debugging the interface of other services. But no more official solution was found, so this approach was adopted.

If there is a good way to achieve the hope can comment message exchange ~

package com.holland.filesystem.swagger;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import springfox.documentation.spring.web.json.Json;
import springfox.documentation.swagger2.web.Swagger2ControllerWebFlux;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/** * Swagger forward controller * configures the URL prefix */ for KNIfe4J
@Controller
public class SwaggerForwardController {

    @Resource
    private Swagger2ControllerWebFlux swagger2ControllerWebFlux;

    @GetMapping("/swagger/v2/api-docs")
    publicResponseEntity<? > forward(@RequestParam(value = "group", required = false) String swaggerGroup,
                                     ServerHttpRequest request) {
        final ResponseEntity<Json> documentation = swagger2ControllerWebFlux.getDocumentation(swaggerGroup, request);

        final Map<String, Object> map = JSON.parseObject(documentation.getBody().value(), Map.class);

        map.computeIfPresent("basePath", (k, pathsObj) -> "/filesystem");
        map.computeIfPresent("paths", (k, pathsObj) -> {
            final Map<String, JSONObject> paths = ((JSONObject) pathsObj).toJavaObject(Map.class);
            final Map<String, JSONObject> res = new HashMap<>(paths.size());
            paths.forEach((k1, v) -> res.put("/filesystem" + k1, v));
            return res;
        });

        returnResponseEntity.ok(map); }}Copy the code

4. Access the interface document

And you’re done!