SpringBoot项目集成knif4j,从此告别手写Api文档

2021-09-29 15:30:50 浏览数 (1)

前言

作为一名后台开发人员,在前后端分离项目的开发过程中,我们写好了后台接口之后总免不了要给前端同事提供一份详细的API接口文档,写完一个接口又要补充一个接口的文档,过程还挺繁琐的。那么有没有一款工具让我们不用再些这些繁琐的API文档呢?答案是有的。之前我们在项目中配置swagger结合相关的注解来生成API文档界面,只是界面不是那么美观,还必须在每个接口控制器方法中添加很多的注解,代码侵入性比较强。

现在越来越多的开发人员使用Knif4j来生成API文档,它是升级版的swagger, 不仅具有美观的界面,而且不需要在控制器方法中添加非常多的参数注解。哪怕不加任何注解,只要在项目中集成并配置好Docket类bean,就能生成界面美观的API接口文档,而且还有接口调试功能。

1 项目介绍

Knife4j的前身是swagger-bootstrap-ui,前身swagger-bootstrap-ui是一个纯swagger-uiui皮肤项目

一开始项目初衷是为了写一个增强版本的swagger的前端ui,但是随着项目的发展,面对越来越多的个性化需求,不得不编写后端Java代码以满足新的需求,在swagger-bootstrap-ui的1.8.5~1.9.6版本之间,采用的是后端Java代码和Ui都混合在一个Jar包里面的方式提供给开发者使用。这种方式虽说对于集成swagger来说很方便,只需要引入jar包即可,但是在微服务架构下显得有些臃肿。

因此,项目正式更名为knife4j,取名knife4j是希望它能像一把匕首一样小巧、轻量并且功能强悍。更名也是希望把她做成一个为Swagger接口文档服务的通用性解决方案,不仅仅只是专注于前端Ui前端。

swagger-bootstrap-ui的所有特性都会集中在knife4j-spring-ui包中,并且后续也会满足开发者更多的个性化需求。主要的变化是项目的相关类包路径更换为com.github.xiaoymin.knife4j前缀,开发者使用增强注解时需要替换包路径后端Java代码和ui包分离为多个模块的jar包,以面对在目前微服务架构下更加方便的使用增强文档注解(使用SpringCloud微服务项目,只需要在网关层集成UI的jar包即可,因此分离前后端)

knife4j沿用swagger-bootstrap-ui的版本号,第1个版本从1.9.6开始,关于使用方法,请参考[版本说明](2.6 版本说明 | knife4j (xiaominfo.com))。

目前主要支持以Java开发为主,并且是依赖于大环境下使用的Spring MVCSpring BootSpring Cloud框架。

当然,Knife4j也提供了离线版本,只要是符合Swagger的OpenAPI版本的规范JSON,都可以通过简单的配置进行适配,离线版本是适合于任何语言中使用Swagger非常的灵活方便。

2 界面鉴赏

Knife4j采用Vue And Design Vue组件重写,相关功能界面如下,供大家赏鉴:

接口文档显示界面如下:

接口调试界面如下:

Swagger Models功能

3 快速开始

本次示例使用Spring Boot作为脚手架来快速集成Knife4j,Spring Boot版本2.3.5.RELEASE,Knife4j版本2.0.7,完整代码可以去参考knife4j-spring-boot-fast-demo

第一步:在maven项目的pom.xml中引入Knife4j的依赖包,代码如下:

代码语言:javascript复制
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.7</version>
</dependency>

第二步:新建Knife4j配置类,配置Docket bean:

代码语言:javascript复制
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        //.title("swagger-bootstrap-ui-demo RESTful APIs")
                        .description("# swagger-bootstrap-ui-demo RESTful APIs")
                        .termsOfServiceUrl("http://www.xx.com/")
                        .contact("xx@qq.com")
                        .version("1.0")
                        .build())
                //分组名称
                .groupName("2.X版本")
                .select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.github.xiaoymin.knife4j.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
}

最終整个工程目录结构如下图:

IndexController.java包含一个简单的RESTful接口,代码示例如下:

代码语言:javascript复制
@Api(tags = "首页模块")
@RestController
public class IndexController {
    @ApiImplicitParam(name = "name",value = "姓名",required = true)
    @ApiOperation(value = "向客人问好")
    @GetMapping("/sayHi")
    public ResponseEntity<String> sayHi(@RequestParam(value = "name")String name){
        return ResponseEntity.ok("Hi:" name);
    }
}

此时,启动Spring Boot工程,在浏览器中访问:http://localhost:17790/doc.html

界面效果图如下:

5 blogserver项目集成knif4j

第一步:项目的pom.xml文件中引入knife4j-spring-boot-starter起步依赖

代码语言:javascript复制
<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-spring-boot-starter</artifactId>
	<version>3.0.2</version>
</dependency>

这里笔者用了最新的3.0.2版本

第二步:新建Knife4j配置类:

代码语言:javascript复制
package org.sang.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.spi.service.contexts.SecurityContext;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;


@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfig {

    @Bean
    public Docket createRestApi(){

        Predicate<RequestHandler> selector1 = RequestHandlerSelectors
                .basePackage("org.sang.controller"); //扫描包改成自己项目下的Controller类所在的包 

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("VBlog博客平台")
                .apiInfo(apiInfo())
                .select()
                .apis(selector1)
                .paths(PathSelectors.any())
                .build()
                .securityContexts(securityContexts())
                .securitySchemes(securitySchemas());

    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("VBlog博客平台RESTful APIs")
                .description("VBlog博客平台 api接口文档")
                .version("1.0")
                .build();

    }

    private List<SecurityScheme> securitySchemas(){
        List<SecurityScheme> list = new ArrayList();
        list.add(new ApiKey("loginToken", "loginToken", "header"));
        return list;
    }


    private List<SecurityReference> securityReferences(){
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[]
                {new AuthorizationScope("global", "accessEverything")};
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("loginToken", authorizationScopes));
        return securityReferences;
    }


    private List<SecurityContext> securityContexts(){

        List<SecurityContext> list = new ArrayList();
        SecurityContext securityContext = SecurityContext.builder()
                .securityReferences(securityReferences())
                .forPaths(PathSelectors.regex("/*"))
                .build();
        list.add(securityContext);

        return list;
    }
}

第三步:新建WebApplicationConfig类重写addViewControllers方法

代码语言:javascript复制
package org.sang.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebApplicationConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
         registry.addViewController("/").setViewName("doc.html");
    }
}

重写addViewControllers方法的目的是为了服务启动后能顺利访问到API文档界面

第四步:在控制器类上加上@Api注解, 在路由方法上加上@ApiOperation注解对路由方法进行描述,加上@ApiImplicitParam对接口入参进行详细描述。

示例代码:

代码语言:javascript复制
@RestController
@RequestMapping(path = "/role")
@Api(value="roleController", tags = "角色相关API")
public class RoleController {

    @Autowired
    private RoleService roleService;

    private static final Logger logger = LoggerFactory.getLogger(RoleController.class);

    @PostMapping(path = "/addRole")
    @ApiOperation(value = "addRole", notes = "添加角色", produces = "application/json",
            consumes = "application/json", response = RespBean.class)
    @ApiImplicitParam(name="role", value = "角色对象", dataTypeClass = Role.class, paramType="body", required = true)
    public RespBean<Integer> addRole(@RequestBody Role role) {
        logger.info("roleCode={},roleName={}",role.getRoleCode(),role.getRoleName());
        int addCount = roleService.addRole(role);
        RespBean<Integer> respBean = new RespBean<>(200, "success");
        respBean.setData(addCount);
        return respBean;
    }
}

@Api注解方法属性说明:

代码语言:javascript复制
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Api {
    // Api名称
    String value() default "";
    // Api标签
    String[] tags() default {""};

    /** @deprecated */
    @Deprecated
    // Api描述,已过时
    String description() default "";

    /** @deprecated */
    @Deprecated
    // Api基础路径,已过时
    String basePath() default "";

    /** @deprecated */
    @Deprecated
    // Api位置,已过时
    int position() default 0;
    // 响应参数数据类型,json格式数据类型为application/json
    String produces() default "";
    // 请求参数数据类型
    String consumes() default "";
    // 协议:http|https|dubbo|rmi
    String protocols() default "";
    // 认证信息
    Authorization[] authorizations() default {@Authorization("")};
    // 是否隐藏
    boolean hidden() default false;
}

这个注解主要用在控制器类上

@ApiOperation注解方法属性说明

代码语言:javascript复制
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiOperation {
    // Api操作方法名称
    String value();
    // Api操作方法注意点
    String notes() default "";
    // Api操作方法标签集合
    String[] tags() default {""};
    // 响应对象类
    Class<?> response() default Void.class;
    // 响应体容器
    String responseContainer() default "";
    // 响应体引用
    String responseReference() default "";
    // http请求类型
    String httpMethod() default "";

    /** @deprecated */
    @Deprecated
    int position() default 0;
    // Api操作方法昵称
    String nickname() default "";
    // 响应体参数类型,若无则与@Api中的该参数值保持一致
    String produces() default "";
    // 请求体参数类型,若无则与@Api中的该参数值保持一致
    String consumes() default "";
    // 接口请求协议,若无则与@Api中的该参数值保持一致
    String protocols() default "";
    // 接口调用认证信息,若无则与@Api中的该参数值保持一致
    Authorization[] authorizations() default {@Authorization("")};
    // 是否隐藏,默认显示
    boolean hidden() default false;
    // 响应头
    ResponseHeader[] responseHeaders() default {@ResponseHeader(
    name = "",
    response = Void.class
)};
    // 响应码,默认为200
    int code() default 200;
    // 扩展参数类别
    Extension[] extensions() default {@Extension(
    properties = {@ExtensionProperty(
    name = "",
    value = ""
)}
)};
    // 忽略json视图,默认否
    boolean ignoreJsonView() default false;
}

这个注解主要用在控制器类中的路由方法上

@ApiImplicitParams注解方法属性说明:

代码语言:javascript复制
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiImplicitParams {
    ApiImplicitParam[] value();
}

这个注解主要用于控制器类中的路由方法中有多个参数时使用,它的value值是个@ApiImplicitParam数组

@ApiImplicitParam 注解方法属性说明:

代码语言:javascript复制
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiImplicitParam {
    // 参数名称
    String name() default "";
    // 参数显示名称
    String value() default "";
    // 参数默认值
    String defaultValue() default "";
    // 允许的多个值
    String allowableValues() default "";
    // 是否必须
    boolean required() default false;
    
    String access() default "";
    // 是否允许多个
    boolean allowMultiple() default false;
    // 参数数据类型
    String dataType() default "";
    // 参数数据类型类
    Class<?> dataTypeClass() default Void.class;
    // 参数类型:query|body|path
    String paramType() default "";
    // 参数示例
    String example() default "";
    // 参数详细示例
    Example examples() default @Example({@ExampleProperty(
    mediaType = "",
    value = ""
)});
    
    String type() default "";
    // 参数格式
    String format() default "";
    // 是否允许参数值为空
    boolean allowEmptyValue() default false;
    // 参数是否只读
    boolean readOnly() default false;
    // 集合格式
    String collectionFormat() default "";
}

6 效果体验

第一步: 启动blogserver项目服务和vue-element-admin前端项目服务

启动blogserver项目服务时在IDEA中选中启动类BlogserverApplication类下的main函数->右键->Debug BlogserverApplication

启动vue-element-admin前端项目服务时在项目根目录下:右键->Git Bash Here

在弹出的命令控制台中输入命令npm run dev 然后回车

控制台出现如下日志信息代表启动成功

代码语言:javascript复制
 App running at:
  - Local:   http://localhost:3000/
  - Network: unavailable

  Note that the development build is not optimized.
  To create a production build, run npm run build.

第二步:打开谷歌浏览器输入http://localhost:8081/blog/doc.html 后回车

由于后台项目中通过SpringSecurity配置了安全认证,浏览器首先会跳转到 http://localhost:3000/#/login 登录页面进项登录,登录成功后再次输入http://localhost:8081/blog/doc.html 后回车就进入了Api文档首页VBlog博客平台RESTful APIs 效果图如下:

点击文档下面的调试可进入接口调试界面

我们输入请求参数后,再点击右上角的发送按钮即可测试接口的可用性,下面的响应内容去可以看到接口的返回信息。这样我们就可以直接通过文档页面测试接口,而不需要打开postman来调试接口了。

更多关于Knif4j增强文档功能请读者查看官方文档增强模式部分:https://doc.xiaominfo.com/knife4j/documentation/enhance.html

7 小结

本文我们通过Knife4j的官方文档学习了Knife4j项目以及如何在自己的SpringBoot项目中集成knife4j-spring-boot-strater组件自动生成升级版的Swagger2API文档。总结起来就一下四个步骤:

  • pom.xml文件中引入knife4j-spring-boot-strater组件的起步依赖
  • 配置swagger2文档Docket类bean,在接口扫描基础包中制定自己项目中控制器类所在的包名
  • 重写WebMvcConfigurer#addViewControllers方法,添加文档文件doc.html视图
  • 控制器类上添加@Api注解, 控制器中操作方法上添加@ApiOperationApiImplicitParam 注解

其中,第四步为可选项,用户也可步添加这三个注解,knif4j也能根据Spring MVC的注解生成接口文档,只是在页面显示的Api接口很多值都是默认值。

好了,本文就写到这里,希望读者朋友们都能动手实践一遍,亲自体验一把knife4j带来的神奇功能!

参考文档

【1】Knife4j官方文档:

https://doc.xiaominfo.com/knife4j/documentation

【2】接口文档从Swagger升级成knife4j使用教程

https://baijiahao.baidu.com/s?id=1683466038755184828

0 人点赞