Spring Security项目Spring MVC开发RESTful API(二)

21次阅读

共计 16075 个字符,预计需要花费 41 分钟才能阅读完成。

查询请求
常用注解

@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);
}
jsonPath 文档语法查询地址
用户详情请求
常用注解

@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 用在实体对象的字段上 用于描述字段含义

正文完
 0