SpringSecurity学习总结-2 第三章 使用SpringMVC开发Restful API_springmvc restful接口开发

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

5、处理创建请求(3-4节内容)

(1)@RequestBody映射请求体到java方法的参数上。

当使用post方法发送请求体数据时,Controller中接收方法的参数上要加@RequestBody来映射请求体到参数上(参数一般是实体类的对象)。

(2)日期类型参数的处理。

前后端使用时间戳来传递时间。

(3)@Valid注解验证请求参数的合法性,BindingResult收集校验结果。

@Valid加在方法的参数上,用来校验前端传来的参数与后台的数据要求是否一致。

BindingResult作为方法的参数,用来收集校验的结果。

6、开发用户信息的修改和删除服务(3-5节内容)

(1)常用的校验注解使用

@NotNull 值不能为空

@Null 值必须为空

@Pattern(regex=) 字符串必须匹配正则表达式

@Email 字符串必须是Email地址

@Size 集合中元素数量必须在min和max之间

等等

(2)自定义校验的提示消息

在加注解时,给message字段设置自定义的校验提示消息,而不是使用默认的提示校验提示信息。例如:@NotBlank(message = “密码不能为空”)

package security.demo.dto;

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import security.demo.validator.MyConstraint;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Past;
import java.util.Date;

//@Data
public class User {

/**

  • id
    */
    @ApiModelProperty(“用户id”)
    private String id;

/**

  • 用户名不能为空
    */
    @MyConstraint(message = “用户名不能为空”)
    @ApiModelProperty(“用户名”)
    private String username;

/**

  • 密码不能为空
    */
    @NotBlank(message = “密码不能为空”)
    @ApiModelProperty(“用户密码”)
    private String password;

/**

  • 必须是一个过去的时间
    */
    @Past(message = “生日必须是一个过去的时间”)
    @ApiModelProperty(“用户生日”)
    private Date birthDay;

/**

  • 声明简单视图接口
    */
    public interface UserSimpleView{};

/**

  • 声明详情视图接口
  • 因为UserDetailView extends UserSimpleView
  • 所以UserDetailView会显示UserSimpleView中显示的所有字段
    */
    public interface UserDetailView extends UserSimpleView{};

public User() {

}

public User(String username, String password) {
this.username = username;
this.password = password;
}

public User(String id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

public User(String id, String username, String password, Date birthDay) {
this.id = id;
this.username = username;
this.password = password;
this.birthDay = birthDay;
}

/**

  • 设定id只在简单视图显示
  • @return
    */
    @JsonView(UserSimpleView.class)
    public String getId() {
    return id;
    }

public void setId(String id) {
this.id = id;
}

/**

  • 设定用户名只在简单视图显示
  • @return
    */
    @JsonView(UserSimpleView.class)
    public String getUsername() {
    return username;
    }

public void setUsername(String username) {
this.username = username;
}

/**

  • 设定密码只在详情视图显示
  • @return
    */
    @JsonView(UserDetailView.class)
    public String getPassword() {
    return password;
    }

public void setPassword(String password) {
this.password = password;
}

/**

  • 设定生日只在简单视图显示
  • @return
    */
    @JsonView(UserSimpleView.class)
    public Date getBirthDay() {
    return birthDay;
    }

public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}

@Override
public String toString() {
return “User{” +
“id='” + id + ‘’’ +
“, username='” + username + ‘’’ +
“, password='” + password + ‘’’ +
“, birthDay=” + birthDay +
‘}’;
}
}

(3)自定义校验注解

①、新建自定义的校验注解

package security.demo.validator;

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.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)//表明这个注解具有校验作用,使用的校验逻辑由validatedBy指定的校验类来执行
public @interface MyConstraint {
/**

  • 校验需要的提示信息
  • @return
    */
    String message();

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

②、新建自定义的校验逻辑类

package security.demo.validator;

import org.springframework.beans.factory.annotation.Autowired;
import security.demo.service.HelloService;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**

  • 自定义的校验类需要实现ConstraintValidator接口
  • MyConstraint:是需要验证的注解
  • String:需要验证的字段类型,这样该注解就只能用在String类型的参数上,如果设置成Object,则任何类型都能使用该注解
    */
    public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {

@Autowired
private HelloService helloService;

/**

  • 校验类初始化时执行
  • @param constraintAnnotation
    */
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
    System.out.println(“my custom validator init”);
    }

/**

  • @param o 需要校验的值
  • @param constraintValidatorContext
  • @return
    */
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
    helloService.greeting(“tom”);
    System.out.println(“需要校验的值:”+o);
    // return true;
    return false;
    }
    }

③、将自定义的校验注解加到需要校验的字段上。

/**

  • 用户名不能为空
    */
    @MyConstraint(message = “用户名不能为空”)
    private String username;

7、Restful API错误处理机制(3-6节内容)

(1)SpringBoot默认的错误处理机制

①: 浏览器端发出的请求,springboot如果处理时有错误,会由BasicErrorController的errorHtml()方法来处理,然后返回页面;

②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有错误,会由BasicErrorController的error()方法来处理,然后返回JSON格式的错误信息;

(2)自定义异常处理

①: 浏览器端发出的请求,出现异常时返回的页面是在**src/main/resources/templates/error/**404.html配置,状态码是多少的就配置以该状态码为名称的页面,并在页面中设置返回的信息。

②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有异常的情况处理方法:

步骤一:创建一个异常处理类

package security.demo.exception;

import java.io.Serializable;

/**

  • 自定义的处理用户不存在的异常处理
    */
    public class UserNotExistException extends RuntimeException implements Serializable {

private static final long serialVersionUID = -4253671095865255861L;

private String id;

public UserNotExistException(String id) {
super(“User Not Exist!”);
this.id = id;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}

步骤二、创建一个异常的拦截处理器

package security.demo.web.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import security.demo.exception.UserNotExistException;

import java.util.HashMap;
import java.util.Map;

/**

  • ControllerAdvice:处理Controller抛出的异常
    */
    @ControllerAdvice
    public class ControllerExceptionHandler {

/**

  • 处理抛出来的UserNotExistException类型的异常
  • @param exception
  • @return
    */
    @ExceptionHandler(UserNotExistException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String,Object> handlerUserNotExistException(UserNotExistException exception){
    Map<String,Object> result = new HashMap<>();
    result.put(“id”,exception.getId());
    result.put(“message”,exception.getMessage());

return result;
}

}

只要springboot方法里抛出UserNotExistException类型的异常就会由ControllerExceptionHandler配置的**handlerUserNotExistException()**方法来处理。

8、Restful API的拦截(以拦截记录时间为例)(3-7、8节内容)

(1)过滤器(Filter)

优点: filter里面是能够获取到(HttpServletRequest request)和响应(HttpServletResponse response),从request中也能获取到传入的参数信息;

缺点:无法知道是哪一个Controller类中的哪个方法被执行。

Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。

1.启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;

2.每一次请求时都只调用方法doFilter()进行处理;

3.停止服务器时调用destroy()方法,销毁实例。

方式一:实现Filter类(有@Component注解

package security.demo.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

/**

  • @Component 这个注解的目的是将TimeFilter交给容器来管理。
  • 时间过滤器
    */
    @Component
    public class TimeFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(“时间过滤器初始化!”);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(“时间过滤器开始执行过滤!”);
// long startTime = new Date().getTime();
long startTime = System.currentTimeMillis();

//执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("TimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
System.out.println(“时间过滤器结束执行过滤!”);
}

@Override
public void destroy() {
System.out.println(“销毁时间过滤器!”);
}
}

方式二:实现Filter((无**@Component注解**)

①先定义一个没有@Component注解的过滤器

package security.demo.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

/**

  • 没有@Component注解的过滤器
    */
    public class OtherTimeFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(“OtherTimeFilter初始化!”);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(“OtherTimeFilter开始执行过滤!”);
long startTime = System.currentTimeMillis();

//执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("OtherTimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
System.out.println(“OtherTimeFilter结束执行过滤!”);
}

@Override
public void destroy() {
System.out.println(“销毁OtherTimeFilter!”);
}
}

②使用Filter配置类来管理没有@Component注解的过滤器

package security.demo.web.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import security.demo.web.filter.OtherTimeFilter;

/**

  • Filter:无法获取处理请求的方法信息
  • springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
  • 配置类
    */
    @Configuration
    public class WebFilterConfig {

/**

  • FilterRegistrationBean是用来注册Filter的类
  • @return
    */
    @Bean
    public FilterRegistrationBean otherTimeFilter(){

FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将自定义的没有@Component注解的过滤器注册进来
registrationBean.setFilter(new OtherTimeFilter());
//设置过滤顺序
registrationBean.setOrder(2);
//设置过滤器的名字
registrationBean.setName(“OtherTimeFilter”);
//设置需要过滤的地址
registrationBean.addUrlPatterns(“/*”);
return registrationBean;

}

}

方式一和方式二的区别:

方式二没有使用@Component注解,而是使用配置类来注册过滤器,这样的好处正如在过滤器注册时的设置一样清晰:

①:可以设置过滤器在过滤器链的位置(顺序);

②:可以设置过滤器在过滤器链中的名称;

③:可以设置过滤器拦截的规则;

而使用@Component注解的过滤器则无法更好的实现上述细节。

(2)拦截器(Interceptor)

优点:  可获请求(HttpServletRequest request)和响应(HttpServletResponse response)对象,也可获取方法所在类的类名及方法名信息**。**

MethodParameter[] methodParameters = ((HandlerMethod)handler).getMethodParameters();
for (MethodParameter methodParameter : methodParameters) {
String parameterName = methodParameter.getParameterName();
// 只能获取参数的名称,不能获取到参数的值
//System.out.println("parameterName: " + parameterName);
}

缺点:从handler对象中只能获取参数的名称,不能获取到参数的值,所以无法获取处理请求的方法里的形参信息,(从request中可以间接能获取到传入的参数信息)

它比filter的执行优先级低。

步骤一:需要先实现HandlerInterceptor

package security.demo.web.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**

  • Interceptor:可获取处理请求的方法信息
    */
    @Component
    public class TimeInterceptor implements HandlerInterceptor {

/**

  • 处理请求的方法执行前运行此方法
  • @param request
  • @param response
  • @param handler 处理请求的方法
  • @return
  • @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println(“preHandle方法执行”);
    System.out.println("处理请求的方法的类名: "+((HandlerMethod)handler).getBean().getClass().getName());
    System.out.println("处理请求的方法名: "+((HandlerMethod)handler).getMethod().getName());
    request.setAttribute(“startTime”,System.currentTimeMillis());

return true;
}

/**

  • 处理请求的方法执行后运行此方法
  • @param request
  • @param response
  • @param handler
  • @param modelAndView
  • @throws Exception
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println(“postHandle方法执行”);
    Long startTime = (Long)request.getAttribute(“startTime”);
    System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));

}

/**

  • 处理请求的方法执行完毕后运行此方法
  • @param request
  • @param response
  • @param handler
  • @param exception
  • @throws Exception
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
    System.out.println(“afterCompletion方法执行”);
    Long startTime = (Long)request.getAttribute(“startTime”);
    System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));

System.out.println(“exception is :”+exception);
}
}

步骤二:新建一个Web的配置类继承WebMvcConfigurationSupport

package security.demo.web.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import security.demo.web.filter.OtherTimeFilter;
import security.demo.web.interceptor.TimeInterceptor;

/**

  • Filter:无法获取处理请求的方法信息
  • springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
  • 配置类:配置过滤器和拦截器,如果同时配置了拦截器和过滤器,二者都会起作用
    */
    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport {

@Autowired
private TimeInterceptor timeInterceptor;

/**

  • FilterRegistrationBean是用来注册Filter的类
  • @return
    */
    @Bean
    public FilterRegistrationBean otherTimeFilter(){

FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将自定义的没有@Component注解的过滤器注册进来
registrationBean.setFilter(new OtherTimeFilter());
//设置过滤顺序
registrationBean.setOrder(2);
//设置过滤器的名字
registrationBean.setName(“OtherTimeFilter”);
//设置需要过滤的地址
registrationBean.addUrlPatterns(“/*”);
return registrationBean;

}

/**

  • 注册拦截器
  • @param registry
    */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(timeInterceptor);
    }
    }
(3)切片(Aspect)(3-8节内容) (最常使用)

优点: 可以获取方法信息及方法里的参数信息。

缺点: 无法直接获取方法的请求及响应对象。

(通过下面代码可以间接获取方法的请求及响应对象)

ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attribute.getRequest();
HttpServletResponse response = attribute.getResponse();

常使用在日志,事务,请求参数安全验证等

package security.demo.web.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeAspect {

/**

  • (* security.demo.web.controller.UserController.*(…)):
  • 第一个星号表示任何类型的返回值,第二个星号是UserController类里任何的方法
  • (…):表示方法里的任何参数
  • 整个的意思是作用在UserController的任何方法上,且不论方法的参数是什么,并且不管返回的是什么类型的方法上(即UserController的所有方法上)
  • ProceedingJoinPoint:ProceedingJoinPoint的对象包含了被拦截的方法的所有信息
    /
    @Around("execution(
    security.demo.web.controller.UserController.*(…))")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println(“time aspect start!”);
    //获取被拦截的方法
    Object proceed = pjp.proceed();
    //获取方法的参数的数组
    Object[] args = pjp.getArgs();
    for(Object arg : args){
    System.out.println("参数是: "+arg);
    }
    System.out.println(“time aspect end!”);

return proceed;
}

}

三者在程序中的执行顺序:

①Filter  ②Interceptor  (③ControllerAdvice)  ④Aspect

执行后的返回顺序:

controller -> aspect -> controllerAdvice -> Interceptor -> Filter

无

9、文件的上传下载

下载功能需要加入IO的jar包

commons-io commons-io 2.5

代码:

package security.demo.web.controller;

import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import security.demo.dto.FileInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@RestController
@RequestMapping(“/file”)
public class FileController {

/**

  • 文件上传
  • @param file
  • @return
    */
    @PostMapping
    public FileInfo update(MultipartFile file){
    //上传时参数的名字
    System.out.println(file.getName());
    //原始的文件名
    String fileOriginalName = file.getOriginalFilename();
    //文件的后缀名
    String fileSuffixName = fileOriginalName.substring(fileOriginalName.lastIndexOf(“.”));
    //文件的大小
    System.out.println(file.getSize());
    String folder = “自己指定一个路径”;

File uploadPath = new File(folder);
if(!uploadPath.exists()){
uploadPath.mkdirs();
}

File saveFilePath = new File(folder, UUID.randomUUID().toString()+fileSuffixName);
try {
file.transferTo(saveFilePath);
} catch (IOException e) {
e.printStackTrace();
}

FileInfo fileInfo = new FileInfo(saveFilePath.getAbsolutePath());
return fileInfo;
}

/**

  • 文件下载,因为是写在响应response中的,所以是无返回值的
  • @param request
  • @param response
  • @param id
    */
    @GetMapping(“/{id}”)
    public void download(HttpServletRequest request, HttpServletResponse response, @PathVariable(“id”) String id) throws IOException {

//输入流
InputStream inputStream = new FileInputStream(new File(“文件存储路径”,“文件的名字(包含后缀)”));
//输出流
OutputStream outputStream = response.getOutputStream();
//设置响应中的内容类型为下载类型
response.setContentType(“application/x-download”);
//设置下载时文件的名字
response.addHeader(“Content-Disposition”,“attachment;filename=(设定的文件名(包含后缀名))”);
IOUtils.copy(inputStream,outputStream);

outputStream.flush();
//关闭流
inputStream.close();
outputStream.close();
}

}

10、异步处理REST服务

(1)使用Runnable异步处理Rest服务

在主线程中使用Callable来发起一个副线程来执行耗时的逻辑。(需要注意的是副线程必须要由主线程发起调用,即主副线程之间必须要由关联)

package security.demo.web.async;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
nse response, @PathVariable(“id”) String id) throws IOException {

//输入流
InputStream inputStream = new FileInputStream(new File(“文件存储路径”,“文件的名字(包含后缀)”));
//输出流
OutputStream outputStream = response.getOutputStream();
//设置响应中的内容类型为下载类型
response.setContentType(“application/x-download”);
//设置下载时文件的名字
response.addHeader(“Content-Disposition”,“attachment;filename=(设定的文件名(包含后缀名))”);
IOUtils.copy(inputStream,outputStream);

outputStream.flush();
//关闭流
inputStream.close();
outputStream.close();
}

}

10、异步处理REST服务

(1)使用Runnable异步处理Rest服务

在主线程中使用Callable来发起一个副线程来执行耗时的逻辑。(需要注意的是副线程必须要由主线程发起调用,即主副线程之间必须要由关联)

package security.demo.web.async;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-wMs5zuBt-1713162396009)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值