天天看点

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文档需求解决测试

继续阅读