摘要:
很久没有分享自己工作上的心得了,而这篇文章也是来源于工作中,目的是升级微服务版本,由于eureka或许“感冒了”,打算用consul,虽然该文章没有用到,然后zuul打算改为gateway。由于为人比较懒,所以不想单独去写开发文档,所以webflux与swagger2.x就成了我该篇主题!
注意事项:
如果是一个负责人的分享者,他的代码一定会尽可能的通熟易懂,如果你跑不起来项目,首先是自己环境搭建好了么?然后在怀疑作者的上传是不是又点儿问题?所以希望大家一定要有自己探索知识的欲望才可以,经过自己的改造是肯定可以ok的,一个完全有问题的代码,我相信没有一个作者敢分享出来吧!为为什么谈这点问题呢?因为接下来的环境和项目依赖十分头疼,一句话,依赖和版本一定别搞错了,我很负责的告诉大家,这周花了2天时间,期间研究到晚上4点,然后8点继续起来干,有朋友很想我立即分享给大家,所以今晚将以前的finchley改造了一下,不过本着浅显易懂的原则,相信大家很容易掌握的!
eureka:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.dqqzj</groupId>
<artifactId>registry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>registry</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<docker.image.prefix>qzj</docker.image.prefix>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意点: 一定要添加web依赖哦!后面的cloud版本就不会贴出来了,代码会上传github供大家参考的!
spring gateway环境搭建:
核心依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml 配置文件如下:
server:
port: 8764
spring:
application:
name: gateway
# zipkin:
# base-url: http://localhost:9411
# sleuth:
# sampler:
# probability: 1.0
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: account
uri: lb://account
predicates:
- Path=/account/**
filters:
- SwaggerHeaderFilter
- StripPrefix=1
- id: common
uri: lb://common
predicates:
- Path=/common/**
filters:
- SwaggerHeaderFilter
- StripPrefix=1
eureka:
client:
service-url:
defaultZone: http://localhost:8763/eureka/
如果大家看了前一个项目版本就可以运行zipkin,这里直接忽略掉即可!
因为Gateway里没有配置SwaggerConfig,而运行Swagger-ui又需要依赖一些接口,所以我的想法是自己建立相应的swagger-resource端点。
package com.github.dqqzj.gateway.swagger.config;
import com.github.dqqzj.gateway.swagger.handler.SwaggerResourceHandler;
import com.github.dqqzj.gateway.swagger.handler.SwaggerSecurityHandler;
import com.github.dqqzj.gateway.swagger.handler.SwaggerUiHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
/**
* @author qinzhongjian
* @date created in 2018/7/21 15:20
* @since 1.0.0
*/
@Slf4j
@Configuration
@AllArgsConstructor
public class SwaggerRouterFunction {
private final SwaggerResourceHandler swaggerResourceHandler;
private final SwaggerSecurityHandler swaggerSecurityHandler;
private final SwaggerUiHandler swaggerUiHandler;
@Bean
public RouterFunction<?> routerFunction() {
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);
}
}
然后便是SwaggerResourceHandler,SwaggerSecurityHandler,SwaggerUiHandler处理器配置如下:
package com.github.dqqzj.gateway.swagger.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
/**
* @author qinzhongjian
* @date created in 2018/7/21 15:44
* @since 1.0.0
*/
@Component
public class SwaggerResourceHandler implements HandlerFunction {
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerResourceHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(swaggerResources.get()));
}
}
package com.github.dqqzj.gateway.swagger.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import java.util.Optional;
/**
* @author qinzhongjian
* @date created in 2018/7/21 15:47
* @since 1.0.0
*/
@Component
public class SwaggerSecurityHandler implements HandlerFunction {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(
Optional.ofNullable(securityConfiguration)
.orElse(new SecurityConfiguration(null,null,null,null,null,ApiKeyVehicle.HEADER,"api_key",","))));
}
}
package com.github.dqqzj.gateway.swagger.handler;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.UiConfiguration;
/**
* @author qinzhongjian
* @date created in 2018/7/21 15:51
* @since 1.0.0
*/
@Component
public class SwaggerUiHandler implements HandlerFunction {
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(
new UiConfiguration(null)));//这个地方如果不是null后果是什么?请大家看看swager-ui-html的右下角就知道了!在springmvc模式的"/"中很好的解释了这一点
}
}
重点:
其实这3个处理器可以用另外一种方式实现,即传统的springmvc处理器模式,代码如下:
package com.github.dqqzj.gateway.swagger.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import java.util.Optional;
/**
* @author qinzhongjian
* @date created in 2018/7/22 09:22
* @since 1.0.0
*/
//@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(null), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(new UiConfiguration("/")), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
由于我用的第一种webflux的路由函数模式,所以这个方式被我注释了,如果你想尝试另一个方式直接将webflux路由函数的3个处理器注释掉即可!废话不多说,继续贴代码如下!
配置SwaggerProvider,获取Api-doc,即SwaggerResources。
package com.github.dqqzj.gateway.swagger.config;
import lombok.AllArgsConstructor;
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;
/**
* @author qinzhongjian
* @date created in 2018/7/21 15:09
* @since 1.0.0
*/
@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<>();
//取出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(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", 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;
}
}
如果你在try it out!失败,那就是这个问题了:断点源码时发现在Swagger中会根据X-Forwarded-Prefix这个Header来获取BasePath,将它添加至接口路径与host中间,这样才能正常做接口测试,而Gateway在做转发的时候并没有这个Header添加进Request,所以发生接口调试的404错误。解决思路是在Gateway里加一个过滤器来添加这个header。
package com.github.dqqzj.gateway.swagger.filter;
import com.github.dqqzj.gateway.swagger.config.SwaggerProvider;
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.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* @author qinzhongjian
* @date created in 2018/7/21 19:43
* @since 1.0.0
*/
@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);
};
}
}
网关的集成到此结束了,然后看看微服务的集成吧!我改造了2哥微服务,一个是common,一个是account.
举例说名其中一个即可。
common微服务集成
核心依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
application.yml配置文件如下:
server:
port: 8765
spring:
application:
name: common
eureka:
client:
service-url:
defaultZone: http://localhost:8763/eureka/
swagger2.x集成配置SwaggerConfiguration如下:
package com.github.dqqzj.common.swagger;
import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.AlternateTypeRules;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* @author qinzhongjian
* @date created in 2018/6/26 12:52
* @since 1.0.0
*/
@EnableSwagger2
@Configuration
public class SwaggerConfiguration {
@Bean
public Docket configure(TypeResolver typeResolver) {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
.build()
.pathMapping("/")
.useDefaultResponseMessages(false)
.apiInfo(apiInfo())
.alternateTypeRules(
AlternateTypeRules.newRule(
typeResolver.resolve(Collection.class, WildcardType.class),
typeResolver.resolve(List.class, WildcardType.class))
)
.enableUrlTemplating(true)
.forCodeGeneration(false);
}
@Bean
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Common MicroService") //标题
.description("Springfox petstore API") //描述
.termsOfServiceUrl("https://github.com/dqqzj/finchley") //超链接
.contact(new Contact("qinzhongjian","https://github.com/dqqzj","798078824@qq.com")) // 联系方式
.version("2.0")
.license("Apache License Version 2.0")
.licenseUrl("https://github.com/springfox/springfox/blob/master/LICENSE")
.build();
}
}
整体效果图如下:
总结:
在社区中我发现有人总结了该篇文章内容,但是不想分享出来,貌似要加群付费199???,本着分享开源技术的初心,废寝忘食研究技术,希望能够与大家一起分享java的快乐!
源码链接: