前言
在看這篇文章之前,建議先看一下這篇文章<<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) {
}
}
}
}