什么是Swagger?
事一个强大的工具,功能有:
1. 生成api文档:
一个类有什么方法、什么属性?
类里边某个方法的作用、属性的意义?
一个或多个(重载)方法的参数是什么,返回值是什么,方法的作者对方法的描述又是什么?
生成Mock.js模拟数据:
提供给前端开发人员使用。
生成API测试页:
同时提供给前端、后端、测试人员。
2. 情境:
从前:
只能预先写好/生成文档,但是如果有改动,API的调用者拿到的就不是最新的文档,可能会出错。
伟大转折:
路人A想,文档不应该提前写好的,而是在用户请求文档时才实时生成文档,如此必定保证用户拿到的是最新版的文档,但是该怎么做?
自从有了Swagger:
API调用文档和测试框架可以靠它生成了!
与Spring Boot整合
导入依赖
方法一:导入官方指定的包
<!-- swagger -->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- swagger-ui -->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- JSON API documentation for spring based applications -->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-bean-validators -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>2.9.2</version>
</dependency>
方法二:使用Starter
<!-- https://mvnrepository.com/artifact/com.spring4all/swagger-spring-boot-starter -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
配置
创建配置类SwaggerConfig:
package config;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Swagger配置类
*/
@Configuration // 声明此类为配置类
@EnableSwagger2 // 启用Swagger
public class SwaggerConfig {
/**
* 配置生成RESTful api的测试接口
* @return Docket
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(this.apiInfo()).select().apis(RequestHandlerSelectors.basePackage("controller")).paths(PathSelectors.any()).build();
}
/**
* 配置API上显示的信息
* @return ApiInfo
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Swagger Test").contact(new Contact("creepyCaller", "https://github.com/creepyCaller", "")).version("0.0.1").description("API 描述文档").build();
}
}
在Spring Boot的main方发上的@SpringBootApplication里添加属性scanBasePackages:
@SpringBootApplication(scanBasePackages = 这里填写包名)
public class BootStrap {
public static void main(String[] args) {
SpringApplication.run(BootStrap.class, args);
}
}
测试是否导入成功
- 启动Spring Boot
- 访问地址http://localhost:8080/swagger-ui.html
- 看看是否加载成功:
使用对类/方法的注解令其自动生成文档(常用注解)
@Api()
@ApiOperation()
注解在方法上,说明方法的作用,每一个URI资源的定义
@ApiImplicitParams({...})
注解在方法上,包含一组ApiImplicitParam
@ApiImplicitParam()
注解在方法上(如果只需要一个参数)。
注解在@ApiImplicitParams中(多个参数)。
指定一个HTTP请求参数的配置信息。
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
= header --> 请求参数的获取:@RequestHeader
= query --> 请求参数的获取:@RequestParam
= path --> 请求参数的获取:@PathVariable
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
综合示例
对订单这个资源的五个HTTP动词具体实现、Swagger注解的示例:
package controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import domain.Want;
import model.ResultModel;
import service.WantService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@Controller
@RequestMapping("/wants")
@Api("订单控制器,用于对订单资源的GET、POST、PATCH、PUT、DELETE操作")
public class WantsController {
private static final int PAGE_SIZE = 10;
private final WantService wantService;
private ObjectMapper mapper;
public WantsController(WantService wantService) {
this.mapper = new ObjectMapper();
this.wantService = wantService;
}
/**
* 异步加载want
* @param page 第 {page} 页
* @return 包含第 {page} 页的订单信息的JSON字符串
* */
@GetMapping(value = "/page/{page}")
@ApiOperation(value = "获取第{page}页的订单列表", notes = "返回Page<T>对象")
@ApiImplicitParam(name = "page", required = false, dataType = "Integer", paramType = "path")
public ResponseEntity<ResultModel> getWantPage(@PathVariable(value = "page") Integer page) {
Page<Want> pager = null;
if (page < 1) {
// 如果请求的页号小于1,则到第一页
pager = wantService.findAll(0, PAGE_SIZE);
} else {
pager = wantService.findAll(page - 1, PAGE_SIZE);
int total = pager.getTotalPages();
if (page > total) {
// 如果请求的页号大于总页数,则到最后一页
pager = wantService.findAll(total - 1, PAGE_SIZE);
}
}
return new ResponseEntity<>(ResultModel.ok(1, pager), HttpStatus.OK);
}
@GetMapping(value = "/{id}")
@ApiOperation(value = "获取id为{id}的订单详情", notes = "返回更新后的订单,标准的返回格式:{\"id\":1,\"name\":\"62LT\",\"amount\":0,\"price\":0.0,\"remark\":\"无\",\"date\":\"2019-10-09T10:23:56.000+0000\",\"status\":0}")
@ApiImplicitParam(name = "id", dataType = "Integer", paramType = "path") // paramType = "path"用于@PathVariable注解的参数的获取
public ResponseEntity<ResultModel> getWant(@PathVariable(value = "id") Integer id) {
return new ResponseEntity<>(ResultModel.ok(1, wantService.findById(id)), HttpStatus.OK);
}
@ApiOperation(value = "完全更新id为{id}的订单", notes = "传入一个完整描述订单的POJO,返回更新后的订单,标准的返回格式:{\"id\":1,\"name\":\"62LT\",\"amount\":0,\"price\":0.0,\"remark\":\"无\",\"date\":\"2019-10-09T10:23:56.000+0000\",\"status\":0}")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", dataType = "Integer", paramType = "path"),
@ApiImplicitParam(name = "param", dataType = "String", paramType = "query")
})
@PutMapping(value = "/{id}")
public ResponseEntity<ResultModel> updateWantComplete(@PathVariable(value = "id") Integer id, @RequestParam("param") String param) {
Want want = null;
try {
want = mapper.readValue(param, Want.class); // 传入的JSON字符串转为对象
} catch (IOException e) {
return new ResponseEntity<>(ResultModel.error(0), HttpStatus.OK);
}
return new ResponseEntity<>(ResultModel.ok(1, wantService.update(id, want)), HttpStatus.OK);
}
@ApiOperation(value = "部分更新id为{id}的订单", notes = "传入部分描述订单的POJO,返回更新后的订单,标准的返回格式:{\"id\":1,\"name\":\"62LT\",\"amount\":0,\"price\":0.0,\"remark\":\"无\",\"date\":\"2019-10-09T10:23:56.000+0000\",\"status\":0}")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", dataType = "Integer", paramType = "path"),
@ApiImplicitParam(name = "param", dataType = "String", paramType = "query")
})
@PatchMapping(value = "/{id}")
public ResponseEntity<ResultModel> updateWantPart(@PathVariable(value = "id") Integer id, @RequestParam("param") String param) {
Want want = null;
try {
want = mapper.readValue(param, Want.class); // 传入的JSON字符串转为对象
} catch (IOException e) {
return new ResponseEntity<>(ResultModel.error(0), HttpStatus.OK);
}
return new ResponseEntity<>(ResultModel.ok(1, wantService.update(id, want)), HttpStatus.OK);
}
@ApiOperation(value = "删除id为{id}的订单", notes = "无返回")
@ApiImplicitParam(name = "id", dataType = "Integer", paramType = "path")
@DeleteMapping(value = "/{id}")
public ResponseEntity<ResultModel> deleteWant(@PathVariable(value = "id") Integer id) {
wantService.delete(new Want(id));
return new ResponseEntity<>(ResultModel.ok(1), HttpStatus.OK);
}
}
Swagger使用导论
- 启动Spring Boot
- 访问地址http://localhost:8080/swagger-ui.html
- 点开wants-controller,这里就做PATCH的示范把
- 点开 [PATCH /wants/{id} ...]后,点击[Try it out]
- 因为这里是PATCH,所以不需要在param表示的JSON对象中表示所有属性
- 这里先去[GET /wants/{id}]里获取id = 1的表项的数据
在id框的Description中输入1,表示请求{ServiceRoot}/wants/1,点击[Execute]
可以看到,这个请求成功的完成了,响应报文的响应体中,是自定义的响应实体,id为1的订单的JSON对象在实体的content中可以找到。 - 在PATCH中修改它的名字
根据返回格式酌情配置传入后端的JSON对象,点击[Execute]。
可以看到,这个PATCH动作已经成功的完成了,返回了修改后的对应订单的JSON格式对象。 - 为了确定PATCH动作成功,笔者决定再访问一次id为1的订单
与上述不同的是这次笔者决定直接在浏览器地址栏请求,访问http://localhost:8080/wants/1后,浏览器接收到了这些字符串:
{"code":"OK","timestamp":1571212229214,"status":1,"message":"success","content":{"id":1,"name":"62式轻型坦克","amount":0,"price":0.0,"remark":"无","date":"2019-10-09T10:23:56.000+0000","status":0}}
可以在它的content的name中看到,名字已经成功的修改,说明PATCH动作成功完成。
附0:不用Spring MVC的方法传入参数(..., HttpSession session)获取HttpSession的方法
因为使用Swagger测试需要手动指定传入的参数,但是session和model这种东西基本不可能靠手敲来实现,所以需要把他们从方法的参量表中移走,令辟一条路实现他们。
在控制器中使用构造器传入HttpSession对象:
private final HttpSession session;
public XXXController(HttpSession session) {
this.session = session;
}
之后就可以在这个控制器中的任意类访问session了。
附1:不用Spring MVC的方法传入参数(..., Model model)将某个对象放入model中的方法
用注解"@ModelAttribute()",这里直接放代码,自己看吧。
package controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "/user")
@Api("访问用户的API")
public class UserController {
/**
* 访问{ServerRoot}/user/{username},
* 转发至用户详情页,
* 详情页用model传入的用户名在GET {ServerRoot}/users/{username},
* 异步加载用户实体对应JSON对应对象,
* 再由Vue.js绘制至DOM组件
* @param username 地址传入的请求用户名
* @return 需要映射的页面地址到视图解析器
*/
@GetMapping(value = "/{username}")
@ApiOperation(value = "获取用户名为{username}的详细信息")
@ApiImplicitParam(name = "username", dataType = "String", paramType = "path")
public String userInfo(@PathVariable(value = "username") @ModelAttribute(value = "username") String username) {
// 怎么把username传到info页,info页再通过它使用Ajax从UsersController::getUserInfo加载用户信息,model?
return "user/info";
}
}