Spring Security專案Spring MVC開發RESTful API(二)

GuoSh發表於2019-03-30

查詢請求

常用註解

  • @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);
    }
複製程式碼

自定義註解

image.png

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;
    }
複製程式碼

檢視返回的異常資訊

image.png

處理狀態碼錯誤

建立檔案結構如下404錯誤將跳轉對應頁面

image.png

RESTful API的攔截

過濾器(Filter)

image.png

建立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標籤去除

image.png

@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)

image.png

建立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檔案

image.png

@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)

image.png

@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的配置類

image.png


@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 用在實體物件的欄位上 用於描述欄位含義

相關文章