spring-cloud-gateway聚合swagger文檔

需求背景

spring cloud搭建微服務系統,每個業務模塊使用swagger開放文檔接口查詢,在業務網關模塊提供swagger文檔聚合查詢接口,可以通過選擇業務模塊分類查看。

框架選型、版本及主要功能

  1. spring boot 2.1.6.RELEASE
  2. spring cloud Greenwich.SR3
  3. spring cloud gateway 2.1.3.RELEASE 網關組件
  4. knife4j 2.0.1 增強swagger ui樣式,網關使用其starter依賴
  5. swagger bootstrap ui 1.9.6 增強swagger ui樣式
  6. spring4all-swagger 1.9.0.RELEASE 配置化swagger參數,免去代碼開發

模塊職責劃分

  1. swagger組件
    開發一個項目內的swagger-spring-boot-starter,整合swagger bootstrap ui 1.9.6和spring4all-swagger 1.9.0.RELEASE,對外提供@EnableSwagger註解服務

  2. 業務模塊
    引用自定義的swagger-spring-boot-starter,同時在配置文件中添加本模塊的swagger基礎信息配置。

  3. 網關模塊
    引用knife4j整合swagger,並開發filter、handler、config對多模塊的swagger進行聚合

開發步驟示例

swagger組件

pom.xml文件依賴

<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>

自定義註解

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

注意@EnableSwagger2Doc,@EnableSwaggerBootstrapUI註解,@EnableSwagger2Doc註解能將swagger配置文檔化,避免業務模塊再開發swagger的代碼,@EnableSwaggerBootstrapUI就是改進了swagger ui界面。

指定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");
    }
}

這樣這個組件就集成完畢了,這個組件以多module的形式存在於項目公共組件中,使用maven引用即可。

業務模塊開發

application中使用@EnableSwagger註解(自行開發的那個,不要搞錯了)

@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();
	}

}

配置文件加上swagger信息:

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

如此業務模塊部分就完成了,非常簡單,代碼零侵入

網關開發

網關的開發是重頭戲,裏面需要集成knife4j(這個框架是swagger bootstrap ui的最終版本,最終版改了個名字,開發是同一個人),並且對請求url進行適配

pom.xml添加swagger的依賴

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

配置文件gateway部分,對業務接口配置和swagger配置要單獨設置routes,例如:

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

我們假定業務轉發的接口URL前綴定義:/api/json/hy/demo,demo爲業務模塊名稱,業務接口對URL處理不需要加過濾器。
route id後綴爲Swagger結尾,需要添加StripPrefix過濾器和自定義過濾器SwaggerHeaderFilter。

ResourceConfig開發,這個類的作用是從route信息裏抽取業務模塊信息,並且展示在swagger ui左上角的下拉框裏

/**
 *  獲取SwaggerResources列表信息,即業務模塊列表
 *  列表數據填充在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<>();
		// 只抽取後綴爲Swagger的路由信息
		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;
	}
}

示例效果如下圖:
【圖一】

swagger ui靜態資源的處理:

/**
 * @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 過濾器示例代碼

/**
 * @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);
		};
	}
}
業務過濾器修改

由於該組件是業務網關組件,肯定會有通用的過濾器,來完成token校驗,身份證識別等功能,記得在這些過濾器將"/v2/api-docs"的URL放行,示例代碼:

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

測試驗證

啓動相應的註冊中心、業務模塊、網關模塊進行驗證,驗證swagger文檔、業務接口的處理能否同時滿足。

專注Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術
Java架構社區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章