从Swagger2到Knife4j

Swagger2

众所周知,程序员要写文档,但是不想写文档。

Swagger2 介绍

Swagger2 可以帮我们生成交互式API文档。

Swagger2 可以动态生成文档,包括接口名、请求方法、参数、返回信息等等。

Swagger2 生成的文档可以直接在线调用,不需要额外的工具(如postman)。

引入依赖

<swagger.version>2.8.0</swagger.version>

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>${swagger.version}</version>
</dependency>
	    
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>${swagger.version}</version>
</dependency>

项目配置

1 写一个配置类

@Configuration
public class SwaggerConfig {
	
	@Value("${swagger.enable}")
	private boolean enableSwagger; //方便动态开启、关闭在线文档
	
	@Bean
        public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
        	.host("localhost:8082")
        	.enable(enableSwagger)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) // 这里是全局扫描@Api注解的类,还可以扫描任意位置,指定包以及针对方法上的指定注解 
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("XXX接口") //标题
                .description("XXX后台服务接口") // 描述
                .termsOfServiceUrl("localhost:8082")
                .contact(new Contact("tenny", "", "")) // 作者信息
                .license("")
                .licenseUrl("")
                .version("1.0") // 文档版本号
                .build();
    }
}

2 在类及方法上添加注解

@Api(tags = "用户管理")
@RestController
@RequestMapping("user")
public class UserController {
	
	@Autowired
	private UserService userService;
	
	@ApiOperation("用户列表")
	@PostMapping("list")
	public Result List() {
	        return Result.ok(userService.list());
	}
	
	@ApiOperation("新增用户")
	@PostMapping("add")
	public Result add(User user) {
		userService.add(user);
		return Result.ok();
	}
	
	@ApiOperation("更新用户")
	@PostMapping("update")
	public Result update(User user) {
		userService.update(user);
		return Result.ok();
	}
	
	@ApiOperation("删除用户")
	@PostMapping("delete")
	public Result delete(@RequestParam List<Integer> ids) {
		userService.delete(ids);
		return Result.ok();
	}

}

这里只用了两个基本的注解 @Api 和 @ApiOperation,其余注解用法可自行探索

常用注解

  • @Api:修饰整个类,描述Controller的作用
  • @ApiOperation:描述一个类的一个方法,或者说一个接口
  • @ApiParam:单个参数描述
  • @ApiModel:用对象来接收参数
  • @ApiModelProperty:用对象接收参数时,描述对象的一个字段
  • @ApiImplicitParam:一个请求参数
  • @ApiImplicitParams:多个请求参数

效果图

浏览器输入ip:端口/swagger-ui.html,如本例中localhost:8082/swagger-ui.html

swagger2首页

接口详细

swagger2接口详细

接口调用

点击Try It Out,输入参数name,age,再点击Execute,即可在线调用接口

swagger3接口调用

swagger-ui.html 404问题

如果项目里有配置实现了WebMvcConfigurer,需要在配置里实现addResourceHandlers方法

@Configuration
public class XxxConfig implements WebMvcConfigurer {
    
    // ...your config
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

如果项目里有配置继承了WebMvcConfigurationSupport,请在配置里重写addResourceHandlers方法

@Configuration
public class XxxConfig extends WebMvcConfigurationSupport {
	
	// ...your config

	@Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
            super.addResourceHandlers(registry);
        }
}

不足

到此,看起来还不错,一般使用也足够了

但是还有一些不足。比如,对JSON支持不好,没有通用token认证,接口多了显示起来很繁杂,没有搜索功能,还有就是,界面也不咋好看23333

Knife4j

所以Knife4j出场了

Knife4j 介绍

Knife4j 是国人开发的对Swagger2的增强。

Knife4j 界面更美观,且更符合国人使用习惯

Knife4j 可以生成各种格式的离线文档,Markdown,html,Word

Knife4j 可以统一添加header或query参数

引入依赖

删除之前的Swagger2依赖,引入新的依赖

<knife4j.version>3.0.3</knife4j.version>

<dependency>
	<groupId>com.github.xiaoymin</groupId>
	<artifactId>knife4j-spring-boot-starter</artifactId>
	<version>${knife4j.version}</version>
</dependency>

因为knife4j依赖已经包含了Swagger2依赖,为防止冲突,原来的Swagger2依赖直接删除

配置

因为是基于Swagger2,所以配置基本不用改。

这里只改了一处:DocumentationType.SWAGGER_2改为DocumentationType.OAS_30

@Configuration
public class SwaggerConfig {
	
	@Value("${swagger.enable}")
	private boolean enableSwagger; //方便动态开启、关闭在线文档
	
	@Bean
        public Docket docket() {
        return new Docket(DocumentationType.OAS_30) // *****只改了这里*****
        	.host("localhost:8082")
        	.enable(enableSwagger)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) // 这里是全局扫描@Api注解的类,还可以扫描任意位置,指定包以及针对方法上的指定注解 
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("XXX接口") //标题
                .description("XXX后台服务接口") // 描述
                .termsOfServiceUrl("localhost:8082")
                .contact(new Contact("tenny", "", "")) // 作者信息
                .license("")
                .licenseUrl("")
                .version("1.0") // 文档版本号
                .build();
    }
}

PS: 因为knife4j3.X的版本是针对OpenAPI3来提供的,如果使用knife4j2.X,对应OpenAPI2,那就不用改了。

效果图

浏览器输入ip:端口/doc.html,如本例中localhost:8082/doc.html

knife4j首页

接口详细,可配合@NotNull注解标识字段是否必填

knife4j接口详细

接口调用

knife4j接口调用

doc.html 404问题

和之前一样,addResourceHandlers方法添加

registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");

弥补Swagger2不足

1 更美观的界面,更符合国人操作习惯

2 搜索功能

knife4j搜索功能

3 全局token

knife4j全局token

4 支持JSON

knife4j支持JSON

5 离线文档

knife4j离线文档

6 个性化设置

knife4j个性化设置

配置进阶

某些情况,可能需要排除一些接口,或者接口分组

自定义排除某些Controller或方法

例如,我想将本例中的新闻管理去除

1 自定义一个注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NewsApi {
	
	String value() default "";
	
}

2 把它放在NewsController头上

@NewsApi
@Api(tags = "新闻管理")
@RestController
@RequestMapping("news")
public class NewsController {
	// ...省略
}

3 更新SwaggerConfig。(或者,现在这个类可以更名为Knife4jConfig XD)

@Configuration
public class SwaggerConfig {
	
	@Value("${swagger.enable}")
	private boolean enableSwagger;
	
	@Bean
        public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
        	.host("localhost:8082")
        	.enable(enableSwagger)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) // 这里是全局扫描@Api注解的类,还可以扫描任意位置,指定包以及针对方法上的指定注解 
                .apis(new SwaggerFilter()) // 自定义过滤
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("XXX接口")
                .description("XXX后台服务接口")
                .termsOfServiceUrl("localhost:8082")
                .contact(new Contact("tenny", "", ""))
                .license("")
                .licenseUrl("")
                .version("1.0")
                .build();
    }
    
    class SwaggerFilter implements Predicate<RequestHandler> {
        @Override
        public boolean test(@Nullable RequestHandler requestHandler) {
            if(requestHandler.findAnnotation(NewsApi.class).isPresent() || requestHandler.findControllerAnnotation(NewsApi.class).isPresent()){
                return false;
            }
            return true;
        }
    }
}

重启项目,新闻管理就没有了

knife4j排除接口

其实,Swagger2本身就有个注解@ApiIgnore,可以直接屏蔽接口。

我这里的做法,只是为了方便接下来演示分组~

接口分组

更新配置,新增了一个docket实例。并给每个docket指定groupName

@Configuration
public class SwaggerConfig {
	
	@Value("${swagger.enable}")
	private boolean enableSwagger;
	
	@Bean
        public Docket docket() {
        return new Docket(DocumentationType.OAS_30)
        	.host("localhost:8082")
        	.groupName("后台系统")
        	.enable(enableSwagger)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) // 这里是全局扫描@Api注解的类,还可以扫描任意位置,指定包以及针对方法上的指定注解 
                .apis(new SwaggerFilter()) // 自定义过滤
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("XXX接口")
                .description("XXX后台服务接口")
                .termsOfServiceUrl("localhost:8082")
                .contact(new Contact("tenny", "", ""))
                .license("")
                .licenseUrl("")
                .version("1.0")
                .build();
    }
    
    class SwaggerFilter implements Predicate<RequestHandler> {
        @Override
        public boolean test(@Nullable RequestHandler requestHandler) {
            if(requestHandler.findAnnotation(NewsApi.class).isPresent() || requestHandler.findControllerAnnotation(NewsApi.class).isPresent()){
                return false;
            }
            return true;
        }
    }
    
    @Bean
    public Docket docket2() {
        return new Docket(DocumentationType.OAS_30)
        	.host("localhost:8082")
        	.groupName("新闻系统")
        	.enable(enableSwagger)
                .apiInfo(apiInfo2())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) // 这里是全局扫描@Api注解的类,还可以扫描任意位置,指定包以及针对方法上的指定注解 
                .apis(new SwaggerFilter2()) // 自定义过滤
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo2() {
        return new ApiInfoBuilder()
                .title("XXX接口")
                .description("XXX新闻系统接口")
                .termsOfServiceUrl("localhost:8082")
                .contact(new Contact("tenny", "", ""))
                .license("")
                .licenseUrl("")
                .version("1.0")
                .build();
    }
    
    class SwaggerFilter2 implements Predicate<RequestHandler> {
        @Override
        public boolean test(@Nullable RequestHandler requestHandler) {
            if(requestHandler.findAnnotation(NewsApi.class).isPresent() || requestHandler.findControllerAnnotation(NewsApi.class).isPresent()){
                return true;
            }
            return false;
        }
    }
}

重启项目,刷新http://localhost:8082/doc.html

接口已经分组了。左上角可以切换

knife4j分组1

knife4j分组2

这个Predicate非常灵活,根据自己业务需求自行探索。

总结

动态文档,在线调用,界面美观,简洁易用

官网:https://doc.xiaominfo.com/knife4j/

posted @ 2022-04-15 10:05  淘气小饼干  阅读(754)  评论(0编辑  收藏  举报