• (02)Restful风格的增删改查、上传、下载案例及其junit测试详解


      一、相关注解

      @GetMapping:等价于@RequestMapping(method=RequestMethod.GET)

      @PostMapping:等价于@RequestMapping(method=RequestMethod.POST)

      @PutMapping:等价于@RequestMapping(method=RequestMethod.PUT)

      @DeleteMapping:等价于@RequestMapping(method=RequestMethod.DELETE)

      @RequestBody:映射请求体到java方法的参数

      @RequestParam:映射请求参数到java方法的参数

      @PageableDefault:指定分页参数默认值

      @PathVariable: 映射url片段到java方法的参数

      @JsonView:控制json输出内容,可以指定哪些字段显示

      @Valid:验证字段是否合法

      BindingResult:验证不合法时获取提示信息,@Valid注解和BingResult验证请求参数的合法性并处理校验结果

      二、演示增删查改、上传、下载

      User.java

    package com.edu.sl.dto;
    
    import java.util.Date;
    
    public class User {
    
        private String id;
        private String username;
        private String password;
        private Date birthDay;
        
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public Date getBirthDay() {
            return birthDay;
        }
        public void setBirthDay(Date birthDay) {
            this.birthDay = birthDay;
        }
    }

      UserController.java

    package com.edu.sl.controller;
    
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/user")
    public class UserController { ... ... }

      UserControllerTest.java

    package com.edu.sl.controller;
    
    import org.junit.Before;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserControllerTest {
    
        @Autowired
        private WebApplicationContext wac;
        
        private MockMvc mockMvc;
        
        @Before
        public void setUp(){
            mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
        }
       ... ...    
    }

      测试依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>

      1、查询操作

      使用@GetMapping注解,测试使用get方法。

      1)不传递参数

    @GetMapping
    public List<User> query(){
      List<User> users = new ArrayList<User>();
      users.add(new User());
      users.add(new User());
      users.add(new User());
      return users;
    }
    @Test
    public void query() throws Exception{
      String content=mockMvc.perform(MockMvcRequestBuilders.get("/user")
                       .contentType(MediaType.APPLICATION_JSON_UTF8))
                       .andExpect(MockMvcResultMatchers.status().isOk())
                       .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                       .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      2)传递参数

    @GetMapping("/a")public List<User> query2(String username,String password){
        System.out.println("username:"+username);
        System.out.println("password:"+password);
        List<User> users = new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;
    }
    @Test
    public void query() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
                              .param("username","sl")
                              .param("password","123456")
                              .contentType(MediaType.APPLICATION_JSON_UTF8))
                              .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                              .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      3)@RequestParam注解

      使用此注解,默认情况下传递的实参中必须含有它指定的参数名,否则报400。此注解含有4个属性可以改变默认情况。

      defaultValue:如果没有实参,形参中默认使用的值。

      name:起别名,默认实参、形参名字一致,使用name属性时形参可以随意命名,会映射过来。

      required:是否必须,默认false必填 ,否则报400。

      value:最终得到的值。

    @GetMapping("/b")
    public List<User> query3(@RequestParam(name="username",required=false,defaultValue="tom") String nikename){
        System.out.println("nikename:"+nikename);
        List<User> users = new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;
    }
    @Test
    public void query3() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/b")
                              .param("username","sl")
                              .contentType(MediaType.APPLICATION_JSON_UTF8))
                              .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                              .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      如果实参中有username这个属性,就取它的值赋给nikename,没有这个属性,nikename就取tom这个值

      4)@PageableDefault注解

      使用此注解指定分页参数默认值,配合Pageable使用,需要引入以下依赖

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-commons</artifactId>
    </dependency>
    @GetMapping("/c")
    public List<User> query4(User user,@PageableDefault(page=10,size=20,sort="age,asc") Pageable pageable){
        System.out.println("page:"+pageable.getPageNumber());
        System.out.println("size:"+pageable.getPageSize());
        System.out.println("sort:"+pageable.getSort());
        List<User> users = new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;
    }
    @Test
    public void query4() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/c")
                              .param("username","sl")
                              .param("size","10")
                              .param("page","2")
                              .param("sort","username,desc")
                              .contentType(MediaType.APPLICATION_JSON_UTF8))
                              .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                              .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      如果实参中传递了size、page、sort参数,则实际使用传递的,如果实参中没有传递,则使用形参中默认的值,不会报错。

      实参中的属性会自动匹配到形参的类的对象的属性上面,如username会自动匹配到实参中user中的username中。

      5)@PathVariable注解

      此注解把声明的url(value="/{id}")中的片段id的值作为参数传到java方法的id上

    @GetMapping(value="/{id3}")
    public User getUserInfo(@PathVariable("id3") String id){
       System.out.println("id:"+id);//可以获取到 User user
    =new User(); user.setUsername("tom"); return user; }
    @Test
    public void getUserInfo() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
        .contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
        .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      @PathVariable里面有name,value属性,作用一样,用来指定变量的名字,例如上例中id3会映射到id上。两个id3要保持一致。

      大括号中的变量可以使用正则表达式,例如只希望id是整数,可以如下:

    @GetMapping(value="/a/{id3:\d+}")
    public User getUserInfo2(@PathVariable("id3") String id){
        System.out.println("id:"+id);
        User user=new User();
        user.setUsername("tom");
        return user;
    }
    @Test
    public void getUserInfo2() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/a/13")
        .contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
        .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }
    @Test
    public void getUserInfo2_fail() throws Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/user/a/1.3")
        .contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(MockMvcResultMatchers.status().is4xxClientError());
    }

      如果将参数值13改为字母或者小数,报404。

      6)@JsonView注解

      分三步:用接口定义视图名称,在get方法上指定视图,在Controller方法上指定视图。

      修改User类,做如下修改:

    public interface UserSimpleView{};
    public interface UserDetailView extends UserSimpleView{};
    
    @JsonView(User.UserSimpleView.class)
    public String getUsername() {
        return username;
    }
    
    @JsonView(User.UserDetailView.class)
    public String getPassword() {
        return password;
    }
    @GetMapping("/d")
    @JsonView(User.UserSimpleView.class)
    public List<User> query5(){
        List<User> list=new ArrayList<User>();
        list.add(new User());
        list.add(new User());
        list.add(new User());
        return list;
    }
    
    @GetMapping("/b/{id:\d+}")
    @JsonView(User.UserDetailView.class)
    public User getUserInfo3(@PathVariable("id") String id){
        User user=new User();
        user.setUsername("tom");
        return user;
    @Test
    public void query5() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/d")
                              .contentType(MediaType.APPLICATION_JSON_UTF8))
                              .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                              .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }
    
    @Test
    public void getUserInfo3() throws Exception{
        String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/b/13")
                              .contentType(MediaType.APPLICATION_JSON_UTF8))
                              .andExpect(MockMvcResultMatchers.status().isOk())
                              .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
                              .andReturn().getResponse().getContentAsString();
        System.out.println(content);
    }

      从System输出的结果中可以看到,query5返回json只有一个username属性,getUserInfo3含有username和password属性。

      2、新增操作

      使用@PostMapping注解,测试使用post方法。

      1)不校验参数值

    @PostMapping
    public User create(@RequestBody User user){
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getId());
        System.out.println(user.getBirthDay());
        user.setId("1");
        return user;
    }
    @Test
    public void whenCreateSuccess() throws Exception{
        String content="{"username":"tom","password":123456}";
        String result=mockMvc.perform(MockMvcRequestBuilders.post("/user")
                             .contentType(MediaType.APPLICATION_JSON_UTF8)
                             .content(content))
                             .andExpect(MockMvcResultMatchers.status().isOk())
                             .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                             .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

      注意:必须加@RequestBody注解,否则接收不到参数值,输出的属性值都是null。

      2)校验参数值

      后端使用hibernate-validator.jar校验,Controller中方法添加@Valid注解,实体类添加需要验证的注解。

      修改User类,做如下修改:

    @NotBlank(message="密码不能为空")
    private String password;
    @Past(message="生日必须是过去的时间")
    private Date birthDay;
    @PostMapping("/a")
    public User create2(@Valid @RequestBody User user){
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getId());
        System.out.println(user.getBirthDay());
        user.setId("1");
        return user;
    }
    @Test
    public void create2() throws Exception{
      //当前时间加上一年 Date birthDay
    =new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}"; String result=mockMvc.perform(MockMvcRequestBuilders.post("/user/a") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(content)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn().getResponse().getContentAsString(); System.out.println(result); }

      此时运行测试用例报400。不会进入方法体。

      说明:字段上的验证注解与方法中@Valid注解同时使用才有效。

      3)BindingResult类

      在Controller中的方法里加上这个类,可以获取到验证不通过的提示信息。

    @PostMapping("/b")
    public User create3(@Valid @RequestBody User user,BindingResult errors){
        if(errors.hasErrors()){
            List<ObjectError> list=errors.getAllErrors();
            for(ObjectError error:list){
                System.out.println(((FieldError)error).getField()+" "+ error.getDefaultMessage());
            }
        }
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getId());
        System.out.println(user.getBirthDay());
        user.setId("1");
        return user;
    }
    @Test
    public void create3() throws Exception{
        Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
        String result=mockMvc.perform(MockMvcRequestBuilders.post("/user/b")
                             .contentType(MediaType.APPLICATION_JSON_UTF8)
                             .content(content))
                             .andExpect(MockMvcResultMatchers.status().isOk())
                             .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                             .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

      此时运行测试用例,会进入方法体。打印结果如下:

      

      说明:BindingResult与@Valid注解同时使用才有效。

      处理日期一般传递时间戳 new date().getTime(),因为js、app等都可以处理时间戳和日期的转换。

      3、修改操作

      使用@PutMapping注解,测试使用put方法。

    @Test
    public void update() throws Exception{
        Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
        String result=mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }
    @Test
    public void update() throws Exception{
        Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
        String result=mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

      4、删除操作

      使用@DeleteMapping注解,测试使用delete方法。

    @DeleteMapping("/{id:\d+}")
    public void delete(@PathVariable String id){
        System.out.println("id :"+id);
    }
    @Test
    public void delete() throws Exception{
        mockMvc.perform(MockMvcRequestBuilders.delete("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(MockMvcResultMatchers.status().isOk());
    }

      5、上传操作

      前后端分离的项目,上传操作一般是前端先异步上传到服务器上,把服务器返回的文件路径或文件ID再和表单一块传给后端。

    public class FileInfo {
    
        private String path;
    
        public String getPath() {
            return path;
        }
        public void setPath(String path) {
            this.path = path;
        }
        public FileInfo(String path) {
            this.path = path;
        }
    }
    @PostMapping("/file")
    public FileInfo upload(MultipartFile file) throws Exception{
        System.out.println(file.getName());//输出file
        System.out.println(file.getOriginalFilename());//输出test.txt
        System.out.println(file.getSize());
        File directory=new File("D:/upLoadTest");
        if(!directory.exists()){//如果文件夹不存在
            directory.mkdir();//创建文件夹
        }
        File localFile=new File("D:/upLoadTest",new Date().getTime()+".txt");
        file.transferTo(localFile);//上传过来的文件 写到本地
        return new FileInfo(localFile.getAbsolutePath());
    }
    @Test
    public void whenUploadSuccess() throws Exception{
        String result=mockMvc.perform(MockMvcRequestBuilders.fileUpload("/user/file")
                             .file(new MockMultipartFile("file","test.txt","multipart/form-data","hello upload".getBytes("UTF-8"))))
                             .andExpect(MockMvcResultMatchers.status().isOk())
                             .andReturn().getResponse().getContentAsString();
        System.out.println(result);
    }

      6、下载操作

    @GetMapping("/file/{id}")
    public void download(@PathVariable String id,HttpServletRequest request,HttpServletResponse response){
        InputStream inputStream=null;
        OutputStream outputStream=null;
        try {
            inputStream=new FileInputStream(new File("D:/upLoadTest",id+".txt"));
            outputStream=response.getOutputStream();
            response.setContentType("application/x-download");
            response.addHeader("Content-Disposition", "attachment;filename=test.txt");
            IOUtils.copy(inputStream, outputStream);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

      重启服务,浏览器输入:http://localhost/user/file/1590645057263,下载成功!

      三、自定义验证注解

      虽然hibernate-validator提供了大量的校验注解,但有时仍不能满足我们的需求,这时就要自定义注解。

    package com.edu.sl.validator;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Target({ElementType.METHOD,ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy=MyConstraintValidator.class)//指定注解使用的类
    public @interface MyConstraint {
        String message();
        Class<?>[] groups() default { };
        Class<? extends Payload>[] payload() default { };
    }
    package com.edu.sl.validator;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import org.springframework.beans.factory.annotation.Autowired;
    
    public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {
        
        @Override
        public void initialize(MyConstraint arg0) {
            System.out.println("校验器初始化方法"); 
        }
    
        @Override
        public boolean isValid(Object arg0, ConstraintValidatorContext arg1) {
            System.out.println("arg0 :"+arg0);
         //写校验逻辑
            return false;//校验失败
        }
    }

      自定义了一个注解和一个类,注解MyConstraint中三个属性是固定的。类MyConstraintValidator中的第二个泛型Object是可用类型,假如定义为String,只有在String类型的字段上可以使用该注解。自定义类中不需要加Component即可被Spring管理,可以注入其他bean用于校验逻辑。

      直接运行测试用例update,输出如下:

      

      四、其他

      1、返回验证码说明:

      400:请求格式错误,例如要求实参中必须有username,但实际没有。或者验证不通过
      405:后台不支持method,例如get请求post的方法

      2、JsonPath使用说明:

      在测试中使用jsonpath很方便,推荐使用。github中搜索 jsonpath,结果如下https://github.com/json-path/JsonPath

      3、Hibernate Validateor使用说明:

      Hibernate Validateor网上也有很多api,哪位网友知道比较全的地址可以告诉一下,下面截图奉上部分。

      

      

      

      

      

      

      

  • 相关阅读:
    js学习笔记之(call、apply)
    windows8 应用的激活与挂起
    SharePoint2010分享
    Windows8 Metro 设计与开发vs11风格样式
    windows8 应用激活
    Windows8 Metro 设计与开发平台预览
    windows8 账户图片设置
    Windows8 Metro 设计与开发必须关注的新特性
    Windows8 Metro 设计与开发你不知道的C模型
    Windows8 Metro 设计与开发VS11的win8模拟器
  • 原文地址:https://www.cnblogs.com/javasl/p/12967387.html
Copyright © 2020-2023  润新知