目录
一、用户登录权限效验
1、Spring拦截器实现用户统一登录验证(重要)
(1)定义一个拦截器
(2)将自定义拦截器加入到系统配置中
2、拦截器实现原理
3、统一前缀加api访问
4、统一异常处理
5、统一数据返回格式
(1)基础版方法:
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 某⽅法 1
*/
@RequestMapping("/m1")
public Object method(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
/**
* 某⽅法 2
*/
@RequestMapping("/m2")
public Object method2(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
// 其他⽅法...
}
从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:
(1)每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也⼀样要传参调⽤和在方法中进行判断。
(2)添加控制器越多,调⽤⽤户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
(3)这些⽤户登录验证的方法和接下来要实现的业务没有任何关联,但每个方法中都要写⼀遍。总结就是太麻烦啦~所以需要提供⼀个公共的 AOP 方法来进⾏统⼀的用户登录权限验证。
(2)中等版方法:利用上一节学习的 Spring AOP的前置通知或者环绕通知实现:
缺点:
如果要在Spring AOP的切面中实现用户权限的登录,有两个问题:
(1) 没办法获取到 HttpSession 对象。
(2)我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,这样的话规则很难去定义。比如我们要求注册方法和登录方法不拦截,这个时规则的定义就比较麻烦。
(3)最终版方法:基于Spring AOP的Spring 拦截器
Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为两步:
(1)创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
(2)将自定义拦截器加入WebMvcConfigurer 的 addInterceptors 方法中。接下来我们通过Spring 拦截器来实现一下用户登录验证问题吧~
@Slf4j
@RequestMapping("/aop")
@RestController
public class UserController {
//模拟写博客
@RequestMapping("/writeBlog")
public String writeBlog(){
log.info("write blog success...");
return "write blog success...";
}
//编辑博客
@RequestMapping("/editBlog")
public String editBlog(){
log.info("edit blog success...");
return "edit blog success...";
}
//删除博客
@RequestMapping("/deleteBlog")
public String deleteBlog(){
log.info("deleteBlog success...");
return "deleteBlog success...";
}
//注册博客
@RequestMapping("/regBlog")
public String regBlog(){
log.info("regBlog success...");
return "regBlog success...";
}
//登录博客
@RequestMapping("/loginBlog")
public boolean loginBlog1(HttpServletRequest request,String username,String password)
{
log.info("login ing...");
//判断username和password是否为空
//为空
if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return false;
}
//如果账号和密码不为admin,就返回false
if(!"admin".equals(username) || !"admin".equals(password)){
return false;
}
//不为空:传入true,如果没有就创建session
HttpSession session = request.getSession(true);
//设置session
session.setAttribute("username",username);
return true;
}
}
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否登录:如果session为空,不去创建
HttpSession session = request.getSession(false);
if(session != null && session.getAttribute("username")!=null){
//说明是登录状态,不进行拦截
return true;
}
//否则说明不是登录状态,直接返回:给出提示:没有权限登录
response.setStatus(401);
return false;
}
}
//实现WebMvcConfigurer
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//重写addInterceptors方法
@Override
public void addInterceptors(InterceptorRegistry registry){
//添加拦截器
//先要有一个拦截器对象:方式1:直接new出来
// LoginInterceptor loginInterceptor = new LoginInterceptor();
//方式2:通过注解的方式
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//表示在未登录状态下拦截所有
.excludePathPatterns("/aop/loginBlog")//表示在未登录状态下loginBlog方法仍然可以访问。(也就是排除掉一些方法)
.excludePathPatterns("/aop/regBlog");//表示排除掉一些方法
}
}
1、拦截规则可以拦截此项目中的使用URL,包括静态文件(图片、JS 和 CSS 等文件)
2、关于对象:
3、我们上方的代码主要干了什么?
(1)首先我们在定义LoginInterceptor拦截器的前提是:session中没有值,那么除loginBlog和regBlog之外的所有方法都要被拦截成功。
(2)之后我们在UserController 类的loginBlog方法中,模拟用户登录,输入用户名和密码为admin,然后将username值设置到session中。那么此时session中就有值了,此时不进行拦截,理论上所有的方法都应该被访问成功。
那么我们测试一下,是不是成功了呢?
测试效果:
正常的访问路径
有了拦截器之后,会在调用 Controller 之前就进行相应的业务处理:
Spring 中的拦截器也是通过动态代理和环绕通知的思想实现。(拦截器是基于AOP实现的,Spring是基于Servlet实现的。)
所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出。而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,从上述源码可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle 方法,这样就会我们之前自定义的拦截器对应上,此时用户登录权限的验证方法就会执行 ,这就是拦截器的实现原理。
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接⼝添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
@Slf4j
@ResponseBody
@RestController
@RequestMapping("/web")
public class ErrorController {
//算数异常
@RequestMapping("/test01")
public boolean test01(){
int a = 10/0;
return true;
}
//空指针异常
@RequestMapping("/test02")
public boolean test02(){
String str = null;
System.out.println(str.length());
return true;
}
//手动抛出异常
@RequestMapping("/test03")
public boolean test03(){
throw new RuntimeException("test03手动抛出异常");
}
}
此时访问test01/test02/test03都访问出错,统一报错如下。
此时,我想要针对不同的错误显示不同的异常信息,就用到了统一异常处理。
(1)统一异常处理使用@ControllerAdvice和@ExceptionHandler实现;
(2)@ControllerAdvice表示控制器通知类;
(3)@ExceptionHandler表示异常处理器;
(4)两个注解结合表示出现异常的时候执行某个通知,也就是某个方法事件;
(5)注意最上方加@ResponseBoby,表示返回的是数据格式。
(6)方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
//@ControllerAdvice 表示控制器通知类
@ControllerAdvice
@ResponseBody
public class ErrorHandler {
//@ExceptionHandler 是异常处理器
//两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件
//当没有具体的异常时,就匹配这个异常
@ExceptionHandler
public Object error(Exception e){
HashMap result = new HashMap<>();
result.put("success",0);
result.put("code",-1);//业务码
result.put("msg","Exception...");
return result;
}
//(1)表示捕获算数异常
@ExceptionHandler
public Object error(ArithmeticException e){
HashMap result = new HashMap<>();
result.put("success",0);
result.put("code",-1);//业务码
result.put("msg","ArithmeticException...");
return result;
}
//(2)表示捕获空指针异常
@ExceptionHandler
public Object error(NullPointerException e){
HashMap result = new HashMap<>();
result.put("success",0);
result.put("code",-1);//业务码
result.put("msg","NullPointerException...");
return result;
}
}
一般我们前后端交互,都需要知道这几点:
(1)业务处理是否成功;
(2)如果成功,业务执行的结果 是什么;
(3)如果失败,失败原因是什么。
对应下面三点:
(1)HTTP状态码
(2)code:业务状态码;
(3)data:业务处理成功,执行返回的结果
(4)msg:业务失败,返回的结果是什么。
统一数据返回格式的好处:方便前端更好的接受和解析后端数据接口返回的数据。
@Slf4j
@RequestMapping("/web")
@RestController
public class UserController {
//测试boolean类型
@RequestMapping("/test01")
public boolean test01(){
log.info("test01 ...");
return true;
}
//测试String类型
@RequestMapping("/test02")
public String test02(){
log.info("test02 ...");
return "test02...";
}
}
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap result = new HashMap<>();
result.put("success",1);
result.put("data",body);
result.put("errMsg","");
return result;
}
}
(1)如果方法返回的类型是boolean成功。
(2) 如果方法返回的类型是String,报错。
@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows//处理异常
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap result = new HashMap<>();
result.put("success",1);
result.put("data",body);
result.put("errMsg","");
if (body instanceof String) {
//解决返回值为字符串时,直接返回,不用包装
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(result);
}
return result;
}
}
小Tips:
(1)统一数据返回格式用 @ControllerAdvice + ResponseBodyAdvice 实现;
(2)@ResponseBodyAdvice 要重写两个方法,其中supports方法表示内容是否需要重写,返回true表示需要重写;
(3)在处理正常数据时,使用hashMap返回;
(4)处理String类型的数据时,直接手动封装返回对象转成json字符串返回;
(5)@SneakThrows相当于tryCatch用来处理异常的。
SpringBoot统一返回处理出现cannot be cast to java.lang.String异常_伏加特遇上西柚的博客-CSDN博客后端服务使用Restful API的形式,前后端得规范一般是json格式,如果返回的是字符串直接手动封装返回对象转成json字符串返回即可。当返回的数据是字符串时,此处得方法是要去循环遍历。异常,返回其他类型就无任何问题。当返回的数据是非字符串时使用的。会先被遍历到,这时会认为。默认会注册一些自带的。_cannot be cast to java.lang.stringhttps://blog.csdn.net/weixin_43811057/article/details/127655041
SpringBoot实现统一功能处理的教程详解_java_脚本之家这篇文章主要为大家详细介绍了SpringBoot如何实现统一功能处理,文中的示例代码讲解详细,对大家学习或工作有一定借鉴价值,感兴趣的同学可以参考阅读下https://www.jb51.net/program/28588440v.htm