天天看点

knife4j使用(整合spring boot和spring cloud gateway)

一、背景

前后端分离大大实现了项目的解耦合,可扩展性,以及专业人做专业事的特点。前端开发不仅仅有网页端,还有ios、安卓、小程序等,他们都可调用后端的接口。然而,传统的手写文档说明增加了后端开发的成本,同时也不利于前后端之间的沟通。与此同时,传统的浏览器直接测试法不仅不方便接口调用方法的指定,而且填写传递参数的方法耗时很大。为此swagger-ui这个能够在线生成接口说明文档及调试的框架诞生了。

  Swagger-ui框架能够在线生成相关的文档说明,说明的内容包括请求地址、请求方法、请求参数、响应体说明等,同时还增加了对应的接口调试。大大方便了前端调用者的理解以及降低了后端调试的成本。然而,原生的swagger-ui不支持表单转为multipart表单提交,说明的耦合度还是很高的,而且不符合国人的习惯。为此knife4j这个封装了原生的swagger-ui框架诞生了。

  框架knife4j提供了文档增强功能,进一步完善了文档的说明,迎合了国人的习惯。这些文档增强功能包括下面几点:

  • 个性化配置:通过个性化ui配置项,可自定义UI的相关显示信息。
  • 离线文档:根据标准规范,生成的在线markdown离线文档,开发者可以进行拷贝生成markdown接口文档,通过其他第三方markdown转换工具转换成html或pdf,这样也可以放弃swagger2markdown组件。这个功能大大降低了后端开发者的时间成本。
  • 接口排序:自1.8.5后,ui支持了接口排序功能,例如一个注册功能主要包含了多个步骤,可以根据swagger-bootstrap-ui提供的接口排序规则实现接口的排序,step化接口操作,方便其他开发者进行接口对接。

与此同时,knife4j进一步完善了接口调试模块,添加了表单提交类型,增加选择multipart形式提交,而且把文档说明和调试进一步分离。

二、Maven引入

SpringBoot项目中引入如下的依赖:

<!-- knife4j-spring-boot -->
<dependency>
  <groupId>com.github.xiaoymin</groupId>
  <artifactId>knife4j-spring-boot-starter</artifactId>
  <version>2.0.1</version>
</dependency>
           

这个配置的相关jar包依赖如下图:

knife4j使用(整合spring boot和spring cloud gateway)

从图中可以看出knife4j封装了原生的swagger-ui,就是在原生的swagger-ui基础上增加了一些适合国人的功能。

三、文件配置

网关中yml需要增加如下配置:

spring:
  cloud:
    gateway:
      locator:
        enabled: true
      routes:
        - id: shop-dn-member
          uri: lb://shop-dn-member
          predicates:
            - Path=/shop-dn-member/**
          filters:
            - SwaggerHeaderFilter
            - StripPrefix=1
      x-forwarded:
        enabled: false
           

同时网关还需要写如下三个java文件配置:

@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
    private static final String HEADER_NAME = "X-Forwarded-Prefix";
    private static final String URI = "/v2/api-docs";
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if (!StringUtils.endsWithIgnoreCase(path,URI )) {
                return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);
        };
    }
}
           
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
            route.getPredicates().stream() .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                    .forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0") .replace("**", "v2/api-docs"))));
        });
        return resources;
    }
    private SwaggerResource swaggerResource(String name, String location) {
        log.info("name:{},location:{}",name,location);
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("2.0");
        return swaggerResource;
    }
}
           
@RestController
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("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

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

    @GetMapping("/swagger-resources")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}
           

在其他项目中应该加入如下的配置:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(BeanValidatorPluginsConfiguration.class)
public @interface EnableBeanValidator {

}
           
@Configuration
@EnableKnife4j
@EnableSwagger2
@EnableBeanValidator
public class SwaggerConfiguration {

    @Bean(value = "memberApi")
    @Order(value = 1)
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.xf.member"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder() .title("member接口")
                .description("member接口").termsOfServiceUrl("http://localhost:7070/") .contact(new Contact("fangxiaofan", "" , "[email protected]"))
                .version("1.0") .build();
    }
}
           

四、类中引用

业务相关类中如果想要显示接口在在线文档里,需要加入相关注解。这里注解同swagger相关的。

相关的引用注解如下:

@Api:用在请求的类上,表示对类的说明

tags=“说明该类的作用,可以在UI界面上看到的注解”

value=“该参数没什么意义,在UI界面上也看到,所以不需要配置”

@ApiOperation:用在请求的方法上,说明方法的用途、作用

value=“说明方法的用途、作用”

notes=“方法的备注说明”

@ApiImplicitParams:用在请求的方法上,表示一组参数说明

@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面

name:参数名

value:参数的汉字说明、解释

required:参数是否必须传

paramType:参数放在哪个地方

· header --> 请求参数的获取:@RequestHeader

· query --> 请求参数的获取:@RequestParam

· path(用于restful接口)–> 请求参数的获取:@PathVariable

· body(不常用)

· form(不常用)

dataType:参数类型,默认String,其它值dataType=“Integer”

defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应

@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息

code:数字,例如400

message:信息,例如"请求参数没填好"

response:抛出异常的类

@ApiModel:用于响应类上,表示一个返回响应数据的信息

(这种一般用在post创建的时候,使用@RequestBody这样的场景,

请求参数无法使用@ApiImplicitParam注解进行描述的时候)

@ApiModelProperty:用在属性上,描述响应类的属性

五、实现效果

knife4j使用(整合spring boot和spring cloud gateway)

进入上图的界面要输入对应的url+/doc.html#/home即可进入接口调试的首页。

knife4j使用(整合spring boot和spring cloud gateway)

文档管理部分不仅仅生成的接口文档,包括了在线文档和离线文档。Swagger Models就是加了swagger注解的实体类,加载了Docket里面相关的配置。在文档配置下面就是对应模块的controller接口,每个接口既有文档说明又有调试部分,调试这里的配置比原生的swagger更加完善,多了响应头和提交参数相关的配置,便于测试文件上传部分。

与此同时,还有设定header请求头的功能,能够传递header的参数,有利于前端传递登录的相关token来进行网关拦截。设置的方法如下图所示。

knife4j使用(整合spring boot和spring cloud gateway)
knife4j使用(整合spring boot和spring cloud gateway)

六、总结

总之,knife4j大大简化了文档的生成和接口调试过程,简化了前端开发人员对接口的理解,迎合了国人的需求。但是文档里仍然有不合理的部分,这里需要我们进一步学习和改进相关的引入机制。

七、参考文献

郭艺宾:080-Spring Boot 整合 knife4j 地址:https://www.jianshu.com/p/48d9473de9c8

Knife4j快速开始:https://doc.xiaominfo.com/guide/useful.html

每特教育第六期微服务电商项目