# SpringBoot 整合 Knife4j

image-20230904123933493

# 1 什么是 Knife4j

在日常开发中,写接口文档使我们必不可少的,而 Knife4j 就是一个接口文档工具。可以看作是 Swagger 的升级版,但是界面比 Swagger 更好看,功能更丰富。

早期,swagger-boostrap-ui 是 1.x 版本,如今 swagger-bootrap-ui 到 2.x,同时也更改名字 Knife4j,适用于单体和微服务项目。

Knife4j 官方网站:https://doc.xiaominfo.com/

# 2 SpringBoot 整合 Knife4j

  1. 创建 SpringBoot 项目

  2. 引入 Knife4j 相关依赖

    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
    </dependency>
  3. application.yml 配置

    # knife4j 的增强配置,不需要增强可以不配
    knife4j:
      enable: false
      setting:
        language: zh_cn
  4. 配置 Knife4j 配置类

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.ParameterBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.schema.ModelRef;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.service.Parameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
    import java.util.ArrayList;
    import java.util.List;
    //swagger2 配置类
    @Configuration
    @EnableSwagger2WebMvc
    public class Knife4jConfig {
        @Bean
        public Docket adminApiConfig(){
            List<Parameter> pars = new ArrayList<>();
            ParameterBuilder tokenPar = new ParameterBuilder();
            tokenPar.name("token")
                    .description("用户token")
                    .defaultValue("")
                    .modelRef(new ModelRef("string"))
                    .parameterType("header")
                    .required(false)
                    .build();
            pars.add(tokenPar.build());
            // 添加 head 参数 end
            Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
                    .groupName("adminApi")
                    .apiInfo(adminApiInfo())
                    .select()
                    // 只显示 admin 路径下的页面
                    .apis(RequestHandlerSelectors.basePackage("com.dkx"))
                    .paths(PathSelectors.regex("/admin/.*"))
                    .build()
                    .globalOperationParameters(pars);
            return adminApi;
        }
        private ApiInfo adminApiInfo(){
            return new ApiInfoBuilder()
                    .title("后台管理系统-API文档")
                    .description("本文档描述了后台管理系统微服务接口定义")
                    .version("1.0")
                    .contact(new Contact("dkx", "http://dkx.com", "1851644015@qq.com"))
                    .build();
        }
    }
  5. 在 controller 类中配置注解让 Knife4j 有中文信息

    • 类:Api (tags = “”)
    • 方法:ApiOperation (value =“”)
    @Api(tags = "角色管理接口")
    @Controller
    @RequestMapping("/admin/system/sysRole")
    public class SysRoleController {
        @Qualifier(value = "SysRoleServiceImpl")
        @Autowired
        private SysRoleService sysRoleService;
        @ApiOperation(value = "查询所有的接口")
        @RequestMapping(value = "/findAll", method = RequestMethod.GET)
        @ResponseBody
        public List<SysRole> findAllRole() {
            List<SysRole> list = sysRoleService.list();
            return list;
        }
        @ApiOperation(value = "逻辑删除接口")
        @RequestMapping(value = "/remove", method = RequestMethod.DELETE)
        @ResponseBody
        public boolean removeRole(Integer id) {
            boolean b = sysRoleService.removeById(id);
            return b;
        }
    }
  6. 测试

    • 启动项目
    • 访问路径:localhost/doc.html

    测试接口的结果

    image-20230904141115698

# 3、接口参数

在方法的入参参数是对象的情况下,我们进行一个假设;
假设这个对象有三个属性,分为 name,age,sex,但我们的实际上只需要前端传来该对象的‘name’值和’age’值。
那么,此时我们通过 @ApiImplicitParam 注解中的’name’属性,来指定该对象的字属性名字即可

@ApiOperation(value = "修改预测外呼参数")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(name = "explicitNumber",value = "外显号码",dataType = "String",required = true),
            @ApiImplicitParam(name = "fsProjectId",value = "预测外呼任务id",dataType = "String",required = true),
            @ApiImplicitParam(name = "remarks",value = "对于该条外呼预测任务的一些备注说明",dataType = "String",required = false),
            @ApiImplicitParam(name = "answerRate",value = "接通率",dataType = "Integer",required = true),
            @ApiImplicitParam(name = "callDuration",value = "通话时长 单位为秒",dataType = "Long",required = true),
            @ApiImplicitParam(name = "afterCallDuration",value = "话后整理时长 单位为秒",dataType = "Long",required = true),
            @ApiImplicitParam(name = "skillGroupId",value = "技能组id",dataType = "String",required = true),
            @ApiImplicitParam(name = "ivrFlowType",value = "是否按键 0:挂断前用户无需按键  1:挂断前需用户按键,并对confirmTip参数定义内容",dataType = "Integer",required = true)
    })
    @PatchMapping(value = "/update")
    public AjaxResult updateForecastOutbound(@RequestBody ForecastProject forecastProject){
        return forecastOutboundService.updateForecastOutbound(forecastProject);
    }

# 4、方法返回值

对于返回值的参数描述,则需要使用 @ApiResponses 注解,再以数组形式,在该注解里头使用 @ApiResponse 注解来对返回值进行描述。
不过和入参描述不同的是,我们对返回值的描述是以一个实体类为描述参照的;
这是因为入参参数那样可以有一个或多个,但返回值就只有一个,不过返回值,也就是对象里头有多个字段。
基于此,我们需要先在用于当作返回值类型的实体类里头,通过 @ApiModelProperty 注解对实体类的属性值进行描述,可以参考给实体类的字段添加描述。
@ApiResponse 的属性如下:

@ApiResponse对入参进行描述
code响应状态码
message该响应状态码所对应的消息
response指定一个用于描述返回值类型,一般实体类。
@ApiResponses({
   @ApiResponse(code = 200,message = "ok",response = ForecastProject.class)
})
@GetMapping(value = "/list")
public TableDataInfo getForecastTaskList(@RequestParam(value = "name",required = false) String name,
                                         @RequestParam(value = "skillGroupId",required = false) String skillGroupId,
                                         @RequestParam(value = "state",required = false) Integer state,
                                         @RequestParam(value = "ivrFlowYype",required = false) Integer ivrFlowYype){
   startPage();
   List<ForecastProject> forecastProjectList = forecastOutboundService.getForecastTaskList(name,skillGroupId,state,ivrFlowYype);
   return getDataTable(forecastProjectList);
}

不知道你有注意到没,例子中,我指定的返回值类型虽然为’ForecastProject‘,但是方法中的返回值类型却是’TableDataInfo‘。
这是因为 TableDataInfo 是一个用于封装响应结果的对象,ForecastProject 的数据其实被封装在了 TableDataInfo 类中的’rows’字段。
那我们为什么不把 response 属性指定为 TableDataInfo 类呢?
这就是工作协调中要灵活多变的地方,因为你把’TableDataInfo‘类当作要描述的返回值类型去给前端调试的话,是没有意义的!
前端开发人员当然知道需要操作 TableDataInfo.rows 来取得数据,但描述具体信息的实体类到底是什么结构,它却不知道,因此当然要将具体的实体类当作返回值类型去描述。

参考:
https://www.freesion.com/article/1709510261/
https://www.cnblogs.com/h-c-g/p/11004020.html
https://blog.csdn.net/jiangyu1013/article/details/83107255
https://blog.51cto.com/u_5634409/2343942
https://blog.csdn.net/qq_25983579/article/details/104532743