基于springboot创建RESTful风格接口
RESTful API风格
restfulAPI.png
特点:URL描述资源
使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果
使用json交互数据
RESTful只是一种风格,并不是强制的标准
REST成熟度模型.png
一、查询请求
1.编写单元测试@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
//查询
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("username", "jojo")
.param("age", "18")
.param("ageTo", "60")
.param("xxx", "yyy")// .param("size", "15")// .param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回
System.out.println(result);//[{"username":null},{"username":null},{"username":null}]
}
}
2.使用注解声明RestfulAPI
常用注解@RestController 标明此Controller提供RestAPI@RequestMapping及其变体。映射http请求url到java方法@RequestParam 映射请求参数到java方法的参数@PageableDefault 指定分页参数默认值@PathVariable 映射url片段到java方法的参数
在url声明中使用正则表达式@JsonView控制json输出内容
查询请求:@RestControllerpublic class UserController {
@RequestMapping(value="/user",method=RequestMethod.GET) public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User()); return users;
}
}
①当前端传递的参数和后台自己定义的参数不一致时,可以使用name属性来标记:(@RequestParam(name="username",required=false,defaultValue="hcx") String nickname
②前端不传参数时,使用默认值 defaultValue="hcx"
③当查询参数很多时,可以使用对象接收
④使用Pageable作为参数接收,前台可以传递分页相关参数
pageSize,pageNumber,sort;
也可以使用@PageableDefault指定默认的参数值。@PageableDefault(page=2,size=17,sort="username,asc")//查询第二页,查询17条,按照用户名升序排列
3.jsonPath表达式书写
jsonpath表达式.png
打包发布maven项目.png
二、编写用户详情服务
@PathVariable 映射url片段到java方法的参数
在url声明中使用正则表达式
@JsonView控制json输出内容
单元测试:@RunWith(SpringRunner.class)@SpringBootTestpublic class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
//获取用户详情
@Test
public void whenGetInfoSuccess() throws Exception {
String result = mockMvc.perform(get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result); //{"username":"tom","password":null}
}
后台代码:@RestController@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了public class UserController { @RequestMapping(value="/user/{id}",method=RequestMethod.GET) public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom"); return user;
}
当希望对传递进来的参数作一些限制时,可以使用正则表达式://测试提交错误信息@Testpublic void whenGetInfoFail() throws Exception {
mockMvc.perform(get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is4xxClientError());
}
后台代码:@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,使用正则表达式@JsonView(User.UserDetailView.class)public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom"); return user;
}
使用@JsonView控制json输出内容
1.场景:在以上两个方法中,查询集合和查询用户详细信息时,期望查询用户集合时不返回密码给前端,而在查询单个用户信息时才返回。
2.使用步骤:
①使用接口来声明多个视图
②在值对象的get方法上指定视图
③在Controller方法上指定视图
在user实体中操作:package com.hcx.web.dto;import java.util.Date;import javax.validation.constraints.Past;import org.hibernate.validator.constraints.NotBlank;import com.fasterxml.jackson.annotation.JsonView;import com.hcx.validator.MyConstraint;public class User {
public interface UserSimpleView{}; //有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来
public interface UserDetailView extends UserSimpleView{};
@MyConstraint(message="这是一个测试") private String username;
@NotBlank(message = "密码不能为空") private String password;
private String id;
@Past(message="生日必须是过去的时间") private Date birthday;
@JsonView(UserSimpleView.class) public Date getBirthday() { return birthday;
} public void setBirthday(Date birthday) { this.birthday = birthday;
} @JsonView(UserSimpleView.class) public String getId() { return id;
} public void setId(String id) { this.id = id;
} @JsonView(UserSimpleView.class) //在简单视图上展示该字段
public String getUsername() { return username;
} public void setUsername(String username) { this.username = username;
} @JsonView(UserDetailView.class) public String getPassword() { return password;
} public void setPassword(String password) { this.password = password;
}
}
在具体的方法中操作Controller:@GetMapping@JsonView(User.UserSimpleView.class)public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User()); return users;
}@RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)//如果希望对传递进来的参数作一些限制,就需要使用正则表达式@JsonView(User.UserDetailView.class)public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom"); return user;
}
单元测试://查询@Testpublic void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("username", "jojo")
.param("age", "18")
.param("ageTo", "60")
.param("xxx", "yyy") //.param("size", "15")
//.param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();//将服务器返回的json字符串当成变量返回
System.out.println(result);//[{"username":null},{"username":null},{"username":null}]}//获取用户详情@Testpublic void whenGetInfoSuccess() throws Exception {
String result = mockMvc.perform(get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result); //{"username":"tom","password":null}
}
代码重构:
1.@RequestMapping(value="/user",method=RequestMethod.GET)
替换成:
@GetMapping("/user")
2.在每个url中都重复声明了/user,此时就可以提到类中声明@RestController@RequestMapping("/user")//在类上声明了/user,在方法中就可以省略了public class UserController {
@GetMapping
@JsonView(User.UserSimpleView.class) public List query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
System.out.println(username);
List users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User()); return users;
}
@GetMapping("/{id:\\d+}") @JsonView(User.UserDetailView.class) public User getInfo(@PathVariable String id) {
User user = new User();
user.setUsername("tom"); return user;
}
}
三、处理创建请求
1.@RequestBody 映射请求体到java方法的参数
单元测试:@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());//1524741370816
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}
}
Controller:要使用@RequestBody才可以接收前端传递过来的参数@PostMappingpublic User create(@RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
user.setId("1"); return user;
}
2.日期类型参数的处理
对于日期的处理应该交给前端或app端,所以统一使用时间戳
前端或app端拿到时间戳,由他们自己决定转换成什么格式,而不是由后端转好直接给前端。
前端传递给后台直接传时间戳:@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());//1524741370816
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}(后台返回的时间戳)
}
Controller:@PostMappingpublic User create(@RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018(Date类型)
user.setId("1"); return user;
}
3.@Valid注解和BindingResult验证请求参数的合法性并处理校验结果
1.hibernate.validator中的常用验证注解:
注解及其含义.png
①在实体中添加相应验证注解:@NotBlankprivate String password;
②后台接收参数时加@Valid注解@PostMappingpublic User create(@Valid @RequestBody User user) {
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1"); return user;
}
2.BindingResult:带着错误信息进入方法体@PostMapping
public User create(@Valid @RequestBody User user,BindingResult errors) {
if(errors.hasErrors()) { //有错误返回true
errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage())); //may not be empty
}
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1"); return user;
}
四、处理用户信息修改
1.自定义消息@Test
public void whenUpdateSuccess() throws Exception { //一年之后的时间
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
System.out.println(date.getTime());//1524741370816
String content = "{\"id\":\"1\",\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);//{"username":"tom","password":null,"id":"1","birthday":1524741229875}
}
Controller:@PutMapping("/{id:\\d+}")public User update(@Valid @RequestBody User user,BindingResult errors) {
/*if(errors.hasErrors()) {
//有错误返回true
errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
//may not be empty
}*/
if(errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> { //FieldError fieldError = (FieldError)error;
//String message = fieldError.getField()+" "+error.getDefaultMessage();
System.out.println(error.getDefaultMessage());
//密码不能为空
//生日必须是过去的时间
//birthday must be in the past
//password may not be empty
}
);
}
System.out.println(user.getId()); //null
System.out.println(user.getUsername()); //tom
System.out.println(user.getPassword());//null
System.out.println(user.getBirthday());//Thu Apr 26 19:13:49 CST 2018
user.setId("1"); return user;
}
实体:public class User {
public interface UserSimpleView{}; //有了该继承关系,在显示detail视图的时候同时会把simple视图的所有字段也显示出来
public interface UserDetailView extends UserSimpleView{};
@MyConstraint(message="这是一个测试") private String username;
@NotBlank(message = "密码不能为空") private String password;
private String id;
@Past(message="生日必须是过去的时间") private Date birthday;
@JsonView(UserSimpleView.class) public Date getBirthday() { return birthday;
} public void setBirthday(Date birthday) { this.birthday = birthday;
} @JsonView(UserSimpleView.class) public String getId() { return id;
} public void setId(String id) { this.id = id;
} @JsonView(UserSimpleView.class) //在简单视图上展示该字段
public String getUsername() { return username;
} public void setUsername(String username) { this.username = username;
} @JsonView(UserDetailView.class) public String getPassword() { return password;
} public void setPassword(String password) { this.password = password;
}
}
2.自定义校验注解
创建一个注解MyConstraint:@Target({ElementType.METHOD,ElementType.FIELD})//可以标注在方法和字段上@Retention(RetentionPolicy.RUNTIME)//运行时注解@Constraint(validatedBy = MyConstraintValidator.class)//validatedBy :当前的注解需要使用什么类去校验,即校验逻辑public @interface MyConstraint {
String message();//校验不通过要发送的信息
Class>[] groups() default { };
Class extends Payload>[] payload() default { };
}
校验类:MyConstraintValidator:public class MyConstraintValidator implements ConstraintValidator { /*ConstraintValidator
参数一:验证的注解
参数二:验证的类型
ConstraintValidator 当前注解只能放在String类型字段上才会起作用
*/
@Autowired
private HelloService helloService;
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println("my validator init");
} @Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
helloService.greeting("tom");
System.out.println(value); return false;//false:校验失败;true:校验成功
}
}
五、处理删除
单元测试:@Testpublic void whenDeleteSuccess() throws Exception {
mockMvc.perform(delete("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
Controller:@DeleteMapping("/{id:\\d+}")public void delete(@PathVariable String id) { System.out.println(id);
}
六、RESTful API错误处理
1.Spring Boot中默认的错误处理机制
Spring Boot中默认的错误处理机制,
对于浏览器是响应一个html错误页面,
对于app是返回错误状态码和一段json字符串
2.自定义异常处理
①针对浏览器发出的请求
在src/mian/resources文件夹下创建文件夹error编写错误页面
错误页面.png
对应的错误状态码就会去到对应的页面
只会对浏览器发出的请求有作用,对app发出的请求,错误返回仍然是错误码和json字符串
②针对客户端app发出的请求
自定义异常:package com.hcx.exception;public class UserNotExistException extends RuntimeException{
private static final long serialVersionUID = -6112780192479692859L;
private String id;
public String getId() { return id;
} public void setId(String id) { this.id = id;
} public UserNotExistException(String id) { super("user not exist"); this.id = id;
}
}
在Controller中抛自己定义的异常//发生异常时,抛自己自定义的异常@GetMapping("/{id:\\d+}")@JsonView(User.UserDetailView.class)public User getInfo1(@PathVariable String id) { throw new UserNotExistException(id);
}
默认情况下,springboot不读取id的信息
抛出异常时,进入该方法进行处理:
ControllerExceptionHandler:@ControllerAdvice //只负责处理异常public class ControllerExceptionHandler {
@ExceptionHandler(UserNotExistException.class) @ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map handleUserNotExistException(UserNotExistException ex){
Map result = new HashMap<>(); //把需要的信息放到异常中
result.put("id", ex.getId());
result.put("message", ex.getMessage()); return result;
}
}
作者:JS_HCX
原文链接
來源:简书