在线接口文档

在开发中,如果遇到后端项目开发完毕,但是前端项目还未开发完成,但又需要对后端接口进行测试

先前的解决方式是通过第三方接口测试软件,如postmanapifox等进行测试

但这类测试工具需要清楚的知道接口的路径、请求方式、请求参数,且还需要手动填写对应内容

当与前端对接时,后端还需要提供详细的接口文档,当接口文档编写的不及时或者更新不及时,就会造成信息闭塞,造成不必要的效率降低

Swagger简介

Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务

官网:https://swagger.io

它的主要作用:

  • 使得前后端分离开发更加方便,有利于团队协作
  • 在线自动生成接口文档,降低后端开发人员编写接口文档的负担
  • 功能测试

Spring已经将Swagger纳入自身的标准,建立了Spring-Swagger项目,现在加Springfox。通过在项目中引入Springfox,就可以非常简单快捷的使用Swagger啦

项目中集成

目前若依框架已经集成了Swagger,核心配置有如下几个:

  1. 关键依赖:

f821521a-00a3-401b-8952-3d35ce933db8

  1. 核心配置类:
    • 位置:com.zzyl.web.core.config.SwaggerConfig
    • 请设计prompt提示词,来阅读以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.zzyl.web.core.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.zzyl.common.config.RuoYiConfig;
import io.Swagger.annotations.ApiOperation;
import io.Swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

/**
* Swagger2的接口配置
*
* @author ruoyi
*/
@Configuration
public class SwaggerConfig
{
/** 系统基础配置 */
@Autowired
private RuoYiConfig ruoyiConfig;

/** 是否开启Swagger */
@Value("${Swagger.enabled}")
private boolean enabled;

/** 设置请求的统一前缀 */
@Value("${Swagger.pathMapping}")
private String pathMapping;

/**
* 创建API
*/
@Bean
public Docket createRestApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的Swagger注解
// .apis(RequestHandlerSelectors.basePackage("com.zzyl.project.tool.Swagger"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
/* 设置安全模式,Swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(pathMapping);
}

/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}

/**
* 安全上下文
*/
private List<SecurityContext> securityContexts()
{
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}

/**
* 默认的安全上引用
*/
private 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()
{
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact(ruoyiConfig.getName(), null, null))
// 版本
.version("版本号:" + ruoyiConfig.getVersion())
.build();
}
}
  1. 访问地址

    • 方式一:系统内部访问

    955e7e76-5d79-4bdf-8bcd-f6386936f3e3

83962e69-c269-4b2c-a85a-5211c687accf

集成knife4j

如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j,对比swagger相比有以下优势:

  1. 友好界面
  2. 离线文档
  3. 接口排序
  4. 安全控制
  5. 在线调试
  6. 文档清晰
  7. 注解增强
  8. 容易上手。

目前,企业项目中一般都使用knife4j框架

若依文档中有相关介绍:插件集成 | RuoYi

实现后的效果:

18161d6d-6f97-4ca6-801e-c8af0ce02e82

  • 可以下载离线文档:

7e774ea9-8c55-4bcb-b3b2-35534ddb724e

Swagger使用

常见注解

通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

注解 说明
@Api 用在Controller类上,描述Controller类的作用
@ApiOperation 用在Controller类中的方法上,说明方法的用途、作用
@ApiParam 用在方法的参数上,描述单个形参的含义,适用于简单场景
@ApiImplicitParam 用在Controller类中的方法上方,描述单个形参的含义,适用于参数复杂或者需要详细描述参数的场景
@ApiModel 用在实体类上,描述用来接收参数或者响应结果的实体类的含义
@ApiModelProperty 用在实体类的属性上,用来描述实体类中属性的含义

请求体参数

一般的形参,如:id,status等这些,在controller的方法可以使用@ApiParam或者@ApiImplicitParam注解进行描述

如果接口是post、put请求,可能接收的是一个对象作为请求体参数,上面两个注解并不能很好的描述对象中的每一个属性,这个时候需要使用新的注解:@ApiModel@ApiModelProperty

比如,在新增护理项目的时候,接收的参数为NursingProject实体类,咱们可以用上面两个注解在实体类中描述类和属性的含义

如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@ApiModel(value = "NursingProject" , description = "护理项目对象")
public class NursingProject extends BaseEntity
{
private static final long serialVersionUID = 1L;

/** 编号 */
@ApiModelProperty("编号")
private Long id;

/** 名称 */
@ApiModelProperty("名称")
@Excel(name = "名称")
private String name;

/** 排序号 */
@ApiModelProperty("排序号")
@Excel(name = "排序号")
private Integer orderNo;

/** 单位 */
@ApiModelProperty("单位")
@Excel(name = "单位")
private String unit;

/** 价格 */
@ApiModelProperty("价格")
@Excel(name = "价格")
private BigDecimal price;

/** 图片 */
@ApiModelProperty("图片")
@Excel(name = "图片")
private String image;

/** 护理要求 */
@ApiModelProperty("护理要求")
@Excel(name = "护理要求")
private String nursingRequirement;

/** 状态(0:禁用,1:启用) */
@ApiModelProperty("状态(0:禁用,1:启用)")
@Excel(name = "状态", readConverterExp = "0=:禁用,1:启用")
private Integer status;

public void setId(Long id)
{
this.id = id;
}

public Long getId()
{
return id;
}

public void setName(String name)
{
this.name = name;
}

public String getName()
{
return name;
}

public void setOrderNo(Integer orderNo)
{
this.orderNo = orderNo;
}

public Integer getOrderNo()
{
return orderNo;
}

public void setUnit(String unit)
{
this.unit = unit;
}

public String getUnit()
{
return unit;
}

public void setPrice(BigDecimal price)
{
this.price = price;
}

public BigDecimal getPrice()
{
return price;
}

public void setImage(String image)
{
this.image = image;
}

public String getImage()
{
return image;
}

public void setNursingRequirement(String nursingRequirement)
{
this.nursingRequirement = nursingRequirement;
}

public String getNursingRequirement()
{
return nursingRequirement;
}

public void setStatus(Integer status)
{
this.status = status;
}

public Integer getStatus()
{
return status;
}

@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("name", getName())
.append("orderNo", getOrderNo())
.append("unit", getUnit())
.append("price", getPrice())
.append("image", getImage())
.append("nursingRequirement", getNursingRequirement())
.append("status", getStatus())
.append("createBy", getCreateBy())
.append("updateBy", getUpdateBy())
.append("remark", getRemark())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.toString();
}
}

由于这个类继承了BaseEntity,还需要使用上面两个注解对BaseEntity进行描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@ApiModel(value = "BaseEntity",description = "Entity基类")
public class BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;

/** 搜索值 */
@JsonIgnore
private String searchValue;

/** 创建者 */
@ApiModelProperty(value = "创建者")
private String createBy;

/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间")
private Date createTime;

/** 更新者 */
@ApiModelProperty(value = "更新者")
private String updateBy;

/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "更新时间")
private Date updateTime;

/** 备注 */
@ApiModelProperty(value = "备注")
private String remark;

/** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModelProperty(value = "请求参数")
private Map<String, Object> params;

}

响应体数据

当查询护理项目的时候,并不能展示详细的返回结果,如下图

8eb88da6-842a-4b4a-9eee-69ddaf39f566

想要解决这个问题,有两步操作

  • 使用@ApiModel和@ApiModelProperty注解对返回值的类型进行说明(已完成)
  • 需要在controller类的方法上指定返回值类型
    • 返回一个对象
    • 返回数据列表(分页或不分页)

返回一个对象

  • 在目前的代码中,返回一个对象的接口代码,是根据id查询对应的对象,并返回

  • 详细代码如下:

1
2
3
4
5
6
7
@ApiOperation("获取护理项目详细信息")
@PreAuthorize("@ss.hasPermi('nursing:nursingProject:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@ApiParam("护理项目ID") @PathVariable("id") Long id)
{
return success(nursingProjectService.selectNursingProjectById(id));
}

这个代码并不能在Swagger中展示详细的返回结果说明,因为目前的返回值是一个AjaxResult,并没有指定泛型

咱们需要使用若依框架中提供的另外一个返回值类型:

07b23398-4964-4196-bf85-445f9bfe580c

在R这个类中也提供了详细的返回值的方法,有成功的,有失败的等等

修改NursingProjectController类中的方法返回值:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取护理项目详细信息
*/
@PreAuthorize("@ss.hasPermi('nursing:project:query')")
@GetMapping(value = "/{id}")
@ApiOperation(value = "获取护理项目详细信息", notes = "根据ID获取护理项目详细信息")
@ApiImplicitParam(name = "id", value = "护理项目ID", required = true, dataType = "Long", paramType = "path")
public R<NursingProject> getInfo(@PathVariable("id") Long id)
{
NursingProject nursingProject = nursingProjectService.selectNursingProjectById(id);
return R.ok(nursingProject);
}

最终的效果如下:

882c7ed8-a15f-42f7-b596-5616b9ac0e5d

返回分页对象

目前的分页列表查询的返回值是TableDataInfo这个类,咱们需要改造下这个类,让它具备接收泛型的能力

  • 定义类中的泛型T
  • 把类中的所有问号改为T
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 表格分页数据对象
*
* @author alexis
*/
public class TableDataInfo<T> implements Serializable
{
private static final long serialVersionUID = 1L;

/** 总记录数 */
private long total;

/** 列表数据 */
private List<T> rows;

/** 消息状态码 */
private int code;

/** 消息内容 */
private String msg;

/**
* 表格数据对象
*/
public TableDataInfo()
{
}

/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<T> list, int total)
{
this.rows = list;
this.total = total;
}


public List<T> getRows()
{
return rows;
}

public void setRows(List<T> rows)
{
this.rows = rows;
}

//其他代码略...

}

修改controller中分页查询的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 查询护理项目列表
*/
@PreAuthorize("@ss.hasPermi('nursing:project:list')")
@GetMapping("/list")
@ApiOperation(value = "查询护理项目列表", notes = "根据护理项目条件查询护理项目列表")
public TableDataInfo<List<NursingProject>> list(
@ApiParam(value = "护理项目查询条件", required = false) NursingProject nursingProject)
{
startPage();
List<NursingProject> list = nursingProjectService.selectNursingProjectList(nursingProject);
return getDataTable(list);
}

重启服务,查看分页接口的返回值效果:

be0e167f-b271-4ed5-b7b6-566e339b4f09