一、《Spring Security开发安全的REST服务》视频笔记
1、Spring boot单元测试
eg:
@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 whenUploadSuccess() throws Exception {
String result = mockMvc.perform(fileUpload("/file")
.file(new MockMultipartFile("file", "test.txt", "multipart/form-data", "hello upload".getBytes("UTF-8"))))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@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("page", "3")
// .param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk()).andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@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);
}
@Test
public void whenGetInfoFail() throws Exception {
mockMvc.perform(get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is4xxClientError());
}
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String reuslt = 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(reuslt);
}
@Test
public void whenCreateFail() throws Exception {
Date date = new Date();
System.out.println(date.getTime());
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String reuslt = 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(reuslt);
}
@Test
public void whenUpdateSuccess() throws Exception {
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
System.out.println(date.getTime());
String content = "{\"id\":\"1\", \"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String reuslt = 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(reuslt);
}
@Test
public void whenDeleteSuccess() throws Exception {
mockMvc.perform(delete("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
}
2、请求url正则表达式
eg:
@PutMapping("/{id:\\d+}")
3、@JsonView注解
使用步骤:
(1)使用接口来声明多个视图
public class User {
public interface UserSimpleView {};
public interface UserDetailView extends UserSimpleView {};
private String id;
@MyConstraint(message = "这是一个测试")
@ApiModelProperty(value = "用户名")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@Past(message = "生日必须是过去的时间")
private Date birthday;
@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;
}
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
上面声明了UserSimpleView和UserDetailView两个接口。
(2)在值对象的get方法上指定视图
例如上述代码中,有两处不同:
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
和
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
而且由于UserSimpleView和UserDetailView是继承关系,所以显示密码的地方也会显示用户名。
(3)在Controller方法上指定视图
例如:
@GetMapping
@JsonView(User.UserSimpleView.class)
@ApiOperation(value = "用户查询服务")
public List<User> query(UserQueryCondition condition,
@PageableDefault(page = 2, size = 17, sort = "username,asc") Pageable pageable) {
System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));
System.out.println(pageable.getPageSize());
System.out.println(pageable.getPageNumber());
System.out.println(pageable.getSort());
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(@ApiParam("用户id") @PathVariable String id) {
// throw new RuntimeException("user not exist");
System.out.println("进入getInfo服务");
User user = new User();
user.setUsername("tom");
return user;
}
4、@RequestBody映射请求体到java方法的参数
eg:
后台接收:
public User create(@RequestBody User user)
前端发送json字符串:
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
System.out.println(date.getTime());
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String reuslt = 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(reuslt);
}
5、日期类型参数的处理
后台统一返回时间戳,前端根据自己的需求将时间戳转成特定格式的日期时间。
eg:
Date date = new Date();
String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
6、@Valid注解和BindingResult验证请求参数的合法性并处理校验结果
愚蠢的逐个校验(代码重构时不方便维护):
if(StringUtils.isNotBlank(pwd)){
}
可以使用@NotBlank:
public class User {
public interface UserSimpleView {};
public interface UserDetailView extends UserSimpleView {};
private String id;
@MyConstraint(message = "这是一个测试")
@ApiModelProperty(value = "用户名")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
但如果仅仅只加这个注解是不行的!!!还要在控制器方法中配合@Valid注解使用:
@PostMapping
@ApiOperation(value = "创建用户")
public User create(@Valid @RequestBody User user) {
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getBirthday());
user.setId("1");
return user;
}
注意:如果没有加上BindingResult参数,且传进来的password为空,则该方法根本不会执行,而是直接报错!!!如果想要知道是发生了什么错误并且能进入方法进行处理,则:
@PostMapping
@ApiOperation(value = "创建用户")
public User create(@Valid @RequestBody User user, BindingResult errors) {
if(errors.hasErrors()){
errors.getAllErrors().stream().forEach(err->System.out.println(err.getDefaultMessage()));
}
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
System.out.println(user.getBirthday());
user.setId("1");
return user;
}
扩展:还有很多常用的验证注解在Hibernate Validator可以查看。
7、自定义校验注解
(1)创建注解(
注意:
@Constraint(validatedBy = MyConstraintValidator.class)
)
@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 { };
}
(2)定义刚刚这个注解的校验逻辑由谁来执行(泛型参数中,第一个是注解名,第二个是注解适合用在什么类型的参数)
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
@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 true;
}
}
(3)使用注解:
@MyConstraint(message = "这是一个测试") private String username;
8、Spring Boot中默认的错误处理机制和自定义异常处理
可以使用chrome的一个插件来模拟app的请求:Restlet Client
SpringBoot默认的错误处理机制是:检测到是浏览器发出,则返回html;是app发出,则返回json字符串。源码在BasicErrorController
二、java8新增时间api:LocalDateTime类
原文:https://www.cnblogs.com/z-y-x/p/10426974.html