写在前面 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; 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也是有默认值的,没什么可说的试试就知道了。
Author:
DengBoCong
Permalink:
http://dengbocong.cn/Spring-Boot/e0188dfb231f/
License:
Licensed under the Apache License, Version 2.0 (the "License")
Slogan:
Stay hungry, Stay foolish.