问题起因:使用Zuul网关服务,需要整合下游系统的swagger,但是下游服务存在context-path配置,无法正确跳转,最后使用ZuulFilter解决。
首先介绍一下Zuul如何整合下游服务swagger,很好理解,就是通过Zuul的swagger地址,实现将下游服务的swagger都放入同一个页面内,流转图如下:
这里进行简单介绍服务整合swagger的步骤其实就是分为两步:
这里不做代码介绍,具体可以查看我的另一篇文章: www.dalaoyang.cn/article/21 ,或者查看本文源码。
这里只新建了一个服务,服务名为test-service。
Zuul相关配置这里不做介绍,首先配置下游服务路由,即访问test-service/**转发到test-service服务,配置如下:
zuul.routes.test-service.path=/test-service/** zuul.routes.test-service.service-id=test-service 复制代码
配置swagger配置文件,如下:
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("使用Swagger2构建RESTful APIs") .description("关注博主博客:https://www.dalaoyang.cn/") .termsOfServiceUrl("https://www.dalaoyang.cn/") .contact("dalaoyang") .version("1.0") .build(); } } 复制代码
新建文档配置,这里主要目的是为了聚合下游服务的swagger,内容很好理解,就是讲SwaggerResource赋值,其中name为swagger的api文档名,location为对应api-docs地址,version为版本,这里利用ZuulProperties来生成对应文档,避免写死代码,完整内容如下:
@Primary @Component public class DocumentConfig implements SwaggerResourcesProvider { @Autowired private ZuulProperties zuulProperties; @Override public List<SwaggerResource> get() { List<SwaggerResource> swaggerResources = new ArrayList<>(); Map<String, ZuulProperties.ZuulRoute> routes = zuulProperties.getRoutes(); for (String serviceName : routes.keySet()) { SwaggerResource swaggerResource = initSwaggerResource(serviceName, "/" + serviceName + "/v2/api-docs", "1.0.0"); swaggerResources.add(swaggerResource); } return swaggerResources; } private SwaggerResource initSwaggerResource(String name, String location, String version) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion(version); return swaggerResource; } } 复制代码
配置到这里,其实已经完成了,访问网关swagger如图所示:
从上面其实可以了解到,聚合文档的操作,其实就是将下游服务的/v2/api-docs整合进来,当然,可以在本文DocumentConfig中将下游服务context-path加入其中,注意注释阶段,完整代码如下:
@Primary @Component public class DocumentConfig implements SwaggerResourcesProvider { @Autowired private ZuulProperties zuulProperties; @Override public List<SwaggerResource> get() { List<SwaggerResource> swaggerResources = new ArrayList<>(); Map<String, ZuulProperties.ZuulRoute> routes = zuulProperties.getRoutes(); for (String serviceName : routes.keySet()) { //假设下游服务的context-path为服务名 SwaggerResource swaggerResource = initSwaggerResource(serviceName, "/" + serviceName +"/" + serviceName + "/v2/api-docs", "1.0.0"); swaggerResources.add(swaggerResource); } return swaggerResources; } private SwaggerResource initSwaggerResource(String name, String location, String version) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion(version); return swaggerResource; } } 复制代码
下游服务加入context-path配置,如下:
server.servlet.context-path=/test-service 复制代码
启动服务,访问Zuul的swagger文档,还是可以同样的访问,但是测试一下在swagger请求一下下游服务api,如下
很明显,404的原因就是因为转发下游服务的时候,没有加上context-path,在本文DocumentConfig配置的方式肯定不是正确的方式,那么如何解决呢?
可以加入一个ZuulFilter来进行统一添加下游服务context-path,首先还原上面修改的DocumentConfig,接下来新建一个Filter继承ZuulFilter,创建一个转发前的拦截器,将转发地址进行修改,也就是我们需要的加入context-pa路径,由于本文下游context-path路径为服务名,所以案例比较简单,内容如下:
@Component public class UrlPathFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 6; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); Object requestURI = requestContext.get(FilterConstants.REQUEST_URI_KEY); Object server = requestContext.get(FilterConstants.PROXY_KEY); String finalURI = "/" + server + requestURI; requestContext.put(FilterConstants.REQUEST_URI_KEY, finalURI); return null; } } 复制代码
需要注意一点,这个拦截器需要在默认ZuulFilter后执行,才能获取requestURI和server。
再次启动项目,就可以正常使用和访问swagger了。