从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
接口详细
接口调用
点击Try It Out
,输入参数name,age,再点击Execute
,即可在线调用接口
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
接口详细,可配合@NotNull
注解标识字段是否必填
接口调用
doc.html 404问题
和之前一样,addResourceHandlers方法添加
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
弥补Swagger2不足
1 更美观的界面,更符合国人操作习惯
2 搜索功能
3 全局token
4 支持JSON
5 离线文档
6 个性化设置
配置进阶
某些情况,可能需要排除一些接口,或者接口分组
自定义排除某些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;
}
}
}
重启项目,新闻管理就没有了
其实,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
接口已经分组了。左上角可以切换
这个Predicate非常灵活,根据自己业务需求自行探索。
总结
动态文档,在线调用,界面美观,简洁易用
官网:https://doc.xiaominfo.com/knife4j/