SpringCloud Gateway + Nacos 多模块下整合swagger2

x33g5p2x  于2022-03-21 转载在 Spring  
字(8.4k)|赞(0)|评价(0)|浏览(497)

前言:
我们经常在springboot单体项目中,集成swagger来整合接口文档;
但是在微服务springcloud项目下,业务模块众多,如果再像之前一样单独访问每个模块的 swagger-ui.html ,则非常麻烦,怎么解决呢???

既然我们已经通过 nacos和gateway 实现统一访问,那我们也可以通过网关将所有的应用的swagger界面聚合起来。
这样前端开发的时候只需要访问网关的swagger就可以,而不用访问每个应用的swagger。

Spring Cloud Gateway集成Swagger2

整体项目结构如下:

|-springcloud demo
|-----service-gateway		 //网关
|-----service-order 		//订单服务
|-----service-user			//用户服务

1、服务接口(订单order & 用户User)

由于服务接口订单和用户两个模块其实属性是差不多,只是接口不一样,因此就随便挑一个服务的配置来说吧

service-user:用户服务的接口

每个微服务只需要引入和swagger相关的依赖即可

同时引入nacos依赖,pom.xml文件相关配置如下:

<!--Nacos-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--Swagger相关-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

接下来是配置swagger的相关配置,SwaggerConfig.java配置如下:

@Configuration
@EnableSwagger2
@RequiredArgsConstructor
public class SwaggerConfig {
	private final SwaggerProperties swaggerProperties;

	@Value("${token.requestHeader}")
	private String requestHeader;

	@Value("${token.startWith}")
	private String startWith;

	@Bean
	@SuppressWarnings("all")
	public Docket createRestApi() {

		ParameterBuilder ticketPar = new ParameterBuilder();
		List<Parameter> pars = new ArrayList<>();
		ticketPar.name(requestHeader).description("token")
				.modelRef(new ModelRef("string"))
				.parameterType("header")
				.defaultValue(startWith)
				.required(true)
				.build();
		pars.add(ticketPar.build());

		return new Docket(DocumentationType.SWAGGER_2)
				.enable(swaggerProperties.getEnabled())
				.apiInfo(apiInfo())
				.host(swaggerProperties.getHost())
				.select()
				.apis(RequestHandlerSelectors.basePackage("com.xx.controller")) //指定扫描的包名, 如果不指定会扫描整个项目
				.paths(Predicates.not(PathSelectors.regex("/error.*")))
				.build()
				.globalOperationParameters(pars);
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
				.description(swaggerProperties.getDescription())
				.title(swaggerProperties.getTitle())
				.version(swaggerProperties.getVersion())
				.build();
	}
}

SwaggerProperties.java

@Data
@Configuration
@ConfigurationProperties(prefix = "swagger")
public class SwaggerProperties {

	// 是否启用swagger
	private Boolean enabled;

	// 描述
	private String description;

	// 标题
	private String title;

	//版本
	private String version;

	// ip和host
	private String host;
}

application.yml

server:
  port: 8001

spring:
  application:
    name: user
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        server-addr: ${spring.cloud.nacos.server-addr}

token:
  startWith: Bearer
  requestHeader: Authorization

swagger:
  enabled: true
  host: 127.0.0.1:${server.port}
  description: springcloud
  title: swagger 接口文档
  version: @project.version@

开启客户端

@SpringBootApplication
@EnableFeignClients
@EnableSwagger2
public class UserApplication {
	public static void main(String[] args) {
		SpringApplication.run(UserApplication.class, args);
	}
}

当然,在服务的模块中还有和自己服务相关的业务接口(Controller代码),在这里就不列举了

订单模块(service-order)的代码配置和用户是类似的

2、文档聚合

有了nacos注册中心,服务模块的接口也已完成,最后一步是把我们所有的微服务都聚合到一个文档,统一输出到前端,供开发者调用了

引入依赖

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-gateway</artifactId>
 </dependency>
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
 <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger2</artifactId>
 </dependency>
 <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger-ui</artifactId>
 </dependency>

application文件配置

server:
  port: 8080

spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        server-addr: ${spring.cloud.nacos.server-addr}
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: user_route
          uri: lb://user
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=2  // 去掉路径的前缀,具体等级根据path 确定
        - id: order_route
          uri: lb://order
          predicates:
            - Path=/api/order/**
          filters:
            - SwaggerHeaderFilter  // 如果是高版本的SpringCloud Gateway,则去掉,否则会报错
            - StripPrefix=2

文档聚合业务编码

在我们使用Spring Boot等单体架构集成swagger项目时,是通过对包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,我们的一个服务就类似于原来我们写的一个业务组

springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息

在Spring Cloud微服务架构下,我们需要重写该接口,主要是通过网关的注册中心动态发现所有的微服务文档,代码如下:

@Configuration
@Primary
@RequiredArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {

	private final RouteLocator routeLocator;

	private final GatewayProperties gatewayProperties;

	@Override
	public List<SwaggerResource> get() {
		List<SwaggerResource> resources = new ArrayList<>();
		List<String> routes = new ArrayList<>();
		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");
		return swaggerResource;
	}

}

接口:

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

启动配置

// 注意:这里不能添加 @EnableSwagger2
@SpringBootApplication
@EnableFeignClients
public class GateWayApplication {
	public static void main(String[] args) {
		SpringApplication.run(GateWayApplication.class, args);
	}
}

文档展示

最后分别依次启动项目:

  • service-user
  • service-order
  • service-gateway

打开文档地址:http://localhost:8080/swagger-ui.html

注意点

在集成Spring Cloud Gateway网关的时候,会出现没有basePath的情况(即定义的例如/user、/order等微服务的前缀), ,因此,在Gateway网关需要添加一个Filter实体Bean,代码如下:

@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();
            return chain.filter(newExchange);
        };
    }
}

然后在配置文件指定这个filter

spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        server-addr: ${spring.cloud.nacos.server-addr}
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: user_route
          uri: lb://user
          predicates:
            - Path=/api/user/**
          filters:
          	- SwaggerHeaderFilter 
            - StripPrefix=2  // 去掉路径的前缀,具体等级根据path 确定
        - id: order_route
          uri: lb://order
          predicates:
            - Path=/api/order/**
          filters:
            - SwaggerHeaderFilter  // 如果是高版本的SpringCloud Gateway,则去掉,否则会报错
            - StripPrefix=2

特别注意:如果是高版本的Spring Cloud Gateway,那么yml配置文件中的SwaggerHeaderFilter配置应该去掉

相关文章