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) {
            }
        }
    }
}

到此完成

在這裏插入圖片描述

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