相关文章推荐
打篮球的自行车  ·  氧气瓶_百度百科·  5 月前    · 
含蓄的弓箭  ·  ONE 原作、あずま ...·  1 年前    · 
非常酷的红薯  ·  驱魔人 - 知乎·  1 年前    · 

API接口文档管理利器-swagger

随着前后端分离的开发模式越来越流行,前端与后端分别交给不同的人员开发,能很大程度的提高开发效率,但是项目开发中的沟通成本也随之升高,这部分沟通成本主要在于前端开发人员与后端开发人员对API接口的沟通。

在我这几年的开发经历中,最早和前端沟通API接口是通过word文档来完成的,这种方式及其低效,由于API接口不可能一开始就能设计得很完善,每次改动都要重新通知并发送word文档给相关关系人,及其麻烦。其次,也没法提供mock数据给到前端。

后面我接触到别的新项目,API接口沟通使用了rap2,rap2是阿里妈妈前端团队开发的API管理平台,能给我们提供方便的接口文档管理、Mock、导出等功能,极大方便了API接口的沟通,同时,其提供的mock功能,能让前端直接调用得到mock数据,从而避免了前端需要自己造数据的问题,提高了前端的开发效率。这个工具我认为其唯一的缺点就是文档和代码是分开维护的,当后端需要开发接口时,每次先在rap2上定义好,再编写代码,同时要保持二者的一致性,接口少还好,接口多的话这个过程及其繁琐,我是深有体会。其次,在后期版本快速迭代的过程中,修改接口实现的时候都必须同步修改rap2接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致写出的代码与接口文档不一致现象。

最后,为了避免上述问题,我们开始采用一个新的API管理工具,那就是swagger,swagger 给我们提供了一个全新的维护 API 文档的方式,下面我们就来了解一下它的优点:

  1. swagger 可以根据代码自动生成 API 文档,很好的保证了文档的时效性,做到了代码变,文档变;
  2. swagger UI 呈现出来的是一份可交互式的 API 文档,我们可以直接在文档页面尝试 API 的调用,省去了准备复杂的调用参数的过程;
  3. 可以将swagger文档规范导入相关的工具(例如 Postman、AgileTC), 这些工具将会为我们自动地创建自动化测试。

以SpringBoot项目为例,为了引入swagger,需要加入如下依赖,鉴于swagger官方的UI界面操作不够友好,这里引入的是另一个UI界面:

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

新建一个swagger配置类:

/**
 * @author yudong
 * @date 2022/1/4
@EnableSwagger2
@Configuration
public class SwaggerConfig {
    @Bean
    public Docket docket() {
        List<RequestParameter> params = new ArrayList<>();
        RequestParameter param = new RequestParameterBuilder()
                // 全局header参数
                .name("Authorization")
                .description("访问授权码头字段")
                .required(true)
                .in(ParameterType.HEADER)
                .build();
        params.add(param);
        return new Docket(DocumentationType.SWAGGER_2)
                // 启用swagger
                .enable(true)
                .select()
                // Controller类需要有Api注解才能生成接口文档
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                // Controller方法需要有ApiOperation注解才能生成接口文档
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 路径使用any风格
                .paths(PathSelectors.any())
                .build()
                .globalRequestParameters(params)
                .consumes(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
                .produces(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
                // 接口文档的基本信息
                .apiInfo(apiInfo());
    @SneakyThrows
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("业务中台")
                .description("这是项目描述")
                .contact(new Contact("liangyudong", "https://www.zhihu.com/people/liang-yu-dong-44/posts",
                        "375709770@qq.com"))
                .version("1.0.0")
                .build();
}

swagger 注解使用说明

@Api :Controller类的说明 ,说明类的作用;

@ApiOperation:Controller类方法的说明,说明方法的作用;

@ApiModel:用于JavaBean上,提供额外信息;

@ApiModelProperty:用于JavaBean的字段上,提供字段说明;

@ApiParam:用在方法参数上显式提供参数说明;

还有更多注解不一一赘述

以一个实际例子说明用法:

/**
 * @author yudong
 * @date 2022/1/4
@Slf4j
@Api(tags = "用户相关")
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private LoginService loginService;
    @ApiOperation("01-登录")
    @PostMapping("/login")
    public Result<User> login(@RequestBody @Validated UserReqVo userReqVo) {
        return Result.success(loginService.login(userReqVo));
    @ApiOperation("02-批量删除用户")
    @PostMapping("/batchDeleteUser")
    public Result<List<Integer>> batchDeleteUser(@RequestBody @Size(min = 1)
                                                 @ApiParam(required = true, allowMultiple = true,
                                                         examples = @Example(@ExampleProperty(mediaType = "userIdList", value = "[1,2]")))
                                                         List<Integer> userIdList) {
        return Result.success(userIdList);
 * @author yudong
 * @date 2022/1/4
@Data
@ApiModel
public class UserReqVo {
    @NotBlank
    @ApiModelProperty(value = "用户名", example = "admin", required = true)
    private String username;
    @NotBlank
    @ApiModelProperty(value = "密码", example = "admin", required = true)
    private String password;
}

本地启动项目后访问文档地址 localhost:8964/doc.html# ,生成的文档是这样的:

这里可以直接调试接口:

这就是swagger的基本用法。

下面我们来探究一些深入用法,目前swagger并不支持对方法的展示顺序进行排序,但很多时候对于Controller里的大量方法我们会希望按照特定的顺序展示,例如按照序号,为此,我们可以替换掉swagger的ServiceModelToSwagger2MapperImpl的实现:

/**
 * 按ApiOperation注解的value值进行排序
 * @author yudong
 * @date 2022/1/4
@Component
@Primary
@SuppressWarnings("all")
public class ServiceModelToSwagger2MapperExtImpl extends ServiceModelToSwagger2MapperImpl {
    @Override
    protected Map<String, Path> mapApiListings(Map<String, List<ApiListing>> apiListings) {
        Map<String, Path> paths = new LinkedHashMap<>();
        apiListings.values().stream()
                .flatMap(Collection::stream)
                .forEachOrdered(each -> {
                    List<ApiDescription> apis = each.getApis();
                    apis.sort((a1, a2) -> {
                        return a1.getOperations().get(0).getSummary().compareTo(a2.getOperations().get(0).getSummary());
                    for (ApiDescription api : apis) {
                        paths.put(
                                api.getPath(),
                                mapOperations(api, ofNullable(paths.get(api.getPath())), each.getModelNamesRegistry())
        return paths;
    private Path mapOperations(
            ApiDescription api,
            Optional<Path> existingPath,
            ModelNamesRegistry modelNamesRegistry) {
        Path path = existingPath.orElse(new Path());
        for (springfox.documentation.service.Operation each : nullToEmptyList(api.getOperations())) {
            Operation operation = mapOperation(each, modelNamesRegistry);
            path.set(each.getMethod().toString().toLowerCase(), operation);
        return path;
}

加入这个类后,就实现了排序效果:

另外,对于一些特殊类型的字段,我们需要定制swagger的展示类型,例如Date类型,swagger默认显示成

但是,Date类型我们通常会序列为long返回,所以我们有两种方案解决这个问题:

一是在@ApiModelProperty注解显式指明dataType为java.lang.Long,如下:

    @ApiModelProperty(value = "出生日期", dataType = "java.lang.Long")
    private Date birthday;

这种方式的缺点是对于每一个Date类型的字段都要这样做,经常会被人遗忘。

第二种是实现ModelPropertyBuilderPlugin接口,介入到swagger中,实现全局修改类型,避免了方式一的缺点(swagger提供了很多的插件接口,用于修改方方面面的swagger数据,大家有兴趣可以去看swagger源码研究):

/**
 * 全局修改swagger,所有Date类型显示为long
 * @author yudong
 * @date 2022/1/4
@Component
public class CustomerSwaggerProperty implements ModelPropertyBuilderPlugin {
    @Override
    public void apply(ModelPropertyContext modelPropertyContext) {
        if (!modelPropertyContext.getBeanPropertyDefinition().isPresent()) {
            return;
        BeanPropertyDefinition beanPropertyDefinition = modelPropertyContext.getBeanPropertyDefinition().get();
        PropertySpecificationBuilder builder = modelPropertyContext.getSpecificationBuilder();
        if (beanPropertyDefinition.getRawPrimaryType() == Date.class) {
            builder.type(new ModelSpecificationBuilder().scalarModel(ScalarType.LONG).build());
    @Override
    public boolean supports(DocumentationType delimiter) {
        return delimiter == DocumentationType.SWAGGER_2;
}

所以,对于这样一个定义的JavaBean,在swagger中展示是这样的:

/**
 * @author yudong
 * @date 2022/1/4
@Data
@ApiModel
public class User {
    @ApiModelProperty(value = "用户id", example = "10000001")
    private String userId;
    @ApiModelProperty(value = "用户名", example = "admin")
    private String username;
    @ApiModelProperty(value = "用户描述", example = "超级管理员")
    private String desc;
    @ApiModelProperty(value = "编号", example = "10001")
    private Long id;