hey-girl东拼西凑原创文章,若有歧义可留言,若需转载需标明出处
前言唠个五毛钱:想做到的效果,通过网关访问swagger地址,可以看到所有服务的API文档。这个要求不过份吧。
开始干活前,我就斗胆先给和我一样懵逼的兄弟扫盲下。不难发现,当我们百度搜索swagger的配置整合时候。我们总是会发现好多兄弟引入的包都不一样。给大家截图看看。下面这些是不是都是我们常见的包。看到五花八门的包是不是给整懵了。所以我就去找了下文档。潦草的学习了下。
swagger是什么?
了表尊重,官网传送先放上swagger
官网首页说的:使用 Swagger 开源和专业工具集为用户、团队和企业简化 API 开发
Swagger 工具的强大功能始于 OpenAPI 规范——RESTful API 设计的行业标准
Swagger提供了哪些产品了。我捡了几个重要的说明下。(下面描述均来自官网)
- Swagger UI:允许任何人(无论是您的开发团队还是您的最终消费者)在没有任何实现逻辑的情况下可视化 API 资源并与之交互。它是根据您的 OpenAPI(以前称为 Swagger)规范自动生成的,带有可视化文档,便于后端实现和客户端使用。
- Swagger Codegen: Swagger Codegen 可以通过为使用 OpenAPI(以前称为 Swagger)规范定义的任何 API 生成服务器存根和客户端 SDK 来简化您的构建过程,因此您的团队可以更好地专注于 API 的实施和采用
- Swagger Editor: 在第一个完全致力于基于 OpenAPI 的 API 的开源编辑器上设计、描述和记录您的 API。Swagger 编辑器是一种开始使用 OpenAPI 规范(以前称为 Swagger)的简单方法,支持 Swagger 2.0 和 OpenAPI 3.0。
- swaggerHub:UI+Codegen+Editor
springfox是又是什么?
先看看springfox的文档springfox文档
springfox是 Springfox java库套件都是关于为使用spring系列项目编写的JSON api自动生成机器和人类可读的规范。 Springfox的工作方式是在运行时检查应用程序一次,根据spring配置、类结构和各种编译时java注释推断API语义 (有道翻译)
springfox的历史: Springfox是由Marty Pitt最初创建的一个项目演变而来的,它被命名为swagger-springmvc。 荣誉归于马蒂 。
扩展对许多针对JSON API规范和文档的不断发展的标准的支持,如swagger、RAML和jsonapi。
浅谈下这俩者啥关系了?
swagger是一个流行的API开发框架,这个框架以“开放API声明”(OpenAPI Specification,OAS)为基础,对整个API的开发周期都提供了相应的解决方案,是一个非常庞大的项目(包括设计、编码和测试,几乎支持所有语言)。
OAS本身是一个API规范,它用于描述一整套API接口,包括一个接口是GET还是POST请求啊,有哪些参数哪些header啊,都会被包括在这个文件中。它在设计的时候通常是YAML格式,这种格式书写起来比较方便,而在网络中传输时又会以json形式居多,因为json的通用性比较强。
由于Spring的流行,Marty Pitt编写了一个基于Spring的组件swagger-springmvc,用于将swagger集成到springmvc中来。而springfox则是从这个组件发展而来,同时springfox也是一个新的项目。
像文章最开始的时候引入的依赖,springfox-swagger2依然是依赖OSA规范文档,也就是一个描述API的json文件,而这个组件的功能就是帮助我们自动生成这个json文件,我们会用到的另外一个组件springfox-swagger-ui就是将这个json文件解析出来,用一种更友好的方式呈现出来。
Springfox其实是一个通过扫描代码提取代码中的信息,生成API文档的工具。API文档的格式不止Swagger的OpenAPI Specification,还有RAML,jsonapi,Springfox的目标同样包括支持这些格式。这就能解释那个swagger2的后缀了,这只是Springfox对Swagger的支持。
说明一个事。首先生成一个满足OSA规范的json文件。然后再通过UI解析这文件展示出来。干好这2件事就可了。
开始整活
上述了半天。也不知你看懂没。但是这都不重要了。重要的是。要开始写代码了。鸡冻!
swagger-ui 位置已从简称http://host/context-path/swagger-ui.html 为http://host/context-path/swagger-ui/index.html OR http://host/context-path/swagger-ui/
使用springfox。先look下文档的一些更新说的啥。如下图。我把几个重要点勾选了下。
涉及的服务和需求说明:
gateway服务
upms服务
common-swagger服务
需要网关和资源服务依赖common-swagger服务
- common-swagger中引入相关依赖包,只需要引入一个这个就好了。其他花里胡哨不需要了哈。
<!--swagger相关包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--webflux 相关包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>provided</scope>
</dependency>
<!--网关 和 swagger 聚合依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<scope>provided</scope>
</dependency>
2.common-swagger中添加资源处理程序配置器,按照文档说的。有2种方式。webMvc或者webFlux。我这里是使用的webFlux.因为需要个gateway整合配置。
配置请看WebFluxSwaggerConfig
public class WebFluxSwaggerConfiguration implements WebFluxConfigurer {
/**
* 添加资源处理器
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.resourceChain(false);
}
}
- 上面说到,当前需求是要整合gateway和其他微服务。所以在common-swagger中添加一个注解EnableGirlSwagger3。为了后续拿到有需要的服务使用。这个注解抛开元注解不说,主要就是2点。一把SwaggerProperties配置注册为Spring的一个Bean。二import用来导入配置类。主要导入的SwaggerAutoConfiguration和GatewaySwaggerAutoConfiguration
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableConfigurationProperties(SwaggerProperties.class)
@Import({ SwaggerAutoConfiguration.class, GatewaySwaggerAutoConfiguration.class })
public @interface EnableGirlSwagger3 {
}
- 接着在common-swagger服务中,添加SwaggerProperties。对应配置鞋子yml即可
package com.hey.girl.common.swagger.support;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: SwaggerProperties 这里没有注册SwaggerProperties
* @author: heihei
* @date: 2021年11月08日 10:37
*/
@Data
@ConfigurationProperties("swagger")
public class SwaggerProperties {
/**
* 是否开启swagger
*/
private Boolean enabled = true;
/**
* swagger会解析的包路径
**/
private String basePackage = "";
/**
* swagger会解析的url规则
**/
private List<String> basePath = new ArrayList<>();
/**
* 在basePath基础上需要排除的url规则
**/
private List<String> excludePath = new ArrayList<>();
/**
* 需要排除的服务
*/
private List<String> ignoreProviders = new ArrayList<>();
/**
* 标题
**/
private String title = "";
/**
* 描述
**/
private String description = "";
/**
* 版本
**/
private String version = "";
/**
* 许可证
**/
private String license = "";
/**
* 许可证URL
**/
private String licenseUrl = "";
/**
* 服务条款URL
**/
private String termsOfServiceUrl = "";
/**
* host信息
**/
private String host = "";
/**
* 联系人信息
*/
private Contact contact = new Contact();
/**
* 全局统一鉴权配置
**/
private Authorization authorization = new Authorization();
/**
* 认证参数
*/
private SwaggerBasic basic = new SwaggerBasic();
@Data
@NoArgsConstructor
public static class Contact {
/**
* 联系人
**/
private String name = "";
/**
* 联系人url
**/
private String url = "";
/**
* 联系人email
**/
private String email = "";
}
@Data
@NoArgsConstructor
public static class Authorization {
/**
* 鉴权策略ID,需要和SecurityReferences ID保持一致
*/
private String name = "";
/**
* 需要开启鉴权URL的正则
*/
private String authRegex = "^.*$";
/**
* 鉴权作用域列表
*/
private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
private List<String> tokenUrlList = new ArrayList<>();
}
@Data
@NoArgsConstructor
public static class AuthorizationScope {
/**
* 作用域名称
*/
private String scope = "";
/**
* 作用域描述
*/
private String description = "";
}
@Data
public static class SwaggerBasic {
/**
* 是否开启 basic 认证
*/
private Boolean enabled;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
}
- 接下来在common-swagger中配置SwaggerAutoConfiguration。在springfox的文档中,给了一个demo的。建议先看看。
package com.hey.girl.common.swagger.config;
/**
* @EnableOpenApi 开启swagger3的注解
* @Description: swagger3配置文件
* @author: heihei
* @date: 2021年11月04日 14:49
*/
@EnableOpenApi
@ConditionalOnProperty(prefix = "swagger", name = "enabled", matchIfMissing = true)
@ConditionalOnMissingClass("org.springframework.cloud.gateway.config.GatewayAutoConfiguration")
public class SwaggerAutoConfiguration {
/**
* 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
* 默认的排除路径,排除Spring Boot默认的错误处理路径和端点(在解析的url规则之上)
* error,由于服务通常加前缀,所以前面/*忽略前缀
*/
private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**", "/*/error");
/**
* swagger会解析的url规则
*/
private static final String BASE_PATH = "/**";
@Bean
public Docket createRestApi(SwaggerProperties swaggerProperties) {
// base-path处理
if (swaggerProperties.getBasePath().isEmpty()) {
swaggerProperties.getBasePath().add(BASE_PATH);
}
// exclude-path处理
if (swaggerProperties.getExcludePath().isEmpty()) {
swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
}
//需要排除的url
List<Predicate<String>> excludePath = new ArrayList<>();
swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));
// 版本请求头处理
List<RequestParameter> pars = new ArrayList<>();
RequestParameterBuilder versionPar = new RequestParameterBuilder().description("灰度路由版本信息")
.in(ParameterType.HEADER).name("VERSION").required(false)
.query(param -> param.model(model -> model.scalarModel(ScalarType.STRING)));
pars.add(versionPar.build());
ApiSelectorBuilder builder = new Docket(DocumentationType.OAS_30)
// // 接口调试地址
.host(swaggerProperties.getHost())
// 将api的元信息设置为包含在json ResourceListing响应中。
.apiInfo(apiInfo(swaggerProperties)).globalRequestParameters(pars)
// 选择哪些接口作为swagger的doc发布
.select()
// 配置要扫描接口的方式
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));
// 过滤path
swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));
swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));
// 授权信息设置,必要的header token等认证信息
// 授权信息全局应用
return builder.build().securitySchemes(Collections.singletonList(securitySchema(swaggerProperties)))
.securityContexts(Collections.singletonList(securityContext(swaggerProperties))).pathMapping("/");
}
/**
* 配置默认的全局鉴权策略的开关,通过正则表达式进行匹配;默认匹配所有URL
*
* @return
*/
private static SecurityContext securityContext(SwaggerProperties swaggerProperties) {
return SecurityContext.builder().securityReferences(defaultAuth(swaggerProperties)).build();
}
/**
* 默认的全局鉴权策略
*
* @return
*/
private static List<SecurityReference> defaultAuth(SwaggerProperties swaggerProperties) {
ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
swaggerProperties.getAuthorization().getAuthorizationScopeList()
.forEach(authorizationScope -> authorizationScopeList.add(
new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()];
return Collections
.singletonList(SecurityReference.builder().reference(swaggerProperties.getAuthorization().getName())
.scopes(authorizationScopeList.toArray(authorizationScopes)).build());
}
/**
* 设置授权信息
*/
private static OAuth securitySchema(SwaggerProperties swaggerProperties) {
ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
swaggerProperties.getAuthorization().getAuthorizationScopeList()
.forEach(authorizationScope -> authorizationScopeList.add(
new AuthorizationScope(authorizationScope.getScope(), authorizationScope.getDescription())));
ArrayList<GrantType> grantTypes = new ArrayList<>();
swaggerProperties.getAuthorization().getTokenUrlList()
.forEach(tokenUrl -> grantTypes.add(new ResourceOwnerPasswordCredentialsGrant(tokenUrl)));
return new OAuth(swaggerProperties.getAuthorization().getName(), authorizationScopeList, grantTypes);
}
/**
* API 页面上半部分展示信息
*/
private static ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
return new ApiInfoBuilder().title(swaggerProperties.getTitle()).description(swaggerProperties.getDescription())
.license(swaggerProperties.getLicense()).licenseUrl(swaggerProperties.getLicenseUrl())
.termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
.contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(),
swaggerProperties.getContact().getEmail()))
.version(swaggerProperties.getVersion()).build();
}
}
针对上面配置。斗胆来代码给解析下。
@ConditionalOnProperty控制配置文件是否可以加载。这里就是根据yml文件的swagger.enable的值来判断。matchIfMissing = true默认没有这个值也加载。
@ConditionalOnMissingClass(“org.springframework.cloud.gateway.config.GatewayAutoConfiguration”)表示当前类路径下没有这个类就创建。也就是网关服务不需要配置这个类
- 接着在common-swagger服务中配置GatewaySwaggerAutoConfiguration
/**
* @Description: 网关swagger 配置类,仅在webflux 环境生效哦
* 当Spring为web服务时,才使注解的类生效;通常是配置类;
* 设置配置类生效环境
* @author: heihei
* @date: 2021年11月08日 11:53
*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class GatewaySwaggerAutoConfiguration {
@Bean
@Primary
public SwaggerProvider swaggerProvider(SwaggerProperties swaggerProperties, GatewayProperties gatewayProperties) {
return new SwaggerProvider(swaggerProperties, gatewayProperties);
}
@Bean
public SwaggerResourceHandler swaggerResourceHandler(SwaggerProvider swaggerProvider) {
return new SwaggerResourceHandler(swaggerProvider);
}
@Bean
public WebFluxSwaggerConfiguration fluxSwaggerConfiguration() {
return new WebFluxSwaggerConfiguration();
}
@Bean
@ConditionalOnProperty(value = "swagger.basic.enabled", havingValue = "true")
public SwaggerBasicGatewayFilter swaggerBasicGatewayFilter(SwaggerProperties swaggerProperties) {
return new SwaggerBasicGatewayFilter(swaggerProperties);
}
@Bean
public SwaggerSecurityHandler swaggerSecurityHandler(
ObjectProvider<SecurityConfiguration> securityConfigurationObjectProvider) {
SecurityConfiguration securityConfiguration = securityConfigurationObjectProvider
.getIfAvailable(() -> SecurityConfigurationBuilder.builder().build());
return new SwaggerSecurityHandler(securityConfiguration);
}
@Bean
public SwaggerUiHandler swaggerUiHandler(ObjectProvider<UiConfiguration> uiConfigurationObjectProvider) {
UiConfiguration uiConfiguration = uiConfigurationObjectProvider
.getIfAvailable(() -> UiConfigurationBuilder.builder().build());
return new SwaggerUiHandler(uiConfiguration);
}
@Bean
public RouterFunction<ServerResponse> swaggerRouterFunction(SwaggerProperties swaggerProperties,
SwaggerUiHandler swaggerUiHandler, SwaggerSecurityHandler swaggerSecurityHandler,
SwaggerResourceHandler swaggerResourceHandler) {
// 开启swagger 匹配路由
if (swaggerProperties.getEnabled()) {
return RouterFunctions
.route(RequestPredicates.GET("/swagger-resources").and(RequestPredicates.accept(MediaType.ALL)),
swaggerResourceHandler)
.andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui")
.and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler)
.andRoute(RequestPredicates.GET("/swagger-resources/configuration/security")
.and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler);
} else {
// 关闭时,返回404
return RouterFunctions.route(
RequestPredicates.GET("/swagger-ui/**").and(RequestPredicates.accept(MediaType.ALL)),
serverRequest -> ServerResponse.notFound().build());
}
}
}
Q1- @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
A1- 网关swagger 配置类,仅在webflux 环境生效。在mvc不需要生效。也就是当我们在upms服务引用的时候。配置文件不加载。
Q2- SwaggerProvider是啥?
A2- 首先看看SwaggerProvider的配置如下,自定义SwaggerResourcesProvider实现SwaggerResourcesProvider接口的get方法,方法可返回多个SwaggerResource,每个SwaggerResource对应每个微服务,我们可以过滤掉网关自身的
package com.hey.girl.common.swagger.support;
/**
* @Description: 添加文档来源
* @author: heihei
* @date: 2021年11月08日 14:07
*/
@Primary
@RequiredArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
private static final String API_URI = "/v2/api-docs";
private final SwaggerProperties swaggerProperties;
private final GatewayProperties gatewayProperties;
@Lazy
@Autowired
private RouteLocator routeLocator;
/**
* 重写get方法 返回多个SwaggerResource
* 自定义SwaggerResourcesProvider实现SwaggerResourcesProvider接口的get方法,
* 方法可返回多个SwaggerResource,每个SwaggerResource对应每个微服务,我们可以过滤掉网关自身的,代码如下
*/
@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()))
.filter(predicateDefinition -> !swaggerProperties.getIgnoreProviders()
.contains(routeDefinition.getId()))
.forEach(predicateDefinition -> resources
.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs()
.get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", API_URI)))));
return resources;
}
private static SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("3.0");
return swaggerResource;
}
}
Q3- SwaggerResourceHandler是啥?
A3- 首先配置SwaggerResourceHandler,就是资源处理器。执行我们自己写的SwaggerResourcesProvider的get方法。
@Slf4j
@RequiredArgsConstructor
public class SwaggerResourceHandler implements HandlerFunction<ServerResponse> {
private final SwaggerResourcesProvider swaggerResources;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(swaggerResources.get()));
}
}
Q4- 这个上面写的就是资源路径配置。(上面已经配置了,所以不贴代码)
Q5- 接着看看这段代码SwaggerBasicGatewayFilter
SwaggerBasicGatewayFilter:
@Slf4j
@RequiredArgsConstructor
public class SwaggerBasicGatewayFilter implements GlobalFilter {
private static final String API_URI = "/v2/api-docs";
private static final String BASIC_PREFIX = "Basic ";
private final SwaggerProperties swaggerProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (!request.getURI().getPath().contains(API_URI)) {
return chain.filter(exchange);
}
if (hasAuth(exchange)) {
return chain.filter(exchange);
}
else {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm=\"girl\"");
return response.setComplete();
}
}
/**
* 简单的basic认证
* @param exchange 上下文
* @return 是否有权限
*/
private boolean hasAuth(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
log.info("Basic认证信息为:{}", auth);
if (!StringUtils.hasText(auth) || !auth.startsWith(BASIC_PREFIX)) {
return Boolean.FALSE;
}
String username = swaggerProperties.getBasic().getUsername();
String password = swaggerProperties.getBasic().getPassword();
String encodeToString = Base64Utils
.encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
return auth.equals(BASIC_PREFIX + encodeToString);
}
}
A5- swagger.basic.enabled值为true,才加载。basic 过滤器
Q6- SwaggerSecurityHandler
A6-SwaggerSecurityHandler配置
@Slf4j
@RequiredArgsConstructor
public class SwaggerSecurityHandler implements HandlerFunction<ServerResponse> {
private final SecurityConfiguration securityConfiguration;
/**
* Handle the given request.
*
* @param request the request to handler
* @return the response
*/
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(Optional.ofNullable(securityConfiguration)
.orElse(SecurityConfigurationBuilder.builder().build())));
}
}
Q7- SwaggerUiHandler
A7- SwaggerUiHandler
@Slf4j
@RequiredArgsConstructor
public class SwaggerUiHandler implements HandlerFunction<ServerResponse> {
private final UiConfiguration uiConfiguration;
/**
* Handle the given request.
* @param request the request to handler
* @return the response
*/
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(BodyInserters
.fromValue(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build())));
}
}
- 差不多配置文件就这些。配置完以后。只需要使用的服务中加上注解
类似这样
然后再配置yml文件中加下配置:
访问:
http://localhost:9999/swagger-ui/#/
可以右上角的服务是都可以选的。已经把服务整合进来了。当我们需要认证的时候。
至此基本配置完成。
再说说注解,相信之前的注解@Api开头的就很常见。但是我发现有新的注解@Tag.但是我使用了,没有效果。这个暂时不知道原因。但是用之前swagger2的注解。还是挺好用的
完结,撒花!后面有啥再补充