需求背景
spring cloud搭建微服務系統,每個業務子產品使用swagger開放文檔接口查詢,在業務網關子產品提供swagger文檔聚合查詢接口,可以通過選擇業務子產品分類檢視。
架構選型、版本及主要功能
- spring boot 2.1.6.RELEASE
- spring cloud Greenwich.SR3
- spring cloud gateway 2.1.3.RELEASE 網關元件
- knife4j 2.0.1 增強swagger ui樣式,網關使用其starter依賴
- swagger bootstrap ui 1.9.6 增強swagger ui樣式
- spring4all-swagger 1.9.0.RELEASE 配置化swagger參數,免去代碼開發
子產品職責劃分
-
swagger元件
開發一個項目内的swagger-spring-boot-starter,整合swagger bootstrap ui 1.9.6和spring4all-swagger 1.9.0.RELEASE,對外提供@EnableSwagger注解服務
-
業務子產品
引用自定義的swagger-spring-boot-starter,同時在配置檔案中添加本子產品的swagger基礎資訊配置。
-
網關子產品
引用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架構社群微信群共同探讨技術