前言
1.什么是swagger
Swagger 是一个用于生成、描述和调用 RESTful 接口的 Web 服务。通俗的来讲,Swagger 就是将项目中所有(想要暴露的)接口展现在页面上,并且可以进行接口调用和测试的服务。
从上述 Swagger 定义我们不难看出 Swagger 有以下 3 个重要的作用:
将项目中所有的接口展现在页面上,这样后端程序员就不需要专门为前端使用者编写专门的接口文档;
当接口更新之后,只需要修改代码中的 Swagger 描述就可以实时生成新的接口文档了,从而规避了接口文档老旧不能使用的问题;
通过 Swagger 页面,我们可以直接进行接口调用,降低了项目开发阶段的调试成本。
Swagger3完全遵循了 OpenAPI 规范。Swagger 官网地址:https://swagger.io/。
2. 什么是Knife4J? 和Swagger什么关系?
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案, 前身是swagger-bootstrap-ui,取名kni4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍!
Knife4j的前身是swagger-bootstrap-ui,为了契合微服务的架构发展,由于原来swagger-bootstrap-ui采用的是后端Java代码+前端Ui混合打包的方式,在微服务架构下显的很臃肿,因此项目正式更名为knife4j
更名后主要专注的方面
前后端Java代码以及前端Ui模块进行分离,在微服务架构下使用更加灵活
提供专注于Swagger的增强解决方案,不同于只是改善增强前端Ui部分
相关文档请参考:https://doc.xiaominfo.com/knife
一.配置
pom依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.配置文件
package com.example.test2.config;
import com.example.test2.model.enums.CommonResultEnum;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.annotations.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.*;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableSwagger2
@EnableKnife4j
public class Swagger2Config {
@Bean
public Docket appApi() {
ArrayList<Response> responseMessages = new ArrayList<>();
// 如果没有做自定义响应处理,可以用sawgger做自定义响应处理
//useDefaultResponseMessages 必须false
Arrays.stream(CommonResultEnum.values()).forEach(
codeEnum -> responseMessages.add(
new ResponseBuilder()
.code(codeEnum.getCode())
.description(codeEnum.getMessage())
.build())
);
return new Docket(DocumentationType.SWAGGER_2)
//是否使用默认返回信息
.useDefaultResponseMessages(false)
//不太清楚这俩是干嘛的..
/* .globalResponses(HttpMethod.GET,responseMessages)
.globalResponses(HttpMethod.POST,responseMessages)*/
//项目名称
.groupName("菜菜小菠菠")
//配置特定的描述
.apiInfo(apiInfo())
.select()
//扫描接口的方式
//通过包名扫描
// .apis(RequestHandlerSelectors.basePackage("com.example.test2.controller"))
//扫描所有
// .apis(RequestHandlerSelectors.any())
//通过api注解扫描
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
//通过方法的注解ApiOperation扫描
// .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
//过滤所有路径
.paths(PathSelectors.any())
.build()
//全局参数
.globalRequestParameters(this.setHeader())
// ⽀持的通讯协议集合
.protocols(Sets.newHashSet("http", "https"));
}
/**
* 配置swagger信息
*
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("菜菜小菠菠接口文档")
.description("该文档主要提供菜菜小菠菠后端的接口 \r\n\n")
.contact(new Contact("", "", null))
.version("0.0.1")
.build();
}
/**
* 添加header配置
*
* @return
*/
private List<RequestParameter> setHeader() {
ArrayList<RequestParameter> parameters = new ArrayList<>();
parameters.add(new RequestParameterBuilder()
.name("token")
.description("token令牌")
.required(true)
.query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
.in(ParameterType.HEADER)
.build());
return parameters;
}
}
3.enum
package com.example.test2.model.enums;
import com.example.test2.model.transdto.BaseResult;
/**
* 公共返回结果代码和信息 实现公共返回接口
*/
public enum CommonResultEnum implements BaseResult {
/**
* 公共信息
*/
COMMON_SUCCESS("0", "请求成功"),
COMMON_FAILED("-1", "请求失败"),
COMMON_SYS_ERROR("1000", "系统运行时错误"),
;
/**
* 返回信息代码
*/
private String code;
/**
* 返回信息内容
*/
private String message;
/**
* constructors
*/
CommonResultEnum(String code, String message) {
this.code = code;
this.message = message;
}
/**
* getters
*/
@Override
public String getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
二.主要注解
* @Api 类注解,在控制类添加此注解,可以对控制器类进⾏功能说明
* tags="说明该类的作⽤,可以在UI界⾯上看到的注解"
* value="该参数没什么意义,可以不配置"
* @ApiOperation ⽅法注解:说明接⼝⽅法的作⽤
* value="说明⽅法的⽤途、作⽤"
* notes="⽅法的备注说明"
* @ApiImplicitParams和@ApiImplicitParam ⽅法注解,说明接⼝⽅法的参数
* @ApiImplicitParam:⽤在@ApiImplicitParams注解中,指定⼀个请求参数的各个⽅⾯
* name:参数名
* value:参数的汉字说明、解释
* required:参数是否必须传
* paramType:参数放在哪个地⽅
* · header --> 请求参数的获取:@RequestHeader
* · query --> 请求参数的获取:@RequestParam
* · path(⽤于restful接⼝)--> 请求参数的获取:@PathVariable
* · div(不常⽤)
* · form(不常⽤)
* dataType:参数类型,默认String,其它值dataType="Integer" * defaultValue:参数的默认值
* @ApiIgnore ⽅法注解,接⼝不会在ui⽂档中出现
* @ApiResponses:⽤在请求的⽅法上,表示⼀组响应
* @ApiResponse:⽤在@ApiResponses中,⼀般⽤于表达⼀个错误的响应信息
* code:状态码,例如400
* message:信息,例如"请求参数没填好"
* response:抛出异常的类
三.界面展示
get请求
@GetMapping(value = "test2")
@ApiOperation(value = "get请求", notes = "get请求notes")
public TestDto getMonthly(
@ApiParam(required = true, value = "角色") @RequestParam String name,
@ApiParam(required = true, value = "职业") String job) {
TestDto monthly = iservice.getdto(name, job);
return monthly;
}
输入http://localhost:8088/swagger-ui.html

接口内部就不给大家展示了,相信大家也都用过swagger-ui,接下来展示doc.html
输入http://localhost:8088/doc.html
和Swagger2Config.java的对应

header的对应 类似于postman的环境变量

接口的配置


可直接进行请求,方便调试

接口文档下载

文档内容:




文档基本能满足接口文档的标准,有些东西自己可以手动改改,比如字体,段落什么的
响应实例的特殊符号修改方法:
ctrl+f查找


其他符号同理
四.详细用法
post请求使用参数传参

可以看到,请求类型变成了body.前端同学看到后肯定会迷惑
加上RequestParam注解后
@PostMapping(value = "test1")
@ApiOperation(value = "get请求", notes = "get请求notes")
public TestDto getMonthly(
@ApiParam(required = true, value = "角色") @RequestParam String name,
@ApiParam(required = true, value = "职业") String job) {
TestDto monthly = iservice.getdto(name, job);
return monthly;
}
可以看到 加上RequestParam注解后,name参数的请求类型变成了query

还有一种方式,是swagger的正统解决方式 ApiImplicitParam注解

可以看到,name的请求类型也会变成query
统一响应类中包含响应结果
实际应用中,我们都会使用响应类来包含响应结果,类似这样
public ResultData getMonthly(
@ApiParam(required = true, value = "角色") String name,
@ApiParam(required = true, value = "职业") String job) {
TestDto monthly = iservice.getdto(name, job);
return ResultData.ok(monthly);
}
响应结果会变成这样:

data里的TestDto类不予显示.我们可以把ResultData里的data的格式是object
/**
* 数据
*/
private Object data;
private ResultData(BaseResult baseResult, Object data) {
this.status = baseResult.getCode();
this.message = baseResult.getMessage();
this.data = data;
}
将ResultData类中的所有data的类型,全部改为T
/**
* 数据
*/
private T data;
private ResultData(BaseResult baseResult, T data) {
this.status = baseResult.getCode();
this.message = baseResult.getMessage();
this.data = data;
}
同时要将方法的返回值改为ResultData<TestDto>
public ResultData<TestDto> getMonthly(
@ApiParam(required = true, value = "角色") String name,
@ApiParam(required = true, value = "职业") String job) {
TestDto monthly = iservice.getdto(name, job);
return ResultData.ok(monthly);
}
swagger页面就有值了

五.踩坑系列
Failed to load API definition

java.lang.NullPointerException: null
at springfox.documentation.swagger2.mappers.RequestParameterMapper.mapParameter(RequestParameterMapper.java:55) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper.beforeMappingOperations(ServiceModelToSwagger2Mapper.java:125) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapOperation(ServiceModelToSwagger2MapperImpl.java:109) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper.mapOperations(ServiceModelToSwagger2Mapper.java:270) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper.lambda$mapApiListings$2(ServiceModelToSwagger2Mapper.java:258) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:1.8.0_181]
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) ~[na:1.8.0_181]
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) ~[na:1.8.0_181]
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270) ~[na:1.8.0_181]
at java.util.TreeMap$ValueSpliterator.forEachRemaining(TreeMap.java:2897) ~[na:1.8.0_181]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_181]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_181]
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:1.8.0_181]
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:1.8.0_181]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_181]
at java.util.stream.ReferencePipeline.forEachOrdered(ReferencePipeline.java:423) ~[na:1.8.0_181]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper.mapApiListings(ServiceModelToSwagger2Mapper.java:253) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapDocumentation(ServiceModelToSwagger2MapperImpl.java:48) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at springfox.documentation.swagger2.web.Swagger2ControllerWebMvc.getDocumentation(Swagger2ControllerWebMvc.java:99) ~[springfox-swagger2-3.0.0.jar:3.0.0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.7.jar:5.3.7]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.7.jar:5.3.7]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.46.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.7.jar:5.3.7]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.46.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
这个报错我是百思不得其解,为什么会报空指针?为什么另一个项目好使,我新建的demo就不好使?我建项目的时候难道有什么错误步骤吗?我为什么一步一个坎?我是谁?我在哪?
这个错误我找了一天,也在网上找了很多答案,有的说缺少spring-boot-starter-web依赖,但我在建项目时这个依赖就已经被idea自动添加了.终于在第二天,一个偶然的机会发现了是因为参数名字和注解的参数名字没对应.
@ApiOperation(value = "添加接口", notes = "添加接口")
@PostMapping(value = "test2")
@ApiImplicitParam(name = "aaa", type = "body", dataTypeClass = TestDto.class, required = true)
public ResultData<String> add(@RequestBody TestDto testDto) {
return ResultData.ok("success");
}
参数名字叫testDto,注解里写的是name = "aaa",改对了就可以快乐的开发了~~~
swagger页面无报错,控制台也无报错


但是没有接口.
最后发现是配置类中包名写错
.apis(RequestHandlerSelectors.basePackage("com.example.test3.controller"))
包名应该是test2,写成了test3
最惨的是,我同时遇到了这两个错误,包名没写对的同时,参数名也写错了...这就造成,我把包名写对,就报错,包名写错,就没接口,脑子都凌乱了....
六.swagger升级为3.0
将pom文件中这两个依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
改为
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
配置类的注解更改为
@Configuration
//@EnableSwagger2
@EnableOpenApi
@EnableKnife4j
public class Swagger3Config {
配置类中的swagger类型改为
// return new Docket(DocumentationType.SWAGGER_2)
return new Docket(DocumentationType.OAS_30)
浏览器中输入的swagger地址由
http://端口号/swagger-ui.html
改为http://端口号/swagger-ui/ 最后的/别忘了
swagger新增的swagger-bootstrap-ui 可以看这篇文档
http://www.hzhcontrols.com/new-1205817.html
swagger原理解析可以看这篇文档
https://blog.youkuaiyun.com/hxpjava1/article/details/78423726/
swagger通过接口生成ts或js代码可以看这篇
https://blog.youkuaiyun.com/weixin_44241402/article/details/128964496?utm_source=702048761
本项目文档地址
https://gitee.com/mhjo/swagger/tree/master/src
大家有发现哪里写的谬误敬请指出