wmSafe致力于互联网资源
道可道,非恒道,名可名,非恒名

【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)

本文最后更新于2022-6-30,已经有666 天没有更新,如果文章内容或图片资源失效,请留言反馈,我们会及时处理,谢谢!

前言

本人想搭建一个企业级spring cloud微服务架构,在已经完成getaway和oauth2上集成swagger,发现真的有许许多多的坑等着我....光是集成这个swagger踩的坑就耗费了两天时间,所以在这里记录一下。

首先在getaway的pom.xml文件中添加:


<!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.plugin</groupId>
                    <artifactId>spring-plugin-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.plugin</groupId>
            <artifactId>spring-plugin-core</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <!-- Swagger 增强knife4j 微服务starter -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-micro-spring-boot-starter</artifactId>
            <version>2.0.8</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.8</version>
            <scope>compile</scope>
        </dependency>

坑点一:
有的spring-plugin-core的版本2.0以下的时候使用swagger的时候会报错,此处需要将spring-plugin-core排除后重新引用。另外说明本人的spring boot版本是2.1.4.RELEASE的,getaway的版本是2.1.0.RELEASE的

然后在getaway中添加swagger配置文件

【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)
目录结构如上图所示

在yml配置文件添加knife4j配置

【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)
我这里是用的nacos,各位如果用其他的也一样,或者写在bootstrap.yml
(所有微服务都要添加这个配置)

随后配置swagger的ResourcesProvider资源提供者

代码贴上:


import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider{
    public static final String API_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<>();
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        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(),
                                predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                        .replace("/**", API_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;
    }

}

在getaway中配置swagger的接口供其它服务调用

因为其他微服务的swagger需要主控文档的接口获取一些参数,所以这里要在getaway中写入几个Controller接口,代码如下:


import com.bi.cloud.config.swagger.SwaggerProvider;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;

import java.util.List;
import java.util.Optional;

@Api(value = "/swagger-resources")
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerResourceController {
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerProvider swaggerProvider;

    @Autowired
    public SwaggerResourceController(SwaggerProvider swaggerProvider) {
        this.swaggerProvider = swaggerProvider;
    }

    @RequestMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @RequestMapping("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @RequestMapping
    public Mono<ResponseEntity<List<SwaggerResource>>> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerProvider.get(), HttpStatus.OK)));
    }

}

getaway配置swagger重大坑点

因为knife4j不会将basePath加入其他微服务调用的路径上,会导致在getaway中调试其他接口时会404,因为拼接的接口请求路径不正常,没有加入getaway中配置的路由转发的规则。
解决办法就是配置一个拦截器,代码如下:


import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

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

然后在getaway中的bootstrap.yml路由规则中加入这个拦截器示例如下:
【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)

微服务中添加swagger的依赖并配置config

swagger依赖就是上面getaway中添加的两处依赖,直接照搬就行
微服务中添加swagger的依赖并配置config就可以生效了,默认路径是在:ip:微服务端口(8003)/doc.html即可看到效果
【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)
代码如下:


import com.google.common.collect.Lists;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;


@Configuration
@EnableOpenApi
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        //schema
        List<GrantType> grantTypes=new ArrayList<>();
        //密码模式
        String passwordTokenUrl="http://localhost:8002/oauth/token";
        ResourceOwnerPasswordCredentialsGrant resourceOwnerPasswordCredentialsGrant=new ResourceOwnerPasswordCredentialsGrant(passwordTokenUrl);
        grantTypes.add(resourceOwnerPasswordCredentialsGrant);
        OAuth oAuth=new OAuthBuilder().name("oauth2")
                .grantTypes(grantTypes).build();
        //context
        //scope方位
        List<AuthorizationScope> scopes = new ArrayList<>();
        scopes.add(new AuthorizationScope("read","read  resources"));
        scopes.add(new AuthorizationScope("write","write resources"));
        scopes.add(new AuthorizationScope("reads","read all resources"));
        scopes.add(new AuthorizationScope("writes","write all resources"));
        Predicate<HttpMethod> methodSelector = new Predicate<HttpMethod>() {
            @Override
            public boolean test(HttpMethod httpMethod) {
                return false;
            }
        };
        Predicate<OperationContext> operationSelector = new Predicate<OperationContext>() {
            @Override
            public boolean test(OperationContext operationContext) {
                return false;
            }
        };
        SecurityReference securityReference = new SecurityReference("oauth2",scopes.toArray(new AuthorizationScope[]{}));
        SecurityContext securityContext = new SecurityContext(Lists.newArrayList(securityReference),PathSelectors.ant("/api/**"),methodSelector,operationSelector);
        //schemas
        List<SecurityScheme> securitySchemes=Lists.newArrayList(oAuth);
        //securyContext
        List<SecurityContext> securityContexts=Lists.newArrayList(securityContext);
        return new Docket(DocumentationType.OAS_30)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.bi.cloud.controller"))
                .paths(PathSelectors.any())
                .build()
                .securityContexts(securityContexts)
                .securitySchemes(securitySchemes)
                .globalRequestParameters(globalRequestParameters())
                .apiInfo(apiInfo());
    }

    private List<RequestParameter> globalRequestParameters() {
        RequestParameterBuilder parameterBuilder = new RequestParameterBuilder().in(ParameterType.HEADER).name("Token").required(false).query(param -> param.model(model -> model.scalarModel(ScalarType.STRING)));
        return Collections.singletonList(parameterBuilder.build());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("APIs微服务接口文档")
                .description("APIs微服务接口管理文档")
                .termsOfServiceUrl("http://127.0.0.1:8003/")
                .version("1.0")
                .build();
    }

}

上方的代码:


        //schema
        List<GrantType> grantTypes=new ArrayList<>();
        //密码模式
        String passwordTokenUrl="http://localhost:8002/oauth/token";
        ResourceOwnerPasswordCredentialsGrant resourceOwnerPasswordCredentialsGrant=new ResourceOwnerPasswordCredentialsGrant(passwordTokenUrl);
        grantTypes.add(resourceOwnerPasswordCredentialsGrant);
        OAuth oAuth=new OAuthBuilder().name("oauth2")
                .grantTypes(grantTypes).build();
        //context
        //scope方位
        List<AuthorizationScope> scopes = new ArrayList<>();
        scopes.add(new AuthorizationScope("read","read  resources"));
        scopes.add(new AuthorizationScope("write","write resources"));
        scopes.add(new AuthorizationScope("reads","read all resources"));
        scopes.add(new AuthorizationScope("writes","write all resources"));
        Predicate<HttpMethod> methodSelector = new Predicate<HttpMethod>() {
            @Override
            public boolean test(HttpMethod httpMethod) {
                return false;
            }
        };
        Predicate<OperationContext> operationSelector = new Predicate<OperationContext>() {
            @Override
            public boolean test(OperationContext operationContext) {
                return false;
            }
        };
        SecurityReference securityReference = new SecurityReference("oauth2",scopes.toArray(new AuthorizationScope[]{}));
        SecurityContext securityContext = new SecurityContext(Lists.newArrayList(securityReference),PathSelectors.ant("/api/**"),methodSelector,operationSelector);
        //schemas
        List<SecurityScheme> securitySchemes=Lists.newArrayList(oAuth);
        //securyContext
        List<SecurityContext> securityContexts=Lists.newArrayList(securityContext);

是用来适配oauth2的密码登录模式的,如有不需要的可以删除,同时也删除下方代码即可


.securityContexts(securityContexts)
.securitySchemes(securitySchemes)

自此getaway和swagger集成就完成了,效果如下:
【Java】记一次spring cloud Alibaba微服务getaway和oauth2集成Swagger(knife4j)(一)

下一章配置oauth2和swagger、getaway集成

本原创文章未经允许不得转载 | 若要转载请注明出处,否则将承担相应的法律责任!
本文链接: https://blog.wm404.com/2022/06/21/98362ddb.html
赞赏排名 赞赏支持

评论

  • captcha
暂无评论,要不来一发?

您的关注就是我们最大的支持

联系我们 关于我们