在使用了SpringBoot之后,我们不再像SpringMVC那样使用JSP了,通常使用Controller做接口,然后前端页面使用Ajax访问接口来交互数据,将前后端进行分离。这样约定接口格式就成了重要的事情,如果是和外部开发对接,那么得出具接口文档,如果是内部使用的接口,那么使用swagger可以自动生成接口文档供前端进行查阅,并且可以直接做接口测试,十分的方便。
首先我们要在SpringBoot项目中引入Swagger2的依赖。
<!-- swagger -->
<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>
我的swagger.version = 2.7.0 ,可以根据SpringBoot版本选择其他版本。
引入依赖之后需要对swagger进行配置。
建一个config包,然后在里面创建一个Config类和一个序列化实现类
import static springfox.documentation.builders.PathSelectors.regex;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @project 项目:springboot
* @createDate 创建时间:2018/11/16 18:49
* @function 作用 : swagger2 的配置
*/
@Configuration
@EnableSwagger2
// 只允许dev和test环境下访问swagger
@Profile({"dev","test"})
public class SwaggerConfig {
@Bean
public Docket platformApi() {
// 添加默认返回状态
// 公共返回状态
List<ResponseMessage> publicResponse = new ArrayList<ResponseMessage>();
publicResponse.add(new ResponseMessageBuilder().code(200).message("成功").responseModel(new ModelRef("string")).build());
publicResponse.add(new ResponseMessageBuilder().code(400).message("缺少参数").responseModel(new ModelRef("string")).build());
publicResponse.add(new ResponseMessageBuilder().code(500).message("服务器内部错误").responseModel(new ModelRef("string")).build());
publicResponse.add(new ResponseMessageBuilder().code(501).message("联系后端,验证类型写错了").responseModel(new ModelRef("string")).build());
publicResponse.add(new ResponseMessageBuilder().code(502).message("请自行检查JSON格式").responseModel(new ModelRef("string")).build());
publicResponse.add(new ResponseMessageBuilder().code(666).message("缺少token或token无效").responseModel(new ModelRef("string")).build());
// get接口返回状态,继承自公共
List<ResponseMessage> getResponse = publicResponse;
getResponse.add(new ResponseMessageBuilder().code(201).message("查询成功但结果为空").responseModel(new ModelRef("string")).build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).forCodeGeneration(true)
.select().apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.apis(RequestHandlerSelectors.any())
.paths(regex("^.*(?<!error)$"))
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts()).globalResponseMessage(RequestMethod.GET,getResponse)
.globalResponseMessage(RequestMethod.PUT, publicResponse)
.globalResponseMessage(RequestMethod.POST, publicResponse)
.globalResponseMessage(RequestMethod.DELETE, publicResponse);
}
private List<ApiKey> securitySchemes() {
List<ApiKey> apiKeyList= new ArrayList();
//指定token的存放位置
apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
return apiKeyList;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts=new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(regex("^(?!auth).*$"))
.build());
return securityContexts;
}
//自动验证
List<SecurityReference> defaultAuth() {
// 指定需要验证信息的域,这里整个文档接口都需要校验
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences=new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
private ApiInfo apiInfo() {
// 指定文档标题和版本号
return new ApiInfoBuilder().title("xxxx项目在线API文档").version("1.0").build();
}
}
在apiInfo方法中,指定文档的标题和版本号,效果如下图
在platformApi方法中,我指定了一些返回码的含义,效果如下图。
securitySchemes和securityContexts还有defaultAuth三个方法是在接口配置了拦截器校验时使用的。
例如我要求请求中header的Authorization要携带用户名密码或者token之类的(上面代码是带token),若不进行配置的话,swagger只是发个普通的请求而已,会被我们的filter拦截,这样就只能看文档,不能直接进行测试了。
所以像上面那样配置之后,swagger右上角会有个按钮用来输入验证信息
我们输入有效的token或者用户名密码之后,每个接口旁边的红色感叹号就会消失了。当然securitySchemes方法中可以增加更多的输入,我就不演示了。
然后是一个序列化配置,就是序列化而已,没什么好说的。
import java.io.IOException;
import java.lang.reflect.Type;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import springfox.documentation.spring.web.json.Json;
/**
*
*/
public class SwaggerJsonSerializer implements ObjectSerializer, ObjectDeserializer {
public final static SwaggerJsonSerializer instance = new SwaggerJsonSerializer();
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
SerializeWriter out = serializer.getWriter();
Json json = (Json) object;
out.write(json.value());
}
@Override
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return null;
}
@Override
public int getFastMatchToken() {
return 0;
}
}
接下来我们看接口怎么写。
import java.util.Date;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.dingguan.shanyu.bean.ResultBean;
import com.dingguan.shanyu.util.Sequences;
import com.dingguan.shanyu.web.comment.pojo.Comment;
import com.dingguan.shanyu.web.comment.pojo.CommentBean;
import com.dingguan.shanyu.web.comment.service.CommentService;
import com.github.pagehelper.Page;
import io.swagger.annotations.*;
/**
* @project 项目:springboot
* @createDate 创建时间:2019/1/3 17:06
* @function 作用 : 评论 的对外接口
*/
@RestController
@RequestMapping("/Comment")
@Api(description = "评论")
public class CommentController {
@Autowired CommentService service;
@PostMapping
@ApiOperation("评论")
@ApiResponses({@ApiResponse(code = 400, message = "缺少参数")})
@ApiImplicitParams({@ApiImplicitParam(name = "comment", value = "*", paramType = "body", dataType = "Comment")})
public ResultBean insert(@RequestBody @Valid Comment comment) {
//初始化参数
comment.setId(Sequences.getId());
comment.setTime(new Date());
comment.setPraise(0);
service.insert(comment);
return new ResultBean(200, comment);
}
@GetMapping
@ApiOperation("查询列表")
@ApiResponses({})
@ApiImplicitParams({
@ApiImplicitParam(name="user",value="用户ID",paramType = "query",dataType = "string"),
@ApiImplicitParam(name="objType",value="评论对象类型:0-帖子,1-评论",paramType = "query",dataType = "int"),
@ApiImplicitParam(name="obj",value="对象ID",paramType = "query",dataType = "string"),
@ApiImplicitParam(name="root",value="根对象",paramType = "query",dataType = "string"),
@ApiImplicitParam(name = "index", value = "第几个", paramType = "query", dataType = "int"), @ApiImplicitParam(name = "size", value = "每页几个", paramType = "query", dataType = "int")})
public ResultBean<List<Comment>> findList(String user,Integer objType,String obj,String root,Integer index, Integer size) {
//验证参数
if (index == null || size == null) {
List<CommentBean> list = service.selectList(user,objType,obj,root);
if(list==null|| list.isEmpty()){
return new ResultBean(201);
}
ResultBean resultBean = new ResultBean(200,list);
// List<replaceBean> beans = service.turnToBeans(list);
// resultBean.setData(beans);
return resultBean;
// return new ResultBean(400);
}
Page<CommentBean> pages = service.selectList(user,objType,obj,root,index, size);
List<CommentBean> list = pages.getResult();
if (list == null || list.isEmpty()) {
return new ResultBean(201);
}
//封装类
// List<CommentBean> beans = service.turnToBeans(list);
ResultBean resultBean = new ResultBean(200, list,index,size, pages.getTotal());
// resultBean.setData(beans);
return resultBean;
}
@DeleteMapping("/{id}")
@ApiOperation("删除")
@ApiResponses({@ApiResponse(code = 400, message = "缺少参数")})
@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "*", paramType = "path", dataType = "stirng")})
public ResultBean deleteById(@PathVariable String id) {
//验证参数
if (id == null) {
return new ResultBean(400);
}
service.delete(id);
return new ResultBean(200);
}
@PutMapping
@ApiOperation("修改")
@ApiResponses({@ApiResponse(code = 400, message = "缺少参数")})
@ApiImplicitParams({@ApiImplicitParam(name = "comment", value = "*必须携带ID", paramType = "body", dataType = "Comment")})
public ResultBean<Comment> update(@RequestBody Comment comment) {
//验证参数
if (comment.getId() == null) {
return new ResultBean(400);
}
service.update(comment);
return new ResultBean(200, comment);
}
}
首先接口是按照不同的controller类,分成了不同的接口组。
Controller类上面的@Api 是对接口组的一个说明
然后我们在写RequestMapping的时候要注意,尽量别使用RequestMapping,因为swagger会为RequestMapping默认生成GET、POST、PUT、DELETE等等一套接口,实际上我们只实现了一个接口,所以不要使用RequestMapping,而去直接使用GetMapping、PostMapping这样的注解。
@ApiOperation 是对这个接口的说明
@ApiResponses 是说明接口返回的状态码的含义。这里是个数组,可以指定多个返回码,如果在前面SwaggerConfig配置了公共的返回码也会有用,但是这里的优先。
@ApiResponse 是@ApiResponses的数组元素,code配置返回码,message配置返回码的含义。
@ApiImplicitParams 是说明接口的入参,这里是个数组,可以设置多个入参。
@ApiImplicitParam 是具体每个入参,name为参数名,value为参数的说明,dataType为常数的类型(可以是Java类,如果String提示错误就用string),paramType是指定参数所在位置(例如path就是restful风格,query是URL上传参,body就是放在请求体中,paramType是可以组合使用的),required 是设置参数是否必填(代码中我没使用)
@ApiIgnore 不在接口文档显示
具体大家自己尝试过才清楚。
还有一点就是它会自己解析接口返回体的格式,我们可以在Response Class里看到,model是类格式,example value是举例
这里返回体的字段注释是怎么配的呢?
如果我们返回String的话就不用配了,我们只能设置自己可以修改的类。
从我的Controller可以看出我是封装了一个ResultBean
import com.dingguan.shanyu.constants.Constants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @createDate 创建时间:2018年9月22日 下午5:56:36
* @function 作用: 返回小程序和web的结果实体类
*/
@Data
@ApiModel
public class ResultBean<T> {
@ApiModelProperty("状态码")
private int code = Constants.RESP_STATUS_OK;
@ApiModelProperty("消息")
private String message;
@ApiModelProperty("数据")
private T data;
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("每次查询数量")
private Integer size;
@ApiModelProperty("第几条开始")
private Integer index;
public ResultBean() {
this.setCode(200);
}
// 封装消息
public ResultBean(int code, String message) {
this.code = code;
this.message = message;
}
// 封装消息
public ResultBean(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
// 封装消息
public ResultBean(int code, T data) {
this.code = code;
this.data = data;
}
// 封装消息
public ResultBean(int code) {
this.code = code;
}
// 封装数据
public static ResultBean returnData(Object o) {
ResultBean result = new ResultBean();
result.setCode(200);
result.setData(o);
return result;
}
//初始化数据
public ResultBean(int code, T data, Integer index, Integer size,Long toatl){
this.code = code;
this.data = data;
this.index = index;
this.size = size;
this.total = toatl;
}
}
加了@ApiModel 之后Swagger 才能解析,然后每个字段上面用@ApiModelProperty加字段注释。
如果像我的Get接口一样是返回list的话它也会自己处理。如果加了泛型,会泛型也一样配置@ApiModel就可以。
项目启动之后,在项目访问路径后面加 /swagger-ui.html 就可以打开文档页面了。在里面输入参数之后,点击Try it out !就会向接口发送请求。
如上图是paramType为query的方式,可以看到CURL和request headers 还有respongse body,一目了然。
补充说明,在SwaggerConfig的platformApi中设置默认返回状态时,
new ModelRef() 可以为 new ModelRef(null) ,但这样在新版本中会报错,所以最后默认为new ModelRef("string")
当然,也可以是自定的类,和ApiImplicitParam 的dataType是类似的