基于springboot创建RESTful风格接口

基于springboot创建RESTful风格接口

RESTful API风格

restfulAPI.png

特点:

  1. URL描述资源
  2. 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果
  3. 使用json交互数据
  4. RESTful只是一种风格,并不是强制的标准

REST成熟度模型.png

一、查询请求

1.编写单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public 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输出内容

查询请求:

@RestController
public 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)
@SpringBootTest
public 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;
        }

当希望对传递进来的参数作一些限制时,可以使用正则表达式:

//测试提交错误信息
@Test
public 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;
}

单元测试:

//查询
@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}]
}


//获取用户详情
@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}

}

代码重构:

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才可以接收前端传递过来的参数

@PostMapping
public 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:

@PostMapping
public 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

①在实体中添加相应验证注解:

@NotBlank
private String password;

②后台接收参数时加@Valid注解

@PostMapping
public 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:校验成功
    }

}

五、处理删除

单元测试:

@Test
public 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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值