一、前言
前后端分离是当今服务形式的主流,如何设计一个好的 RESTful API ,以及如何让前端小伙伴可以处理标准的 response JSON 数据结构都至关重要,为了让前端有更好的逻辑展示与页面交互处理,每一次 RESTful 请求都应该包含以下几个信息:
名称 | 描述 |
---|---|
status | 状态码,标识请求成功与否,http状态保持一致 |
msg | 错误消息,与错误码相对应,更具体的描述异常信息 |
exception | 异常信息,给出详细的异常栈信息 |
data | 返回结果,通常是 Bean 对象对应的 JSON 数据, 通常为了应对不同返回值类型,将其声明为泛型类型 |
二、实现
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
复制代码
2.定义通用返回类
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@ToString
@Builder
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponses<T> implements Serializable{
private static final long serialVersionUID = -6548932393957418417L;
@Builder.Default
private int status = 200;
private String code;
private String msg;
private String exception;
private T data;
}
复制代码
3.控制器
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.gitee.example.model.User;
import com.gitee.web.api.ApiResponses;
/**
* 接口类
*/
@RestController
@RequestMapping(value = "/users2")
public class UserController2 {
// 创建线程安全的Map
private static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
@GetMapping
public ApiResponses<List<User>> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> r = new ArrayList<>(users.values());
return ApiResponses.<List<User>>builder().data(r).msg("查询列表成功").build();
}
@PostMapping
public ApiResponses<String> postUser(@RequestBody User user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@RequestBody绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return ApiResponses.<String>builder().msg("新增成功").build();
}
@GetMapping("/{id}")
public ApiResponses<User> getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return ApiResponses.<User>builder().data(users.get(id)).msg("查询成功").build();
}
@PutMapping("/{id}")
public ApiResponses<String> putUser(@PathVariable Long id, @RequestBody User user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return ApiResponses.<String>builder().msg("更新成功").build();
}
@DeleteMapping("/{id}")
public ApiResponses<String> deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return ApiResponses.<String>builder().msg("删除成功").build();
}
}
复制代码
4.Mock测试
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import com.gitee.example.model.User;
import com.gitee.web.api.ApiResponses;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTests2 {
@Autowired
private MockMvc mvc;
@Test
public void testUserController2() throws Exception {
// 测试UserController
RequestBuilder request;
// 1、get查一下user列表,应该为空
request = get("/users2/");
MvcResult result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<List<User>> response = JSONUtil.toBean(content, new TypeReference<ApiResponses<List<User>>>() {}, true);
assertThat(response.getData().size(), is(0));
assertThat(response.getMsg(), is("查询列表成功"));
// 2、post提交一个user
User puser = new User();
puser.setId(1L);
puser.setName("测试大师");
puser.setAge(20);
request = post("/users2/")
.contentType(MediaType.APPLICATION_JSON)
.content(JSONUtil.toJsonStr(puser));
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<String> response1 = JSONUtil.toBean(content, new TypeReference<ApiResponses<String>>() {}, true);
assertThat(response1.getMsg(), is("新增成功"));
// 3、get获取user列表,应该有刚才插入的数据
request = get("/users2/");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<List<User>> response2 = JSONUtil.toBean(content, new TypeReference<ApiResponses<List<User>>>() {}, true);
assertThat(response2.getData().size(), is(1));
assertThat(response2.getMsg(), is("查询列表成功"));
// 4、put修改id为1的user
puser.setName("测试终极大师");
puser.setAge(30);
request = put("/users2/1")
.contentType(MediaType.APPLICATION_JSON)
.content(JSONUtil.toJsonStr(puser));
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<String> response3 = JSONUtil.toBean(content, new TypeReference<ApiResponses<String>>() {}, true);
assertThat(response3.getMsg(), is("更新成功"));
// 5、get一个id为1的user
request = get("/users2/1");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<User> response4 = JSONUtil.toBean(content, new TypeReference<ApiResponses<User>>() {},true);
User user = response4.getData();
assertThat(user.getId(), is(1L));
assertThat(user.getName(), is("测试终极大师"));
// 6、del删除id为1的user
request = delete("/users2/1");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<String> response5 = JSONUtil.toBean(content, new TypeReference<ApiResponses<String>>() {}, true);
assertThat(response5.getMsg(), is("删除成功"));
// 7、get查一下user列表,应该为空
request = get("/users2/");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ApiResponses<List<User>> response6 = JSONUtil.toBean(content, new TypeReference<ApiResponses<List<User>>>() {}, true);
assertThat(response6.getData().size(), is(0));
assertThat(response6.getMsg(), is("查询列表成功"));
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END