Spring Cloud 之服务网关 Gateway(二) 集成 Swagger 组件
概述 Swagger 是一个可视化 API 测试工具, 能够有效的构建强大的 Restful API 文档, 省去接口文档管理工作. 如果修改了代码, API 文档也会实时更新. 并且可以部分替代 Postman 用来调试接口
Spring Boot 整合了 swagger 组件, 使用也比较简单. 微服务随着项目的增加, 访问每一个应用的 swagger 显然是不合适的. 我们希望网关可以将所有的应用的 swagger 页面聚合起来. 这样前端只要访问网关的 swagger 的就可以了
Spring Cloud Gateway 整合 Swagger 会有一个麻烦, Gateway 底层是 WebFlux, 而 WebFlux 和 Swagger 不兼容. 所以不能通过一般的 Spring Boot 项目的方式简单的整合 Swagger, 否则启动的时候会报错. 常用的做法是自定义几个配置类来实现
编写简单案例 聚合模块说明 版本说明
Spring Boot : 2.0.9.RELEASE
Spring Cloud: Finchley.RELEASE
项目整体结构采用 maven 多 Module 结构
模块端口说明Demo父项目Eureka15002注册中心Gateway15000网关Comment15003应用服务 项目树
一个注册中心 一个网关 一个应用服务 |_ demo |_ eureka |_ gateway |_ comment |_ pom.xml
编写 Eureka 服务 参考: Spring Cloud 之 Eureka 服务注册与发现
配置信息
编写应用程序服务 Comment-server 编写 pom 文件
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> </dependencies>
编写工程的配置文件,
eureka: client: service-url: defaultZone: http://localhost:15002/eureka/ instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15 perfer-in-address: true server: port: 15003 spring: application: name: comment-server profiles: active: dev
编写配置类
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2)// .apiInfo(apiInfo())// .select()// .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))// .paths(PathSelectors.any())// .build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder()// .title("Swagger API")// .description("test")// .termsOfServiceUrl("")// .contact(new Contact("Spring Cloud China","http://springcloud.cn",""))// .version("2.0")// .build(); } }
编写控制器
@RestController @RequestMapping("/comments") @Slf4j @Api("comments") public class CommentController { @Autowired private CommentService commentService; @ApiOperation(value = "获取评论详情", notes = "根据评论 id 获取详情") @ApiParam(value = "评论id") @GetMapping("/detail/{id}") public String getCommentDetails(@PathVariable("id") String id) { log.debug("Get comment by id [{}]", id); return id; } }
验证效果
服务启动后, 通过访问 http://localhost:15003/swagger-ui.html 访问 API 文档界面
编写 Gateway 网关服务 编写 pom 文件
<dependencies> <!-- 健康监控 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Spring Cloud Gateway 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> </dependencies>
application.yml 文件配置
spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true # 开启基于服务发现的路由规则 lower-case-service-id: true # 开启小写的 serviceId 进行基于服务路由的转发 routes: - id: comment_server_route uri: lb://comment-server # 路由集群内其他服务,url需要用[lb://]+[serviceId] predicates: - Path=/comment/** filters: - SwaggerHeaderFilter - StripPrefix=1 loadbalancer: retry: enabled: true # 内部已默认开启负载均衡 eureka: client: service-url: defaultZone: http://localhost:15002/eureka/ # 指定注册中心地址, 以便使用服务发现功能 instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15 perfer-in-address: true server: port: 15000 management: endpoints: health: enabled: true gateway: enabled: true web: exposure: include: "*" # 暴露所有端点, 默认是 info, health logging: level: org.springframework.cloud.gateway: debug
配置 GatewaySwaggerProvider, 获取 Api-doc, 即 GatewaySwaggerProvider
@Component @Primary public class GatewaySwaggerProvider implements SwaggerResourcesProvider { public static final String API_URI = "/v2/api-docs"; private final RouteLocator routeLocator; private final GatewayProperties gatewayProperties; public GatewaySwaggerProvider(RouteLocator routeLocator, GatewayProperties gatewayProperties) { this.routeLocator = routeLocator; this.gatewayProperties = gatewayProperties; } @Override public List<SwaggerResource> get() { List<SwaggerResource> resources = new ArrayList<>(); List<String> routes = new ArrayList<>(); // 取出 Spring Cloud Gateway 中的 route routeLocator.getRoutes()// .subscribe(route -> routes.add(route.getId())); // 结合 application.yml 中的路由配置, 只获取有效的 route 节点 gatewayProperties.getRoutes().stream()// .filter(routeDefinition -> routes.contains(routeDefinition.getId()))// .forEach(routeDefinition -> routeDefinition.getPredicates().stream()// .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName().toLowerCase()))// .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs()// .get(NameUtils.GENERATED_NAME_PREFIX + "0")// .replace("/**", API_URI))))); return resources; } private SwaggerResource swaggerResource(String name, String location) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion("2.0"); return swaggerResource; } }
编写 swagger-resource 端点
@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))); } }
配置过滤器
(**Finchley.SR2 版本可跳过这一步, 配置文件里的 - SwaggerHeaderFilter 也不用配置 **)
@Component public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = "X-Forwarded-Prefix"; @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); if(!StringUtils.endsWithIgnoreCase(path, GatewaySwaggerProvider.API_URI)){ return chain.filter(exchange); } String basePath = path.substring(0,path.lastIndexOf(GatewaySwaggerProvider.API_URI)); ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME,basePath).build(); ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newExchange); }; } }
启动类
@SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
发送请求测试效果 注册中心(eureka), 网关服务(gateway) 和应用服务(comment-server)依次启动后, 访问 http://localhost:15000/swagger-ui.html 可以看到 swagger 页面
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan