前言
本人想搭建一个企业级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配置文件
在yml配置文件添加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路由规则中加入这个拦截器示例如下:
微服务中添加swagger的依赖并配置config
swagger依赖就是上面getaway中添加的两处依赖,直接照搬就行
微服务中添加swagger的依赖并配置config就可以生效了,默认路径是在:ip:微服务端口(8003)/doc.html即可看到效果
代码如下:
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)
下一章配置oauth2和swagger、getaway集成