手记

基于springboot创建RESTful风格接口

基于springboot创建RESTful风格接口

RESTful API风格

restfulAPI.png

特点:

  1. URL描述资源

  2. 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果

  3. 使用json交互数据

  4. 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<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
        System.out.println(username);
        List<User> 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表达式书写

github链接:https://github.com/json-path/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<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
    System.out.println(username);
    List<User> 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<User> query(@RequestParam(name="username",required=false,defaultValue="tom") String username){
        System.out.println(username);
        List<User> 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<MyConstraint, Object> {    /*ConstraintValidator<A, T>
    参数一:验证的注解
    参数二:验证的类型
    ConstraintValidator<MyConstraint, String> 当前注解只能放在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<String, Object> handleUserNotExistException(UserNotExistException ex){
        Map<String, Object> result = new HashMap<>();        //把需要的信息放到异常中
        result.put("id", ex.getId());
        result.put("message", ex.getMessage());        return result;
    }

}

完整Demo链接:https://github.com/GitHongcx/RESTfulAPIDemo



作者:JS_HCX  

原文链接 
來源:简书


3人推荐
随时随地看视频
慕课网APP