Spring cloud gateway aggregate swagger document

Demand background

spring cloud builds a micro service system. Each business module uses the swagger open document interface to query. The swagger document aggregation query interface is provided in the business gateway module, which can be viewed by selecting the business module classification.

Frame selection, version and main functions

  1. spring boot 2.1.6.RELEASE
  2. spring cloud Greenwich.SR3
  3. spring cloud gateway 2.1.3.RELEASE gateway component
  4. knife4j 2.0.1 enhances the swagger ui style, and the gateway uses its starter dependency
  5. swagger bootstrap ui 1.9.6 enhanced swagger ui style
  6. spring4all-swagger 1.9.0.RELEASE configures swagger parameters, eliminating code development

Division of module responsibilities

  1. swagger component
    Develop a swagger spring boot starter in the project and integrate swagger bootstrap ui 1.9.6 and spring4all swagger 1.9.0 Release, providing @ EnableSwagger annotation service externally

  2. Business module
    Reference the customized swagger spring boot starter, and add the basic swagger information configuration of this module in the configuration file.

  3. Gateway module
    Reference knife4j to integrate swagger, and develop filter, handler and config to aggregate multi module swagger

Example of development steps

swagger component

pom.xml file dependency

<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>swagger-bootstrap-ui</artifactId>
	<version>1.9.6</version>
</dependency>
<dependency>
	<groupId>com.spring4all</groupId>
	<artifactId>swagger-spring-boot-starter</artifactId>
	<version>1.9.0.RELEASE</version>
</dependency>

Custom annotation

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableSwagger2Doc
@EnableSwaggerBootstrapUI
@Import(SwaggerCommandLineRunner.class)
public @interface EnableSwagger {
}

Note that @ EnableSwagger2Doc, @ enableswaggerbootstrap UI annotation, @ EnableSwagger2Doc annotation can document the swagger configuration and avoid the business module from redeveloping the code of swagger, @ enableswaggerbootstrap UI improves the swagger ui interface.

Specifies the default access port for swagger

@Slf4j
@Component
public class SwaggerCommandLineRunner implements CommandLineRunner {

    @Value("${server.port:8080}")
    private String serverPort;

    @Override
    public void run(String... args) {
        log.info("swagger url:http://localhost:" + serverPort + "/doc.html");
    }
}

In this way, the integration of this component is completed. This component exists in the public component of the project in the form of multiple module s, which can be referenced by maven.

Business module development

Use @ EnableSwagger annotation in application (self-developed one, don't make a mistake)

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.hy.demo.**.mapper")
@EnableSwagger
public class DemoApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
		ctx.start();
	}

}

Configuration file plus swagger information:

swagger:
  enabled: true
  title: hy demo
  base-package: com.hy.demo

In this way, the business module part is completed, which is very simple and has zero code intrusion

Gateway development

The development of the gateway is an important play, which requires the integration of knife4j (this framework is the final version of the swagger bootstrap ui, the final version has changed its name, and the development is the same person), and the adaptation of the request url

pom. Adding the dependency of swagger to XML

<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-spring-boot-starter</artifactId>
	<version>2.0.1</version>
</dependency>

In the gateway part of the configuration file, routes should be set separately for business interface configuration and swagger configuration, for example:

spring:
  application:
    name: gate
  cloud:
    gateway:
      discovery:
        locator:
          #enabled: true0
          enabled: false
          lower-case-service-id: true
      routes:
        - id: demo
          uri: lb://demo
          predicates:
            - Path=/api/json/hy/demo/**

        - id: demoSwagger
          uri: lb://demo
          predicates:
            - Path=/demo/**
          filters:
            - SwaggerHeaderFilter
            - StripPrefix=1

We assume that the interface URL prefix definition of business forwarding is: / api/json/hy/demo. The demo is the name of the business module. The business interface does not need to add a filter for URL processing.
The suffix of route id is Swagger, so StripPrefix filter and custom filter SwaggerHeaderFilter need to be added.

Developed by ResourceConfig, this class is used to extract business module information from route information and display it in the drop-down box in the upper left corner of swagger ui

/**
 *  Get the SwaggerResources list information, that is, the business module list
 *  The list data is filled in the drop-down box in the upper left corner of the swagger ui
 * @description:
 * @author: demo
 * @create: 2020-02-25 17:15
 **/
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {

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

	private final RouteLocator routeLocator;
	private final GatewayProperties gatewayProperties;


	@Override
	public List<SwaggerResource> get() {
		List<SwaggerResource> resources = new ArrayList<>();
		List<String> routes = new ArrayList<>();
		// Only the routing information with Swagger suffix is extracted
		routeLocator.getRoutes().filter(r -> r.getId().endsWith("Swagger")).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("/**", SWAGGER_URI))));
		});

		return resources;
	}

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

The example effect is as follows:
[figure 1]

Processing of swagger ui static resources:

/**
 * @description:
 * @author: demo
 * @create: 2020-02-25 17:19
 **/
@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")
	public Mono<ResponseEntity> swaggerResources() {
		return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
	}
}

swagger filter sample code

/**
 * @description:
 * @author: demo
 * @create: 2020-02-25 17:01
 **/
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
	private static final String HEADER_NAME = "X-Forwarded-Prefix";

	private static final String SWAGGER_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,SWAGGER_URI)) {
				return chain.filter(exchange);
			}
			String basePath = path.substring(0, path.lastIndexOf(SWAGGER_URI));
			ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
			ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
			return chain.filter(newExchange);
		};
	}
}
Business filter modification

Since this component is a business gateway component, there must be a general filter to complete the functions of token verification and ID card identification. Remember to release the URL of "/ V2 / API docs" in these filters. Example code:

if (StringUtils.endsWithIgnoreCase(path,"/v2/api-docs")) {
	return chain.filter(exchange);
}

Test verification

Start the corresponding registration center, business module and gateway module to verify whether the processing of swagger documents and business interfaces can be met at the same time.

Focus on Java high concurrency and distributed architecture, and share more technical dry goods and experiences. Please pay attention to official account: Java architecture community
You can scan the QR code on the left to add friends and invite you to join the wechat group of Java architecture community to discuss technology together

Tags: swagger

Posted by ponsho on Tue, 24 May 2022 02:34:56 +0300