Spring Security项目Spring MVC开发RESTful API(二)
查询请求
常用注解
- @RestController 标明此Controller提供RestAPI
- @RequestMapping 映射http请求url到java方法
- @RequestParam 映射请求参数到java方法到参数
- @PageableDefault 指定分页参数默认值
编写一个简单的UserController类
@RestController @RequestMapping(value = "/user") public class UserController { @RequestMapping(method = RequestMethod.GET) public List<User> query(@RequestParam(name = "username",required = true) String username, @PageableDefault(page = 1,size = 20,sort = "username",direction = Sort.Direction.DESC)Pageable pageable){ System.out.println(pageable.getSort()); List<User>users=new ArrayList<>(); users.add(new User("aaa","111")); users.add(new User("bbb","222")); users.add(new User("ddd","333")); return users; } }
@PageableDefault SpingData分页参数 page当前页数默认0开始 sizi每页个数默认10 sort 排序
Srping boot 测试用例
在demo的pom.xml里面引入spirngboot的测试
<!--spring测试框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
测试/user接口
@RunWith(SpringRunner.class) //运行器 @SpringBootTest public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void stup(){ mockMvc= MockMvcBuilders.webAppContextSetup(wac).build(); } //测试用例 @Test public void whenQuerSuccess() throws Exception { String result=mockMvc.perform(MockMvcRequestBuilders.get("/user") //传过去的参数 .param("username","admin") .contentType(MediaType.APPLICATION_JSON_UTF8)) //判断请求的状态吗是否成功,200 .andExpect(MockMvcResultMatchers.status().isOk()) //判断返回的集合的长度是否是3 .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3)) //打印信息 .andDo(MockMvcResultHandlers.print()) .andReturn().getResponse().getContentAsString(); //打印返回结果 System.out.println(result); }
用户详情请求
常用注解
- @PathVariable 映射url片段到java方法参数
- @JsonView 控制json输出内容
实体对象
@NoArgsConstructor @AllArgsConstructor public class User { public interface UserSimpleView{}; public interface UserDetailView extends UserSimpleView{}; private String username; private String password; @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类
@RestController @RequestMapping(value = "/user") public class UserController { @RequestMapping(value = "/{id:\\d+}",method = RequestMethod.GET) // 正则表达式 :\\d+ 表示只能输入数字 //用户名密码都显示 @JsonView(User.UserDetailView.class) public User userInfo(@PathVariable String id){ User user=new User(); user.setUsername("tom"); return user; } }
测试用例
@RunWith(SpringRunner.class) //运行器 @SpringBootTest public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void stup(){ mockMvc= MockMvcBuilders.webAppContextSetup(wac).build(); } //用户详情用例 @Test public void whenUserInfoSuccess() throws Exception { String result=mockMvc.perform(MockMvcRequestBuilders.get("/user/1") .contentType(MediaType.APPLICATION_JSON_UTF8)) //判断请求的状态吗是否成功,200 .andExpect(MockMvcResultMatchers.status().isOk()) //判断返回到username是不是tom .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom")) //打印信息 .andDo(MockMvcResultHandlers.print()) .andReturn().getResponse().getContentAsString(); //打印返回结果 System.out.println(result); } }
用户处理创建请求
常用注解
- @RequestBody 映射请求体到java方法到参数
- @Valid注解和BindingResult验证请求参数合法性并处理校验结果
实体对象
@NoArgsConstructor @AllArgsConstructor public class User { public interface UserSimpleView{}; public interface UserDetailView extends UserSimpleView{}; private String id; private String username; //不允许password为null @NotBlank private String password; private Date 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; } @JsonView(UserSimpleView.class) public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
Controller类
@RequestMapping(method = RequestMethod.POST) @JsonView(User.UserSimpleView.class) //@Valid启用校验password不允许为空 public User createUser(@Valid @RequestBody User user, BindingResult errors){ //如果校验有错误是true并打印错误信息 if(errors.hasErrors()){ errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage())); } System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getBirthday()); user.setId("1"); return user; }
测试用例
@RunWith(SpringRunner.class) //运行器 @SpringBootTest public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void stup(){ mockMvc= MockMvcBuilders.webAppContextSetup(wac).build(); } //用户创建用例 @Test public void whenCreateSuccess() throws Exception { Date date=new Date(); String content="{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}"; String result=mockMvc.perform(MockMvcRequestBuilders.post("/user") .content(content) .contentType(MediaType.APPLICATION_JSON_UTF8)) //判断请求的状态吗是否成功,200 .andExpect(MockMvcResultMatchers.status().isOk()) //判断返回到username是不是tom .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn().getResponse().getContentAsString(); //打印返回结果 System.out.println(result); } }
修改和删除请求
验证注解
| 注解 | 解释 |
| -------- | -------- |
| @NotNull | 值不能为空 |
| @Null | 值必须为空 |
| @Pattern(regex=) | 字符串必须匹配正则表达式 |
| @Size(min=,max=) | 集合的元素数量必须在min和max之间 |
| @Email | 字符串必须是Email地址 |
| @Length(min=,max=) | 检查字符串长度 |
| @NotBlank | 字符串必须有字符 |
| @NotEmpty | 字符串不为null,集合有元素 |
| @Range(min=,max=) | 数字必须大于等于min,小于等于max |
| @SafeHtml | 字符串是安全的html |
| @URL | 字符串是合法的URL |
| @AssertFalse | 值必须是false |
| @AssertTrue | 值必须是true |
| @DecimalMax(value=,inclusive) | 值必须小于等于(inclusive=true)/小于(inclusive=false) value指定的值 |
| @DecimalMin(value=,inclusive) | 值必须大于等于(inclusive=true)/大于(inclusive=false) value指定的值 |
| @Digits(integer=,fraction=) | integer指定整数部分最大长度,fraction小数部分最大长度 |
| @Future | 被注释的元素必须是一个将来的日期 |
| @Past | 被注释的元素必须是一个过去的日期 |
| @Max(value=) | 值必须小于等于value值 |
| @Min(value=) | 值必须大于等于value值 |
自定义注解修改请求
实体对象
@NoArgsConstructor @AllArgsConstructor public class User { public interface UserSimpleView{}; public interface UserDetailView extends UserSimpleView{}; private String id; //自定义注解 @MyConstraint(message = "账号必须是tom") private String username; //不允许password为null @NotBlank(message = "密码不能为空") private String password; //加验证生日必须是过去的时间 @Past(message = "生日必须是过去的时间") private Date 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; } @JsonView(UserSimpleView.class) public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
Controller类
@RequestMapping(value = "/{id:\\d+}",method = RequestMethod.PUT) @JsonView(User.UserSimpleView.class) //@Valid启用校验password不允许为空 public User updateUser(@Valid @RequestBody User user, BindingResult errors){ //如果校验有错误是true并打印错误信息 if(errors.hasErrors()){ errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage())); } System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getBirthday()); user.setId("1"); return user; }
测试用例
@RunWith(SpringRunner.class) //运行器 @SpringBootTest public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void stup(){ mockMvc= MockMvcBuilders.webAppContextSetup(wac).build(); } //用户修改用例 @Test public void whenUpdateSuccess() throws Exception { //当前时间加一年 Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); String content = "{\"id\":\"1\",\"username\":\"44\",\"password\":null,\"birthday\":" + date.getTime() + "}"; String result = mockMvc.perform(MockMvcRequestBuilders.put("/user/1") .content(content) .contentType(MediaType.APPLICATION_JSON_UTF8)) //判断请求的状态吗是否成功,200 .andExpect(MockMvcResultMatchers.status().isOk()) //判断返回到username是不是tom .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn().getResponse().getContentAsString(); //打印返回结果 System.out.println(result); }
自定义注解
MyConstraint类
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //作用在字段跟方法上面 @Target({ElementType.FIELD,ElementType.METHOD}) //运行时注解 @Retention(RetentionPolicy.RUNTIME) //需要校验注解的类 @Constraint(validatedBy = MyConstraintValidator.class) public @interface MyConstraint { String message() default "{org.hibernate.validator.constraints.NotBlank.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
MyConstraintValidator类
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; //范型1.验证的注解 2.验证的数据类型 public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> { @Override public void initialize(MyConstraint myConstraint) { //校验器初始化的规则 } @Override public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { //校验username如果是tom验证通过 if (value.equals("tom")){ return true; }else{ return false; } } }
删除请求
Controller类
@RequestMapping(value = "/{id:\\d+}",method = RequestMethod.DELETE) //@Valid启用校验password不允许为空 public void deleteUser(@PathVariable String id){ System.out.println(id); }
测试用例
@RunWith(SpringRunner.class) //运行器 @SpringBootTest public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void stup(){ mockMvc= MockMvcBuilders.webAppContextSetup(wac).build(); } //用户删除用例 @Test public void whenDeleteSuccess() throws Exception { mockMvc.perform(MockMvcRequestBuilders.delete("/user/1") .contentType(MediaType.APPLICATION_JSON_UTF8)) //判断请求的状态吗是否成功,200 .andExpect(MockMvcResultMatchers.status().isOk()); }
服务异常处理
把BindingResult errors去掉
@RequestMapping(method = RequestMethod.POST) @JsonView(User.UserSimpleView.class) //@Valid启用校验password不允许为空 public User createUser(@Valid @RequestBody User user){ //如果校验有错误是true并打印错误信息 // if(errors.hasErrors()){ // errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage())); // } System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getBirthday()); user.setId("1"); return user; }
查看返回的异常信息
处理状态码错误
创建文件结构如下404错误将跳转对应页面
RESTful API的拦截
过滤器(Filter)
创建filter文件
@Component public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("TimeFilter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("TimeFilter doFilter"); long start=new Date().getTime(); filterChain.doFilter(servletRequest,servletResponse); System.out.println("耗时"+(new Date().getTime()-start)); } @Override public void destroy() { System.out.println("TimeFilter destroy"); } }
自定义filter
需要吧filter文件@Component标签去除
@Configuration public class WebConfig { @Bean public FilterRegistrationBean timeFilterRegistration(){ FilterRegistrationBean registration=new FilterRegistrationBean(); TimeFilter timeFilter=new TimeFilter(); registration.setFilter(timeFilter); //filter作用的地址 List<String>urls=new ArrayList<>(); urls.add("/user"); registration.setUrlPatterns(urls); return registration; } }
拦截器(Interceptor)
创建Interceptor文件
@Component public class TimeInterceptor implements HandlerInterceptor { //控制器方法调用之前 @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("preHandle"); System.out.println("进入方法"+((HandlerMethod)o).getMethod().getName()); httpServletRequest.setAttribute("startTime",new Date().getTime()); //是否调用后面的方法调用是true return true; } //控制器方法被调用 @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); Long start= (Long) httpServletRequest.getAttribute("startTime"); System.out.println("time interceptor耗时"+(new Date().getTime()-start)); } //控制器方法完成之后 @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("afterCompletion"); System.out.println("exception is"+e); } }
把过滤器添加到webconfig文件
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private TimeInterceptor timeInterceptor; //过滤器 @Bean public FilterRegistrationBean timeFilterRegistration(){ FilterRegistrationBean registration=new FilterRegistrationBean(); TimeFilter timeFilter=new TimeFilter(); registration.setFilter(timeFilter); //filter作用的地址 List<String>urls=new ArrayList<>(); urls.add("/user/*"); registration.setUrlPatterns(urls); return registration; } //拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); } }
切片(Aspect)
@Aspect @Component public class TimeAspect { //@Befor方法调用之前 //@After()方法调用 //@AfterThrowing方法调用之后 //包围,覆盖前面三种 @Around("execution(* com.guosh.web.controller.UserController.*(..))")//表达式表示usercontroller里所有方法其他表达式可以查询切片表达式 public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable { System.out.println("time aspect start"); //可以获取到传入参数 Object[]args=pjp.getArgs(); for (Object arg: args) { System.out.println("arg is"+arg); } long start=new Date().getTime(); //相当于filter里doFilter方法 Object object=pjp.proceed(); System.out.println("time aspect耗时"+(new Date().getTime()-start)); System.out.println("time aspect end"); return object; } }
总结
过滤器Filter :可以拿到原始的http请求与响应信息
拦截器Interceptor :可以拿到原始的http请求与响应信息还可以拿到处理请求方法的信息
切片Aspect :可以拿到方法调用传过来的值
使用rest方式处理文件服务
返回的上传文件后路径对象
在application.yml里添加上传地址
#上传文件路径 uploadfiledir: filePath: /Users/shaohua/webapp/guoshsecurity
@Data @NoArgsConstructor @AllArgsConstructor public class FileInfo { private String path; }
@RestController @RequestMapping("/file") public class FileController { @Value("${uploadfiledir.filePath}") private String fileDataStorePath;//文件上传地址 @RequestMapping(method = RequestMethod.POST) public FileInfo upload(@RequestParam("file") MultipartFile file) throws IOException { //文件名 System.out.println(file.getOriginalFilename()); //文件大小 System.out.println(file.getSize()); //获取文件后缀名 String ext=StringUtils.getFilenameExtension(file.getOriginalFilename()); File fileDir = new File(fileDataStorePath); //判断是否创建目录 if (!fileDir.exists()) { if (!fileDir.mkdirs() || !fileDir.exists()) { // 创建目录失败 throw new RuntimeException("无法创建目录!"); } } File localFile=new File(fileDataStorePath, UUID.randomUUID().toString().replace("-", "")+"."+ext); file.transferTo(localFile); //返回上传的路径地址 return new FileInfo(localFile.getAbsolutePath()); } //下载文件 @RequestMapping(value ="/{id}" ,method = RequestMethod.GET) public void download(@PathVariable String id, HttpServletResponse response){ //模拟下载直接填好了下载文件名称 try(InputStream inputStream = new FileInputStream(new File(fileDataStorePath,"13a2c075b7f44025bbb3c590f7f372eb.txt")); OutputStream outputStream=response.getOutputStream();){ response.setContentType("application/x-download"); response.addHeader("Content-Disposition","attachment;filename="+"13a2c075b7f44025bbb3c590f7f372eb.txt\""); IOUtils.copy(inputStream,outputStream); } catch (Exception e) { e.printStackTrace(); } } }
使用Swagger工具
在demo模块引入
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
添加swagger的配置类
@Configuration @EnableSwagger2 public class Swagger2Config { @Value("${sys.swagger.enable-swgger}") private Boolean enableSwgger; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .enable(enableSwgger) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.guosh.web")) //swgger插件作用范围 //.paths(PathSelectors.regex("/api/.*")) .paths(PathSelectors.any()) //过滤接口 .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SpringSecurityDemo") //标题 .description("API描述") //描述 .contact(new Contact("guoshaohua", "http://www.guoshaohua.cn", ""))//作者 .version("1.0") .build(); } }
常用注解
- 通过@Api用于controller类上对类的功能进行描述
- 通过@ApiOperation注解用在controller方法上对类的方法进行描述
- 通过@ApiImplicitParams、@ApiImplicitParam注解来给参数增加说明
- 通过@ApiIgnore来忽略那些不想让生成RESTful API文档的接口
- 通过@ApiModel 用在返回对象类上描述返回对象的意义
- 通过@ApiModelProperty 用在实体对象的字段上 用于描述字段含义