diff --git a/README.md b/README.md index d423f9d7..e940998f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ ### 作业描述 - -#### 切换到课程对应的分支查看每节课的作业要求 -* Routing: routing -* Data Binding: data-binding -* Validation: validation -* Customised Response: customize-response -* Error Handling: error-handling - - 注意:最终需要将改动合并到master分支 - +* 将所有接口的返回值都替换成使用ResponseEntity +* 所有post请求都返回201,并且返回的头部带上index字段(值为创建的资源在列表中的位置:eg: 添加的热搜事件在列表中的index) +* 完成demo里的练习:get /rs/list和 get/rs/{index}接口返回的数据中不包含user字段 +* 添加获取所有用户的接口: get /users,期望返回的数据格式例子: + ``` + [{ + "user_name": "xxxx", + "user_age": 19, + "user_gender": "female", + "user_email": "xxx@xx", + "user_phone": "1xxxxxxxxxx" + }] +* 先写测试!!! +* hint: @JsonpProperty的使用 \ No newline at end of file diff --git a/git status b/git status new file mode 100644 index 00000000..54623e43 --- /dev/null +++ b/git status @@ -0,0 +1,5 @@ +* customize-response + data-binding + master + vaildation + validation diff --git a/src/main/java/com/thoughtworks/rslist/api/RsController.java b/src/main/java/com/thoughtworks/rslist/api/RsController.java index 6899e4de..44dadb38 100644 --- a/src/main/java/com/thoughtworks/rslist/api/RsController.java +++ b/src/main/java/com/thoughtworks/rslist/api/RsController.java @@ -1,11 +1,86 @@ package com.thoughtworks.rslist.api; +import com.thoughtworks.rslist.domain.User; +import com.thoughtworks.rslist.exception.Error; +import com.thoughtworks.rslist.exception.RsEventNotValidException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.thoughtworks.rslist.domain.RsEvent; +import org.springframework.web.bind.annotation.*; -import java.util.Arrays; +import javax.validation.Valid; +import java.awt.*; +import java.util.ArrayList; import java.util.List; @RestController public class RsController { - private List rsList = Arrays.asList("第一条事件", "第二条事件", "第三条事件"); + private List rsList = initRsEventList(); + private List initRsEventList(){ + List rsEventList = new ArrayList<>(); + User user = new User("lyx","female",18,"1@2.com","12222222222"); + rsEventList.add(new RsEvent("第一条事件","无标签",user)); + rsEventList.add(new RsEvent("第二条事件","无标签",user)); + rsEventList.add(new RsEvent("第三条事件","无标签",user)); + return rsEventList; + } + + @GetMapping("/rs/{index}") + public ResponseEntity getOneRsEvent(@PathVariable int index){ + if (index <= 0 || index > rsList.size()){ + throw new RsEventNotValidException("invalid index"); + } + return ResponseEntity.ok(rsList.get(index -1)); + } + + @GetMapping("/rs/list") + public ResponseEntity getRsEventBetween(@RequestParam(required = false) Integer start,@RequestParam(required = false) Integer end){ + if(start == null || end == null){ + return ResponseEntity.ok(rsList); + } + if((end - start +1) >rsList.size() || start > rsList.size()){ + throw new RsEventNotValidException("invalid request param"); + } + return ResponseEntity.ok(rsList.subList(start - 1,end)); + } + + @PostMapping("/rs/event") + public ResponseEntity addRsEvent(@RequestBody @Valid RsEvent rsEvent) throws JsonProcessingException { + rsList.add(rsEvent); + return ResponseEntity.created(null).build(); + } + + @DeleteMapping ("/rs/{index}") + public ResponseEntity deleteRsEvent(@PathVariable int index) { + rsList.remove(index-1); + return ResponseEntity.ok(rsList.remove(index-1)); + + } + + @PatchMapping("/rs/{index}") + public ResponseEntity modifyRsEvent(@PathVariable int index,@RequestBody RsEvent rsEvent ) { + String eventName = rsEvent.getEventName(); + String keyWord = rsEvent.getKeyWord(); + rsList.get(index - 1).setEventName(eventName); + rsList.get(index - 1).setKeyWord(keyWord); + return ResponseEntity.ok(null); + } + + @ExceptionHandler({RsEventNotValidException.class, MethodArgumentNotValidException.class}) + public ResponseEntity rsExceptionHandler (Exception e){ + String errorMessage; + if(e instanceof MethodArgumentNotValidException){ + errorMessage = "invalid param"; + }else { + errorMessage = e.getMessage(); + } + Error error = new Error(); + error.setError(errorMessage); + return ResponseEntity.badRequest().body(error); + } } diff --git a/src/main/java/com/thoughtworks/rslist/api/UserController.java b/src/main/java/com/thoughtworks/rslist/api/UserController.java new file mode 100644 index 00000000..6c84f3bc --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/api/UserController.java @@ -0,0 +1,42 @@ +package com.thoughtworks.rslist.api; + +import com.thoughtworks.rslist.domain.User; +import com.thoughtworks.rslist.exception.Error; +import com.thoughtworks.rslist.exception.RsEventNotValidException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; + +@RestController +public class UserController{ + List userList = new ArrayList<>(); + + @PostMapping("/user") + public ResponseEntity addUser(@RequestBody @Valid User user){ + userList.add(user); + return ResponseEntity.created(null).build(); + } + + @GetMapping("/user") + public List getUserList(){ + return userList; + } + + @ExceptionHandler({RsEventNotValidException.class, MethodArgumentNotValidException.class}) + public ResponseEntity rsExceptionHandler (Exception e){ + String errorMessage; + if(e instanceof MethodArgumentNotValidException){ + errorMessage = "invalid user"; + }else { + errorMessage = e.getMessage(); + } + Error error = new Error(); + error.setError(errorMessage); + return ResponseEntity.badRequest().body(error); + } + +} \ No newline at end of file diff --git a/src/main/java/com/thoughtworks/rslist/component/RsEventHandler.java b/src/main/java/com/thoughtworks/rslist/component/RsEventHandler.java new file mode 100644 index 00000000..91205965 --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/component/RsEventHandler.java @@ -0,0 +1,22 @@ +package com.thoughtworks.rslist.component; + +import com.thoughtworks.rslist.exception.Error; +import com.thoughtworks.rslist.exception.RsEventNotValidException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; + +public class RsEventHandler { + @ExceptionHandler({RsEventNotValidException.class, MethodArgumentNotValidException.class}) + public ResponseEntity rsExceptionHandler (Exception e){ + String errorMessage; + if(e instanceof MethodArgumentNotValidException){ + errorMessage = "invalid param"; + }else { + errorMessage = e.getMessage(); + } + Error error = new Error(); + error.setError(errorMessage); + return ResponseEntity.badRequest().body(error); + } +} diff --git a/src/main/java/com/thoughtworks/rslist/domain/RsEvent.java b/src/main/java/com/thoughtworks/rslist/domain/RsEvent.java new file mode 100644 index 00000000..6038adcc --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/domain/RsEvent.java @@ -0,0 +1,48 @@ +package com.thoughtworks.rslist.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.validation.Valid; + +@Valid +public class RsEvent { + private String eventName; + private String keyWord; + @Valid + private User user; + + public RsEvent(String eventName,String keyWord,User user){ + this.eventName = eventName; + this.keyWord = keyWord; + this.user = user; + } + + public RsEvent(){ + } + + public User getUser() { + return user; + } + + @JsonProperty + public void setUser(User user) { + this.user = user; + } + + public String getKeyWord() { + return keyWord; + } + + public void setKeyWord(String keyWord) { + this.keyWord = keyWord; + } + + public String getEventName() { + return eventName; + } + + public void setEventName(String eventName) { + this.eventName = eventName; + } +} diff --git a/src/main/java/com/thoughtworks/rslist/domain/User.java b/src/main/java/com/thoughtworks/rslist/domain/User.java new file mode 100644 index 00000000..ca0e65a8 --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/domain/User.java @@ -0,0 +1,76 @@ +package com.thoughtworks.rslist.domain; + +import javax.validation.constraints.*; + +public class User { + @NotNull + @Size(max = 8) + private String name; + @NotNull + private String gender; + @NotNull + @Min(18) + @Max(100) + private int age; + @Email + private String email; + @Pattern(regexp = "1\\d{10}") + private String phone; + private int voteNum = 10; + + public User(String name, String gender, int age, String email, String phone) { + this.name = name; + this.gender = gender; + this.age = age; + this.email = email; + this.phone = phone; + } + + public String getName() { + return name; + } + + public void setName() { + this.name = name; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public int getVoteNum() { + return voteNum; + } + + public void setVoteNum(int voteNum) { + this.voteNum = voteNum; + } +} diff --git a/src/main/java/com/thoughtworks/rslist/exception/Error.java b/src/main/java/com/thoughtworks/rslist/exception/Error.java new file mode 100644 index 00000000..28460c4a --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/exception/Error.java @@ -0,0 +1,14 @@ +package com.thoughtworks.rslist.exception; + + +public class Error { + private String error; + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } +} diff --git a/src/main/java/com/thoughtworks/rslist/exception/RsEventNotValidException.java b/src/main/java/com/thoughtworks/rslist/exception/RsEventNotValidException.java new file mode 100644 index 00000000..0b76eaa7 --- /dev/null +++ b/src/main/java/com/thoughtworks/rslist/exception/RsEventNotValidException.java @@ -0,0 +1,14 @@ +package com.thoughtworks.rslist.exception; + +public class RsEventNotValidException extends RuntimeException{ + private String errorMessage; + + public RsEventNotValidException( String errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public String getMessage() { + return errorMessage; + } +} diff --git a/src/test/java/com/thoughtworks/rslist/api/RsControllerTest.java b/src/test/java/com/thoughtworks/rslist/api/RsControllerTest.java new file mode 100644 index 00000000..836676c3 --- /dev/null +++ b/src/test/java/com/thoughtworks/rslist/api/RsControllerTest.java @@ -0,0 +1,175 @@ +package com.thoughtworks.rslist.api; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.thoughtworks.rslist.domain.RsEvent; +import com.thoughtworks.rslist.domain.User; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +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.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class RsControllerTest { + @Autowired + MockMvc mockMvc; + @Test + @Order(1) + public void should_get_rs_event_list() throws Exception{ + mockMvc.perform(get("/rs/list")).andExpect(jsonPath("$",hasSize(3))) + .andExpect(jsonPath("$[0].eventName",is("第一条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[0]",not(hasKey("user")))) + .andExpect(jsonPath("$[1].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1]",not(hasKey("user")))) + .andExpect(jsonPath("$[2].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[2].keyWord",is("无标签"))) + .andExpect(jsonPath("$[2]",not(hasKey("user")))) + .andExpect(status().isOk()); + } + + @Test + @Order(2) + public void should_get_one_rs_event() throws Exception{ + mockMvc.perform(get("/rs/1")) + .andExpect(jsonPath("$.eventName",is("第一条事件"))) + .andExpect(jsonPath("$.keyWord",is("无标签"))) + .andExpect(status().isOk()); + mockMvc.perform(get("/rs/2")) + .andExpect(jsonPath("$.eventName",is("第二条事件"))) + .andExpect(jsonPath("$.keyWord",is("无标签"))) + .andExpect(status().isOk()); + mockMvc.perform(get("/rs/3")) + .andExpect(jsonPath("$.eventName",is("第三条事件"))) + .andExpect(jsonPath("$.keyWord",is("无标签"))) + .andExpect(status().isOk()); + } + @Test + @Order(3) + public void should_get_rs_event_between() throws Exception{ + mockMvc.perform(get("/rs/list?start=1&end=2")) + .andExpect(jsonPath("$",hasSize(2))) + .andExpect(jsonPath("$[0].eventName",is("第一条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(status().isOk()); + mockMvc.perform(get("/rs/list?start=2&end=3")) + .andExpect(jsonPath("$",hasSize(2))) + .andExpect(jsonPath("$[0].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(status().isOk()); + mockMvc.perform(get("/rs/list?start=1&end=3")) + .andExpect(jsonPath("$",hasSize(3))) + .andExpect(jsonPath("$[0].eventName",is("第一条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(jsonPath("$[2].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[2].keyWord",is("无标签"))) + .andExpect(status().isOk()); + } + @Test + @Order(4) + public void should_add_rs_event() throws Exception{ + User user = new User("lyx","female",18,"1@2.com","12222222222"); + RsEvent rsEvent = new RsEvent("猪肉涨价了","经济",user); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(rsEvent); + mockMvc.perform(post("/rs/event").content(jsonString).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + mockMvc.perform(get("/rs/list")).andExpect(jsonPath("$",hasSize(4))) + .andExpect(jsonPath("$[0].eventName",is("第一条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(jsonPath("$[2].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[2].keyWord",is("无标签"))) + .andExpect(jsonPath("$[3].eventName",is("猪肉涨价了"))) + .andExpect(jsonPath("$[3].keyWord",is("经济"))) + .andExpect(status().isOk()); + } + + @Test + @Order(5) + public void should_modify_rs_event_eventName_and_keyWord() throws Exception { + User user = new User("lyx","female",18,"1@2.com","12222222222"); + RsEvent rsEvent = new RsEvent("猪肉涨价了","经济",user); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(rsEvent); + + mockMvc.perform(patch("/rs/1").content(jsonString).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + mockMvc.perform(get("/rs/list")).andExpect(jsonPath("$",hasSize(3))) + .andExpect(jsonPath("$[0].eventName",is("猪肉涨价了"))) + .andExpect(jsonPath("$[0].keyWord",is("经济"))) + .andExpect(jsonPath("$[0]",not(hasKey("user")))) + .andExpect(jsonPath("$[1].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1]",not(hasKey("user")))) + .andExpect(jsonPath("$[2].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[2].keyWord",is("无标签"))) + .andExpect(jsonPath("$[2]",not(hasKey("user")))) + .andExpect(status().isOk()); + } + + @Test + @Order(6) + public void should_delete_rs_event() throws Exception { + mockMvc.perform(delete("/rs/1")).andExpect(status().isOk()); + + mockMvc.perform(get("/rs/list")).andExpect(jsonPath("$",hasSize(2))) + .andExpect(jsonPath("$[0].eventName",is("第二条事件"))) + .andExpect(jsonPath("$[0].keyWord",is("无标签"))) + .andExpect(jsonPath("$[1].eventName",is("第三条事件"))) + .andExpect(jsonPath("$[1].keyWord",is("无标签"))) + .andExpect(status().isOk()); + } + + @Test + public void should_throw_rs_event_not_valid_exception() throws Exception { + mockMvc.perform(get("/rs/0")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error",is("invalid index"))); + } + + @Test + void should_throw_method_arguement_not_valid_exception() throws Exception { + User user = new User("lyx11111111","female",18,"1@2.com","12222222222"); + RsEvent rsEvent = new RsEvent("猪肉涨价了","经济",user); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(rsEvent); + + mockMvc.perform(post("/rs/event").content(jsonString).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error",is("invalid param"))); + } + @Test + public void should_throw_arguement_not_valid_range_about_start_and_end() throws Exception{ + mockMvc.perform(get("/rs/list?start=1&end=7")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error",is("invalid request param"))); + } + +} diff --git a/src/test/java/com/thoughtworks/rslist/api/UserControllerTest.java b/src/test/java/com/thoughtworks/rslist/api/UserControllerTest.java new file mode 100644 index 00000000..32008aef --- /dev/null +++ b/src/test/java/com/thoughtworks/rslist/api/UserControllerTest.java @@ -0,0 +1,102 @@ +package com.thoughtworks.rslist.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.thoughtworks.rslist.domain.RsEvent; +import com.thoughtworks.rslist.domain.User; + +import org.junit.jupiter.api.Test; +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.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.*; +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.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +public class UserControllerTest { + @Autowired + MockMvc mockMvc; + + @Test + public void should_register_user() throws Exception { + User user = new User("lyx","female",18,"1@2.com","12222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + + mockMvc.perform(get("/user")) + .andExpect(jsonPath("$",hasSize(1))) + .andExpect(jsonPath("$[0].name",is("lyx"))) + .andExpect(jsonPath("$[0].gender",is("female"))) + .andExpect(jsonPath("$[0].age",is(18))) + .andExpect(jsonPath("$[0].email",is("1@2.com"))) + .andExpect(jsonPath("$[0].phone",is("12222222222"))) + .andExpect(status().isOk()); + } + + + @Test + public void name_should_less_than_8() throws Exception{ + User user = new User("lyx111111","female",18,"1@2.com","12222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void age_should_between_18_and_100() throws Exception{ + User user = new User("lyx","female",118,"1@2.com","12222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void email_should_right() throws Exception{ + User user = new User("lyx","female",28,".com","12222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void phone_should_right() throws Exception{ + User user = new User("lyx","female",28,".com","3312222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void should_throw_user_not_valid_exception() throws Exception{ + User user = new User("lyx11111111111","female",28,".com","12222222222"); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonString = objectMapper.writeValueAsString(user); + mockMvc.perform(post("/user") + .content(jsonString) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error",is("invalid user")));; + } + +} \ No newline at end of file