spring cloud zuul整合swagger

前言

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

到此完成

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章