js css html

gateway整合swagger3.0+knife4j-spri

2022-07-26  本文已影响0人  无我_无他_有你

1.子服务整合swagger

引入依赖

<swagger-version>3.0.0</swagger-version>
<knife4j-spring-ui-version>3.0.3</knife4j-spring-ui-version>
<dependencies>
         <!-- 整合swagger start -->
         <!-- 3.0版本只需要引入一个,不再像2.x版本需要引入两个包 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>${swagger-version}</version>
        </dependency>
        <!-- ui美化依赖 -->
            <!--swagger UI-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-ui</artifactId>
            <version>${knife4j-spring-ui-version}</version>
        </dependency>
        <!-- 整合swagger end -->
</dependencies>

swagger配置文件

package com.ly.tulip.commons.swagger.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.List;

/**
 * 类说明: swagger 配置类
 *
 * @author wqf
 * @date 2022/7/26 10:23
 */
@Component
@EnableOpenApi
public class SwaggerConfig {

    /**
     * 是否开启swagger,生产环境一般关闭,所以这里定义一个变量
     */
    @Value("${swagger.enable:false}")
    private Boolean enable;

    /**
     * 项目应用名
     */
    @Value("${swagger.application-name}")
    private String applicationName;

    /**
     * 项目版本信息
     */
    @Value("${swagger.application-version}")
    private String applicationVersion;

    /**
     * 项目描述信息
     */
    @Value("${swagger.application-description}")
    private String applicationDescription;

    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
                .pathMapping("/")
                // 定义是否开启swagger,false为关闭,可以通过变量控制,线上关闭
                .enable(enable)
                //配置api文档元信息
                .apiInfo(apiInfo())
                // 选择哪些接口作为swagger的doc发布
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalRequestParameters(getGlobalRequestParameters());
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(applicationName)
                .description(applicationDescription)
                .contact(new Contact("xxxx平台接口文档", "www.xxx.com", "xxx.com"))
                .version(applicationVersion)
                .build();
    }


    //添加全局通用参数 一般用添加请求token
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder()
                .name("Authentication")
                .description("身份令牌")
                .required(true)
                //添加参数到请求头中
                .in(ParameterType.HEADER)
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
                .required(false)
                .build());
        return parameters;
    }
  }
}

yml配置swagger相关信息

swagger:
  enable: true
  application-name: 网关服务
  application-version: 1.0
  application-description: 网关服务

配置完,访问测试地址: http://ip:端口号/doc.html#/home

在微服务中存在多个服务,可以将以上配置部分做成公共模块,哪个模块需要引入swagger,引入该公共模块就行了。

2.整合到gateway

1.引入依赖 ,同上
2.gateway模块添加两配置文件,如下

SwaggerHandler

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

import java.util.Optional;

/**
 * SWAGGER-RESOURCES控制对象
 * @author ROCKY
 */
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider swaggerResources;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }

    @GetMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration)
                        .orElse(SecurityConfigurationBuilder.builder().build()),
                HttpStatus.OK));
    }

    @GetMapping("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration)
                        .orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @SuppressWarnings("rawtypes")
    @GetMapping("")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}

SwaggerResourcesProvider


import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

/**
 * 类说明: 聚合系统接口
 *
 * @author wqf
 * @date 2022/7/26 16:47
 */
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
    // SWAGGER3默认的URL后缀
    public static final String SWAGGER3URL = "/v3/api-docs";
    // 网关路由
    @Autowired
    private RouteLocator routeLocator;

    @Autowired
    private GatewayProperties gatewayProperties;

    // 聚合其他服务接口
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resourceList = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        // 获取网关中配置的route
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        for (RouteDefinition routeDefinition : gatewayProperties.getRoutes()) {
            if (routes.contains(routeDefinition.getId())) {
                for (PredicateDefinition predicateDefinition : routeDefinition.getPredicates()) {
                    if ("Path".equalsIgnoreCase(predicateDefinition.getName())) {
                        resourceList.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs()
                                        .get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER3URL)));
                    }
                }
            }
        }
        return resourceList;
    }

    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("3.0");
        return swaggerResource;
    }
}

注意点:

1. 网关对:/swagger-ui/**; /v3/api-docs 等swagger访问路径记得放行

2. 网关配置文件需要配置路由规则,以下配置是按服务名称进行路由转发的,好处是服务端口改了也没事。

server:
  port: 10000
spring:
  profiles:
    active: dev
  application:
    name: gateway-server
  #配置网关路由规则 按服务名进行路由
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件结合,通过serviceId 转发到具体的服务实例
          enabled: true #是否开启基于服务发现的路由规则
          lower-case-service-id: true # 是否将服务名称转小写
      routes:
        # 唯一标识 - 系统核心服务
        - id: base-server
          uri: lb://base-server
          #断言,路径相匹配的进行路由,配置服务端的方法路
          predicates:
            - Path=/base-server/**
          filters:
            - StripPrefix=0
        # 唯一标识 - 系统用户服务
        - id: auth-server
          uri: lb://auth-server
          predicates:
            - Path=/auth-server/**
          filters:
            - StripPrefix=0
  1. yml配置了几个服务的路由,则只有当所有服务都正常时,swagger访问页面才会正常,如果配置两个服务的路由规则,但只启动一个服务swagger是会报错的

出现的问题:
在swagger页面测试请求时,发现发出去的请求没有带上服务名导致访问404,如:http://127.0.0.1:10000/logout
正确请求是:http://127.0.0.1:10000/auth-server/logout
解决方法:java目录下创建目录 springfox.documentation.oas.web
在该目录下创建文件SpecGeneration

package springfox.documentation.oas.web;

import io.swagger.v3.oas.models.servers.Server;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 类说明: 处理swagger 模拟请求时 没有追加服务名的问题
 *
 * @author wqf
 * @date 2022/7/26 16:29
 */
@Slf4j
public class SpecGeneration {

    private static final String HEADER_NAME = "X-Forwarded-Prefix";
    private static final Logger LOGGER = getLogger(SpecGeneration.class);
    public static final String OPEN_API_SPECIFICATION_PATH
            = "${springfox.documentation.open-api.v3.path:/v3/api-docs}";
    protected static final String HAL_MEDIA_TYPE = "application/hal+json";

    private SpecGeneration() {
        throw new UnsupportedOperationException();
    }

    /**
     * 创建一个默认的 swagger 的server
     *
     * @param requestPrefix /v3/api-docs
     * @param requestUrl    请求的url
     * @return Server
     */
    public static Server inferredServer(String requestPrefix, String requestUrl) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String serverUrl = requestUrl.replace(requestPrefix, "");
        String header = null;
        try {
            URI url = new URI(requestUrl);
            serverUrl = String.format("%s://%s:%s", url.getScheme(), url.getHost(), url.getPort());
            header = request.getHeader(HEADER_NAME);
            if (!StringUtils.isEmpty(header)) {
                log.info("当前的服务为:{}", header);
                serverUrl += header;
            }
        } catch (URISyntaxException e) {
            LOGGER.error("Unable to parse request url:" + requestUrl);
        }
        String description = "Inferred Url";
        if (!StringUtils.isEmpty(header)) {
            description += " For " + header.substring(1);
        }
        return new Server()
                .url(serverUrl)
                .description(description);
    }

    public static String decode(String requestURI) {
        try {
            return URLDecoder.decode(requestURI, StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            return requestURI;
        }
    }
}

2022/07/27 补充
knife4j-spring-ui 依赖整合还是有点问题,可以先移除这个依赖,其他配置不变,swagger访问地址使用:
http://ip:网关端口号/swagger-ui/index.html

上一篇下一篇

猜你喜欢

热点阅读