前言
在看这篇文章之前,建议先看一下这篇文章<<spring cloud zuul 动态路由>>,这篇文章的整合,是基于<<spring cloud zuul 动态路由>>
相关依赖 [只列出必要的,其他的自行处理]
spring boot 版本号 2.2.2.RELEASE
spring-cloud-dependencies 版本 Hoxton.SR1
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.53</version>
</dependency>
<!--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>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
zuul 整合swagger
package cn.zhangfusheng.zuul.server.config;
import com.netflix.appinfo.InstanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName: SwaggerConfig
* @Author: ZFS
* @Date: 2019/4/30 18:22
*/
@Slf4j
@EnableSwagger2
@Configuration
public class SwaggerConfig {
@Autowired
private DiscoveryClient discoveryClient;
@Bean
public Docket createRestApi() {
log.debug("初始化zuul-swagger-api文档----start");
ArrayList<SecurityScheme> securitySchemes = new ArrayList<>();
securitySchemes.add(new ApiKey("Set-Cookie", "Cookie", "header"));
securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
ApiInfo apiInfo = new ApiInfoBuilder().title("spring cloud project zull swagger api").description("spring cloud project zull swagger api").termsOfServiceUrl("-------").version("1.0").build();
Docket docket = new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("")).paths(PathSelectors.any()).build().securitySchemes(securitySchemes);
log.debug("初始化zuul-swagger-api文档----end");
return docket;
}
@Bean
@Primary
public SwaggerResourcesProvider swaggerResourcesProvider() {
return new ProjectSwaggerResourcesProvider(discoveryClient, Arrays.asList("eureka-server", "config-server", "zuul-server"));
}
static class ProjectSwaggerResourcesProvider implements SwaggerResourcesProvider {
private final DiscoveryClient discoveryClient;
/**
* 需要忽略的服务的 serviceId
*/
private final List<String> ignoreServerNames;
public ProjectSwaggerResourcesProvider(DiscoveryClient discoveryClient, List<String> ignoreServerNames) {
this.discoveryClient = discoveryClient;
this.ignoreServerNames = ignoreServerNames;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> swaggerResources = new ArrayList<>();
// 今日时间 用于网关的动态路由一天一变化
String localDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// 服务提供者的serviceId
discoveryClient.getServices().forEach(serviceId -> {
// serviceId 对应的实例
discoveryClient.getInstances(serviceId).forEach(instance -> {
// 实例是否在忽略配置中
if (!ignoreServerNames.contains(serviceId)) {
InstanceInfo instanceInfo = ((EurekaServiceInstance) instance).getInstanceInfo();
// 从 eureka.instance.metadata-map中获取context-path,既服务提供者的server.servlet.context-path
// 如果获取不到,则采用服务名称'-'切割的方式作为路由前缀
String contexPath = instanceInfo.getMetadata().getOrDefault(
"context-path", String.format("/%s/", instanceInfo.getSecureVipAddress().split("-")[0]))
.replaceAll("/", "");
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(serviceId);
swaggerResource.setSwaggerVersion("1.0");
// 指定 v2/api-docs 的访问路径
swaggerResource.setLocation(String.format("/%s/%s/%s/v2/api-docs?group=%s", contexPath, localDate, contexPath, serviceId));
swaggerResources.add(swaggerResource);
}
});
});
return swaggerResources;
}
}
}
spring mvc 配置
package cn.zhangfusheng.zuul.server.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author fusheng.zhang
* @Description
* @create 2020-04-20 11:07:00
*/
@Slf4j
@Configuration
public class ZuulWebMvcConfigurer implements WebMvcConfigurer {
/**
* 如果你在配置文件中配置了页面访问的相关信息,就需要有该方法,否则无法访问swagger-ui.html
* @param registry --
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.debug("初始化swagger页面的位置---start");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
log.debug("初始化swagger页面的位置---end");
}
/**
* 如果url中包含".",则"."后面的内容会被忽略,重写改方法可以禁止忽略"."后的内容
* @param configurer PathMatchConfigurer
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
做到这里,就可以成功访问swagger-ui.html
注意: 到这里也仅仅是能访问一下网关的swagger-UI.html 页面,展示一下swagger的文档,
但是swagger文档中的接口访问是404的,
如果你观察仔细的话,你可能会发现访问的路径未携带zuul网关的路由配置,如下图
解决访问接口404
这里我是采用spring的filter对 v2/api-docs 接口的返回值进行了处理,修改一下返回值中的basePath
package cn.zhangfusheng.zuul.server.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* @author fusheng.zhang
* @Description 对 v2/api-docs 的返回值进行处理
* @create 2020-04-20 11:24:00
*/
@Slf4j
@Component
public class ResponseFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String uri = ((RequestFacade) servletRequest).getRequestURI();
if (uri.contains("api-docs")) {
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) servletResponse);
filterChain.doFilter(servletRequest, responseWrapper);
byte[] content = responseWrapper.getContent();
String result = new String(content, StandardCharsets.UTF_8);
ServletOutputStream out = servletResponse.getOutputStream();
JSONObject jsonObject = JSON.parseObject(result, Feature.DisableCircularReferenceDetect);
String localDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String basePath = String.format("%s/%s%s", jsonObject.getString("basePath"), localDate, jsonObject.getString("basePath"));
jsonObject.put("basePath", basePath);
byte[] re = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
servletResponse.setContentLength(re.length);
out.write(re);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
private static class ResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream byteArrayOutputStream;
private final ServletOutputStream outputStream;
public ResponseWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
byteArrayOutputStream = new ByteArrayOutputStream();
outputStream = new WrapperOutputStream(byteArrayOutputStream);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return outputStream;
}
@Override
public void flushBuffer() throws IOException {
if (outputStream != null) {
outputStream.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return byteArrayOutputStream.toByteArray();
}
static class WrapperOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
}
}
}
}