写在前面

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 让部署管理和使用功能强大的API从未如此简单,非常好用哦。不过这里要提醒一下的是,项目发布生产环境的时候,记得关闭swagger,以防泄漏项目接口文档,被攻击。如果觉得写得挺好,请给点个关注和点个赞哦,嘿嘿嘿

准备

首先第一步要做的当然是创建一个只有web场景的新项目,然后引入swagger的依赖,这里的swagger-ui提供了一个非常好看的可视化UI,帮助我们整理Controller的API,从而生成API文档。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

配置

我们导入了swagger的依赖之后,当然是需要对swagger进行相关的配置,我们在Config包下创建一个SwaggerConfig配置类,用它来对swagger进行相关的配置,配置类的具体内容如下。当然,你还可对对固定接口进行过滤,不过我这里就不演示那么复杂了,就举个简单的例子。

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
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.dbc.ubiquity.Controller"))
.paths(PathSelectors.any())
.build();
}


private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Ubiquity系统")
.description("Ubiquity系统 API 1.0.0")
.termsOfServiceUrl("填一个你的借口文档的地址,或者其他地址都可以")
.version("1.0")
.contact(new Contact("DengBoCong", "个人网站地址", "你的邮箱"))
.build();
}
}

经过这2步配置后,我们启动服务后,访问:http://localhost:8080/swagger-ui.html就完成了集成。在上面的`basePackage("com.dbc.ubiquity.Controller")`,我们配置了Swagger会默认把所有Controller中的RequestMapping方法都生成API出来。

常用注解

  • @Api:修饰整个类,描述Controller的作用

  • @ApiOperation:描述一个类的一个方法,或者说一个接口

  • @ApiParam:单个参数描述

  • @ApiModel:用对象来接收参数

  • @ApiProperty:用对象接收参数时,描述对象的一个字段

  • @ApiResponse:HTTP响应其中1个描述

  • @ApiResponses:HTTP响应整体描述

  • @ApiIgnore:使用该注解忽略这个API

  • @ApiClass

  • @ApiError

  • @ApiErrors

  • @ApiParamImplicit

  • @ApiParamsImplicit

写Controller测试

我们知道上面的一些注解的含义之后呢,我们现在可以来写Controller来测试一下,不过这里要说明一下的是,我们没有集成数据库,所以我这里使用模拟数据,Spring已经内含了模拟数据类,即MockMvc,首先我们先写两个实体类(这个实体类当然不能使用@Entity 来注释啦),具体内容如下:

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
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;

import java.util.Date;

public class Message {
private Long id;
@ApiModelProperty(value = "消息体")
private String text;
@ApiModelProperty(value = "消息总结")
private String summary;
private Date createDate;

public Long getId() {
return id;
}

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

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public String getSummary() {
return summary;
}

public void setSummary(String summary) {
this.summary = summary;
}

public Date getCreateDate() {
return createDate;
}

public void setCreateDate(Date createDate) {
this.createDate = createDate;
}

@Override
public String toString() {
return "Message{" +
"id=" + id +
", text='" + text + '\'' +
", summary='" + summary + '\'' +
", createDate=" + createDate +
'}';
}
}

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
public class User {
private Long id;
private String name;
private int age;

public Long getId() {
return id;
}

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

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

然后是Repository类,具体内容如下

1
2
3
4
5
6
7
8
9
10
import java.util.List;

public interface MessageRepository {
List<Message> findAll();
Message save(Message message);
Message update(Message message);
Message updateText(Message message);
Message findMessage(Long id);
void deleteMessage(Long id);
}
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
@Service("messageRepository")
public class InMemoryMessageRepository implements MessageRepository {
private static AtomicLong counter = new AtomicLong();
private final ConcurrentMap<Long, Message> messages = new ConcurrentHashMap<>();

@Override
public List<Message> findAll() {
List<Message> messages = new ArrayList<>(this.messages.values());
return messages;
}

@Override
public Message save(Message message) {
Long id = message.getId();
if (id == null){
id = counter.incrementAndGet();
message.setId(id);
}
this.messages.put(id, message);
return message;
}

@Override
public Message update(Message message) {
this.messages.put(message.getId(), message);
return message;
}

@Override
public Message updateText(Message message) {
Message msg = this.messages.get(message.getId());
msg.setText(message.getText());
this.messages.put(msg.getId(), msg);
return msg;
}

@Override
public Message findMessage(Long id) {
return this.messages.get(id);
}

@Override
public void deleteMessage(Long id) {
this.messages.remove(id);
}
}

然后我们编写一个对各个实体类进行相应格式的基础类,具体内容如下

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
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/*
* 通用相应对象
* */
@ApiModel(description = "响应对象")
public class BaseResult<T> {
private static final int SUCCESS_CODE = 0;
private static final String SUCCESS_MESSAGE = "成功";

@ApiModelProperty(value = "响应码", name = "code", required = true, example = "" + SUCCESS_CODE)
private int code;

@ApiModelProperty(value = "响应消息", name = "msg", required = true, example = SUCCESS_MESSAGE)
private String msg;

@ApiModelProperty(value = "响应数据", name = "data")
private T data;

private BaseResult(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}

private BaseResult() {
this(SUCCESS_CODE, SUCCESS_MESSAGE);
}

private BaseResult(int code, String msg) {
this(code, msg, null);
}

private BaseResult(T data) {
this(SUCCESS_CODE, SUCCESS_MESSAGE, data);
}

public static <T> BaseResult<T> success() {
return new BaseResult<>();
}

public static <T> BaseResult<T> successWithData(T data) {
return new BaseResult<>(data);
}

public static <T> BaseResult<T> failWithCodeAndMsg(int code, String msg) {
return new BaseResult<>(code, msg, null);
}

public static <T> BaseResult<T> buildWithParam(ResponseParam param) {
return new BaseResult<>(param.getCode(), param.getMsg(), null);
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public static class ResponseParam {
private int code;
private String msg;

private ResponseParam(int code, String msg) {
this.code = code;
this.msg = msg;
}

public static ResponseParam buildParam(int code, String msg) {
return new ResponseParam(code, msg);
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public int getCode() {
return code;
}
}
}

到这里,我们所有的工作包括配置和逻辑都已经完成了,接下来就是测试我们编写的代码是否会生效了,测试类的具体内容如下:

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
import org.aspectj.lang.annotation.Before;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
public class MessageControllerTest {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

// @Before(value = "saveMessage()")
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
saveMessages();
}

@Test
public void saveMessage() throws Exception {
setup();
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("text", "text");
params.add("summary", "summary");
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.post("/message")
.params(params)).andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

@Test
public void getAllMessages() throws Exception {
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/messages"))
.andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

@Test
public void getMessage() throws Exception {
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/message/6"))
.andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

@Test
public void modifyMessage() throws Exception {
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", "6");
params.add("text", "text");
params.add("summary", "summary");
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.put("/message").params(params))
.andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

@Test
public void patchMessage() throws Exception {
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", "6");
params.add("text", "text");
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.patch("/message/text").params(params))
.andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

@Test
public void deleteMessage() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete("/message/6"))
.andReturn();
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/messages"))
.andReturn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
}

private void saveMessages() {
for (int i=1;i<10;i++){
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id",""+i);
params.add("text", "text"+i);
params.add("summary", "summary"+i);
try {
MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.post("/message")
.params(params)).andReturn();
} catch (Exception e) {
e.printStackTrace();
}
}
}

}

接下来我们访问http://localhost:8080/swagger-ui.html
在这里插入图片描述

最后

Swagger2默认将所有的Controller中的RequestMapping方法都会暴露,然而在实际开发中,我们并不一定需要把所有API都提现在文档中查看,这种情况下,使用注解@ApiIgnore来解决,如果应用在Controller范围上,则当前Controller中的所有方法都会被忽略,如果应用在方法上,则对应用的方法忽略暴露API。注解@ApiOperation和@ApiParam可以理解为API说明,多动手尝试就很容易理解了。如果我们不使用这样注解进行说明,Swagger2也是有默认值的,没什么可说的试试就知道了。