- 921. Minimum Add to Make Parentheses Valid 使括号有效的最少添加
- 915. Partition Array into Disjoint Intervals 分割数组
- 932. Beautiful Array 漂亮数组
- 940. Distinct Subsequences II 不同的子序列 II
在微服务大行其道到今天,服务到碎片化也带来了管理和监控的困难(统一集成网关系统在前面的sia-gateway的文章中有做分享,感兴趣的可以前往阅读)。 swagger为我们开发带来了极大到便利,但是在庞杂的系统中,即便对于开发的接入和调试,对各个分散对在线文档,也显得有些杂乱无章。即便是有如同yapi等在线文档系统,也同样需要精力去维护,而且对于文档与代码分离,同样会带来版本不一致和调试困难等问题。 在网关层集成swagger显得非常易用和优雅。但是面对各个公司不同网关配置方案,给swagger的集成带来和诸多不便和技术难题。下面我分享我在实现spring cloud gateway 集成swagger的实现方案。# 系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
在pom文件中添加如下依赖,推荐使用2.9.2版本
<!-- swagger 包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
@Configuration
@EnableSwagger2
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public class SwaggerConfig {
@Bean
public Docket openApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("open")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.api.controller"))
.paths(PathSelectors.any())
.build();
}
@Bean
public Docket adminApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("admin")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.api.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("微服务swagger")
.contact(new Contact("LiuChao", "", "liuchao332@163.com"))
.version("1.0")
.description("API 描述")
.build();
}
}
说明:1、此配置类为基本的swagger集成的基本配置,有些区别的是,此处配置类2个Docket元素,分别设置了不同的groupName。此操作是为了满足,将业务端和管理端分成不同的swagger页面进行展示。这个地方会为网关集成带来一些改变。不是必须要这样设置,不想将接口分开,放到同一个页面也没什么不可。 2、在配置项中需要新增swagger.enable配置,来控制是否启用swagger。
spring cloud gateway 的集成非常简单,基本上实现来了开箱即用,进行简单的配置,集成eureka,就能满足我们基本的业务场景,这里就不在赘述了。
和微服务所应用的包相同
网关的swagger集成方案与服务单元有较大不同,也更为复杂。
@Configuration
public class SwaggerConfig {
@Bean
public SecurityConfiguration securityConfiguration() {
return SecurityConfigurationBuilder.builder().build();
}
@Bean
public UiConfiguration uiConfiguration() {
return UiConfigurationBuilder.builder().showExtensions(true).build();
}
}
此配置类中定义SecurityConfiguration,UiConfiguration 将在下面的配置中用到这2个bean
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
private final SecurityConfiguration securityConfiguration;
private final UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources, SecurityConfiguration securityConfiguration, UiConfiguration uiConfiguration) {
this.swaggerResources = swaggerResources;
this.securityConfiguration = securityConfiguration;
this.uiConfiguration = uiConfiguration;
}
@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<List<SwaggerResource>>> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
此类定义了swagger网关层的开放接口,在访问swagger-ui中会拉去此接口的数据。
```java
@Primary
@Component
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String SOURCE_URI = "http://%s/swagger-resources";
private final RouteLocator routeLocator;
private final RouteDefinitionLocator routeDefinitionLocator;
private final RestTemplate restTemplate;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
routeDefinitionLocator.getRouteDefinitions()
.filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.subscribe(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources
.addAll(swaggerResource(routeDefinition))));
return resources;
}
private List<SwaggerResource> swaggerResource(RouteDefinition route) {
try {
String sourceUrl = String.format(SOURCE_URI, route.getUri().getHost());
ResponseEntity<String> content = restTemplate.getForEntity(sourceUrl, String.class);
List<SwaggerResource> swaggerResources = JsonUtil.toList(content.getBody(), SwaggerResource.class);
swaggerResources.stream().forEach(swaggerResource -> {
swaggerResource.setName(route.getUri().getHost().toLowerCase() + "-" + swaggerResource.getName());
swaggerResource.setUrl("/" + route.getUri().getHost().toLowerCase() + swaggerResource.getUrl());
});
return swaggerResources;
} catch (Exception e) {
return new ArrayList<>();
}
}
}
这个类是核心,这个类封装的是SwaggerResource,即在swagger-ui.html页面中顶部的选择框,选择服务的swagger页面内容。 RouteLocator:获取spring cloud gateway中注册的路由 RouteDefinitionLocator:获取spring cloud gateway路由的详细信息 RestTemplate:获取各个配置有swagger的服务的swagger-resources
在一般的情况下,网关一般使用一个服务,一个路由的配置方式,我也建议大家这么使用,这样管理控制都非常灵活。但在项目中,也遇到过一种特别的配置方式。如下:
routes:
- id: remove_api_prefix
uri: http://127.0.0.1:80
order: 1
predicates:
- Path=/api/**
filters:
- StripPrefix=1
按照这种配置方式,在请求url前加上/api前缀,网关在收到此类请求后,将api截取掉,在转发给自己;网关再次收到没有api的请求后,对请求第二级目录按服务名进行路由。 这种方式可以将在注册中心注册对服务都暴露到网关到路由中,就不需要对每个服务配置路由规则。 对于新加的服务,也不行要配置新的路由。
但是这样也引起了新的问题,在swagger的baseUrl,出现了,符号
www.example-gateway.com/api,/example-service/
且通过swagger发送的请求,在url上都出现了,。导致不能够请求到正确到地址上,然后就开始分析这个,的来源。
curl -X POST “http://www.example-gateway.com/api,/example-service/user/info” -H “accept: /”
初步可以判断,这个符号肯定和这种特殊的路由方式有关系,因为在使用一服务一路由的配置方式的时候,并没有这个问题。从哪里入手呢,还是从服务的swagger页面入手,因为这个符号就来自这里。 如上截图,在api-docs接口中所返回的数据里,basePath就已经带上里这个符号。有了一个初步的定位思路,从swagger的api-docs接口入手。
在找到swagger接口源头,往下debug,终于在XForwardPrefixPathAdjuster类中发现类这个符号出现的地方。源码如下图,
public class XForwardPrefixPathAdjuster implements PathAdjuster {
static final String X_FORWARDED_PREFIX = "X-Forwarded-Prefix";
private final HttpServletRequest request;
public XForwardPrefixPathAdjuster(HttpServletRequest request) {
this.request = request;
}
public String adjustedPath(String path) {
String prefix = this.request.getHeader("X-Forwarded-Prefix");
if (prefix != null) {
return !SpringVersionCapability.supportsXForwardPrefixHeader(SpringVersion.getVersion()) ? prefix + path : prefix;
} else {
return path;
}
}
}
本以为发现源头,但是在debug的过程中,到这里接收到http头的X-Forwarded-Prefix参数,已经带有这个符号。 由此可以判断,在swagger接收到到http请求就已经带有这个符号。所以只能往上找原因。那就只有一个地方了,问题出在网关。
然后我就开始spring-cloud-gateway-core-2.2.2.RELEASE.jar包里,找什么地方在封装header的X-Forwarded-Prefix参数。有赖于spring合理的代码和包结构,没用多少时间就找到XForwardedHeadersFilter类,如下所示:
private void updateRequest(HttpHeaders updated, URI originalUri, String originalUriPath, String requestUriPath) {
if (requestUriPath != null && originalUriPath.endsWith(requestUriPath)) {
String prefix = substringBeforeLast(originalUriPath, requestUriPath);
if (prefix != null && prefix.length() > 0 && prefix.length() <= originalUri.getPath().length()) {
this.write(updated, "X-Forwarded-Prefix", prefix, this.isPrefixAppend());
}
}
}
在此方法,完成的header头的X-Forwarded-Prefix的封装。再往下看write方法
private void write(HttpHeaders headers, String name, String value, boolean append) {
if (append) {
headers.add(name, value);
List<String> values = headers.get(name);
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
headers.set(name, delimitedValue);
} else {
headers.set(name, value);
}
}
在这里,当网关在截取掉api,再发送给自己,这个地方就会在此请求中,List会将这个请求头中封装两次的X-Forwarded-Prefix,/api和/example-service。
StringUtils.collectionToCommaDelimitedString(values)
方法将它们拼接到一起,就产生了,符号。如今要想办法消除掉这个符号。灵机一动,在写个filer,将,干掉就行了。
添加一个global filter,删除X-Forwarded-Prefix参数到,符号。完美解决。
@Component
public class XForwardedPrefixFilter implements HttpHeadersFilter, Ordered {
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
List<String> xForwareds = input.get("X-Forwarded-Prefix");
String forward = xForwareds.get(0);
if (forward.contains(",")) {
forward = forward.replaceAll(",", "/");
}
input.set("X-Forwarded-Prefix", forward);
return input;
}
@Override
public int getOrder() {
return 1;
}
}
我尝试阅读有关 Spring BOM、Spring Boot 和 Spring IO 的文档。 但是没有说明,我们应该如何一起使用它们? 在我的项目中,我们已经有了自己的 Parent POM ,所以
我正在开发的很酷的企业应用程序正在转向 Spring。这对所有团队来说都是非常酷和令人兴奋的练习,但也是一个巨大的压力源。我们所做的是逐渐将遗留组件移至 Spring 上下文。现在我们有一个 huuu
我正在尝试使用 @Scheduled 运行 Spring 批处理作业注释如下: @Scheduled(cron = "* * * * * ?") public void launchMessageDi
我对这两个概念有点困惑。阅读 Spring 文档,我发现,例如。 bean 工厂是 Spring 容器。我还读到“ApplicationContext 是 BeanFactory 的完整超集”。但两者
我们有一个使用 Spring BlazeDS 集成的应用程序。到目前为止,我们一直在使用 Spring 和 Flex,它运行良好。我们现在还需要添加一些 Spring MVC Controller 。
假设我有一个类(class) Person带属性name和 age ,它可以像这样用 Spring 配置: 我想要一个自定义的 Spring 模式元素,这很容易做到,允许我在我的 Sp
如何在 Java 中以编程方式使用 Spring Data 创建 MongoDB 复合索引? 使用 MongoTemplate 我可以创建一个这样的索引:mongoTemplate.indexOps(
我想使用 spring-complex-task 执行我的应用程序,并且我已经构建了复杂的 spring-batch Flow Jobs,它执行得非常好。 你能解释一下spring批处理流作业与spr
我实现了 spring-boot 应用程序,现在我想将它用作非 spring 应用程序的库。 如何初始化 lib 类,以便 Autowiring 的依赖项按预期工作?显然,如果我使用“new”创建类实
我刚开始学习 spring cloud security,我有一个基本问题。它与 Spring Security 有何不同?我们是否需要在 spring boot 上构建我们的应用程序才能使用 spr
有很多人建议我使用 Spring Boot 而不是 Spring 来开发 REST Web 服务。我想知道这两者到底有什么区别? 最佳答案 总之 Spring Boot 减少了编写大量配置和样板代码的
您能向我解释一下如何使用 Spring 正确构建 Web 应用程序吗?我知道 Spring 框架的最新版本是 4.0.0.RELEASE,但是 Spring Security 的最新版本是 3.2.0
我如何才能知道作为 Spring Boot 应用程序的一部分加载的所有 bean 的名称?我想在 main 方法中有一些代码来打印服务器启动后加载的 bean 的详细信息。 最佳答案 如spring-
我有一个使用 Spring 3.1 构建的 RESTful API,也使用 Spring Security。我有一个 Web 应用程序,也是一个 Spring 3.1 MVC 应用程序。我计划让移动客
升级到 Spring 5 后,我在 Spring Rabbit 和 Spring AMQP 中遇到错误。 两者现在都设置为 1.5.6.RELEASE 有谁知道哪些版本应该与 Spring 5 兼容?
我现在已经使用 Spring Framework 3.0.5 和 Spring Security 3.0.5 多次了。我知道Spring框架使用DI和AOP。我还知道 Spring Security
我收到错误 Unable to Location NamespaceHandler when using context:annotation-config running (java -jar) 由
在 Spring 应用程序中嵌入唯一版本号的策略是什么? 我有一个使用 Spring Boot 和 Spring Web 的应用程序。 它已经足够成熟,我想对其进行版本控制并在运行时看到它显示在屏幕上
我正在使用 spring data jpa 进行持久化。如果存在多个具有相同名称的实体,是否有一种方法可以将一个实体标记为默认值。类似@Primary注解的东西用来解决多个bean的依赖问题 @Ent
我阅读了 Spring 框架的 DAOSupport 类。但是我无法理解这些 DAOSuport 类的优点。在 DAOSupport 类中,我们调用 getXXXTemplate() 方法来获取特定的
我是一名优秀的程序员,十分优秀!