天天看點

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔

  • 需求
  • 解決
    • 配置swagger2
      • pom
      • swagger配置類
      • order-service測試使用
    • 配置gateway網關
      • pom
      • yml
      • GatewayApplication啟動類
      • SwaggerProvider
      • SwaggerHandler
      • SwaggerHeaderFilter
      • AuthorizeFilter全局過濾器
  • 測試

需求

1、有兩個微服務user-service和order-service都需要使用swagger2生成接口文檔,後期還有其他微服務。如果每一個都去通路

http://ip:port/swagger-ui.html

會比較麻煩。

2、後端接口通路都是需要攜帶token令牌才能進行請求通路的,如果每一個微服務都需要做請求攔截,代碼備援繁瑣,後期不便于維護。

想達到的效果: 統一通路gateway網關

http://localhost:10010/swagger-ui.html

位址,在網關中統一管理所有微服務的swagger文檔,通路使用者管理的單點登入擷取token,然後攜帶token去通路微服務的其他接口。

端口如下:

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

頁面效果:

user-service使用者微服務的接口文檔

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

切換order-service訂單微服務的接口文檔

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

解決

配置swagger2

pom

<dependency>
    <groupId>com.spring4all</groupId>
    <artifactId>swagger-spring-boot-starter</artifactId>
    <version>1.9.1.RELEASE</version>
</dependency>
           

swagger配置類

這裡和springboot使用swagger一樣

/**
 * Swagger2配置資訊
 */
@Configuration
@EnableSwagger2
public class Swagger2Config {

//    @Value("${swagger.enable}")
//    private boolean swaggerEnable;
    //是否允許顯示swagger。此值可在application.yml中設定。
    //作為開關,可在生産環境和開發環境打開或關閉,簡便易行。

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(webApiInfo())
                //.enable(swaggerEnable)
                .select()
                // 針對方法注解ApiOperation生成接口文檔api
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();

    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("網站-API文檔")
                .description("本文檔描述了網站微服務接口定義")
                .version("1.0")
                .contact(new Contact("封于修", "http://zysheep.cn", "[email protected]"))
                .build();
    }
}
           

這裡我把swagger2抽成了一個公共的元件,微服務需要就引入坐标依賴,并在啟動類上使用

@ComponentScan

注解掃描元件到ioc容器中即可使用swagger2

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

order-service測試使用

這裡使用

@ComponentScan(basePackages = {"cn.zysheep"})

掃描swagger注冊為容器中的Bean

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試
SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

配置gateway網關

我的環境

springboot springcloud spring-cloud-alibaba
2.3.9.RELEASE Hoxton.SR10 2.2.6.RELEASE
SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

pom

<!--網關-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務發現依賴-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--swagger依賴 Spring Cloud Gateway整合Swagger聚合微服務系統API文檔-->
<dependency>
    <groupId>com.spring4all</groupId>
    <artifactId>swagger-spring-boot-starter</artifactId>
    <version>1.9.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>cn.zysheep</groupId>
    <artifactId>commons-util</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
           

yml

server:
  port: 10010 # 網關端口


spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        namespace: 4f64927e-a2a7-4e2f-b657-3da7c57eb008
    gateway:
      routes: # 網關路由配置
        - id: user-service # 路由id,自定義,隻要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标位址 http就是固定位址
          uri: lb://user-service # 路由的目标位址 lb就是負載均衡,後面跟服務名稱
          predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件
            - Path=/user/** # 這個是按照路徑比對,隻要以/user/開頭就符合要求
          filters:
            - StripPrefix=1
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1
logging:
  level:
    cn.zysheep.gateway: debug
           

GatewayApplication啟動類

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

SwaggerProvider

配置SwaggerProvider,擷取

Api-doc

,即SwaggerResources。

@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
    public static final String API_URI = CommonConstants.SWAGGER_URL;
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;


    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        //取出gateway的route
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        //結合配置的route-路徑(Path),和route過濾,隻擷取有效的route節點
        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
                .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
                        .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                        .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
                                // 1、公共路徑使用服務名的http://localhost:10010/user/v2/api-docs
//                                predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
//                                        .replace("/**", API_URI)))));
                                // 2、公共路徑使用服務名的http://localhost:10010/user-service/v2/api-docs
                                "/"+routeDefinition.getId()+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;
    }
}
           

SwaggerHandler

因為Gateway裡沒有配置SwaggerConfig,而運作Swagger-ui又需要依賴一些接口,是以我的想法是自己建立相應的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)));
    }

}
           

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, SwaggerProvider.API_URI)) {
                return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);
        };
    }
}
           

AuthorizeFilter全局過濾器

@Order(-1)
@Component
@Slf4j
public class AuthorizeFilter implements GlobalFilter {

    /**
     *  處理目前請求,有必要的話通過{@link GatewayFilterChain}将請求交給下一個過濾器處理
     *
     * @param exchange 請求上下文,裡面可以擷取Request、Response等資訊
     * @param chain 用來把請求委托給下一個過濾器
     * @return {@code Mono<Void>} 傳回标示目前過濾器業務結束
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.debug("請求:  {}", exchange.getRequest().getURI().getPath());
        log.info("---------enter gateway interceptor--------");
        String url = exchange.getRequest().getURI().getPath();
        log.info("url---------------{}", url);
        //登入直接放行
        if (StringUtils.contains(url, CommonConstants.LOGIN_URL)) {
            log.info("     login .............. ");
            return chain.filter(exchange);
        }else if(StringUtils.contains(url, CommonConstants.SWAGGER_URL)){//swagger直接放行
            log.info("     swagger2 .............. ");
            return chain.filter(exchange);
        } else {

            //擷取token
            ServerHttpRequest request = exchange.getRequest();
            String token = request.getHeaders().getFirst("token");
            //驗證token
            Map<String, Object> tokenMap = JWTUtil.parseToken(token);
            log.info("-----{}", tokenMap);
            if (ObjectUtils.isEmpty(tokenMap.get("ERR_CODE"))) {
                request = exchange.getRequest().mutate().headers(httpHeaders -> {
                    httpHeaders.add("tokenMap", JSON.toJSONString(tokenMap));
                }).build();
                ServerWebExchange build = exchange.mutate().request(request).build();
                return chain.filter(build);
            } else {
                return errorInfo(exchange, tokenMap.get("ERR_MSG").toString(), 500);
            }
        }
    }

    /**
     * 傳回response
     *
     * @param exchange
     * @param message  異常資訊
     * @param status   data中的status
     * @return
     */
    public static Mono<Void> errorInfo(ServerWebExchange exchange, String message, Integer status) {
        // 自定義傳回格式
        Map<String, Object> resultMap = new HashMap<>(8);
        resultMap.put("code", status);
        resultMap.put("msg", StringUtils.isBlank(message) ? "服務異常!" : message);
        resultMap.put("data", null);
        return Mono.defer(() -> {
            byte[] bytes;
            try {
                bytes = new ObjectMapper().writeValueAsBytes(resultMap);
            } catch (JsonProcessingException e) {
                log.error("網關響應異常:", e);
                throw new RuntimeException("資訊序列化異常");
            } catch (Exception e) {
                log.error("網關響應異常:", e);
                throw new RuntimeException("寫入響應異常");
            }
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString());
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Flux.just(buffer));
        });
    }
}
           

測試

測試流程:

  1. 通路gateway網關

    http://localhost:10010/swagger-ui.html

  2. 通路使用者微服務的單點登入擷取token值
  3. 複制token,通路訂單微服務的訂單管理查詢訂單

1、通路gateway網關

http://localhost:10010/swagger-ui.html

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

2、通路使用者微服務的單點登入擷取token值

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

3、複制token,通路訂單微服務的訂單管理查詢訂單

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

成功響應結果

SpringCloud Gateway整合Swagger2聚合微服務系統API文檔需求解決測試

繼續閱讀