网站建设案例,建筑工程完工后移交情况说明,建设部网站公告,移动端响应式布局目录
1.统一用户登录权限效验
1.1.最初用户登录验证
1.2.Spring AOP用户统一登录验证的问题
1.3.Spring拦截器
1.3.1.创建自定义拦截器#xff0c;实现 HandlerInterceptor 接口并重写 preHandle#xff08;执行具体方法之前的预处理#xff09;方法
1.3.2.将自定义拦…目录
1.统一用户登录权限效验
1.1.最初用户登录验证
1.2.Spring AOP用户统一登录验证的问题
1.3.Spring拦截器
1.3.1.创建自定义拦截器实现 HandlerInterceptor 接口并重写 preHandle执行具体方法之前的预处理方法
1.3.2.将自定义拦截器加入到系统配置WebMvcConfigurer 的 addInterceptors 方法中并设置相应的拦截规则
排除所有的静态资源
1.4.拦截器实现原理
1.4.1.实现原理源码分析
1.4.2.拦截器小结
1.5.扩展统一访问前缀添加
2.统一异常处理
2.1.创建统一异常类并标识当前的类为ControllerAdvice
2.2.实现异常处理的方法可以定义多个在方法上添加ExceptionHandler注解异常类型
3.统一数据返回格式
3.1.为什么需要统一数据返回格式
3.2.统一数据返回格式的实现
3.2.1.标识当前的类为ControllerAdvice并重写ResponseBodyAdvice接口
3.2.2.实现重写的方法封装统一返回的方法
3.3.ControllerAdvice源码分析
4.总结 下面进入AOP实战环节~
1.统一用户登录权限效验
1.1.最初用户登录验证
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;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;}}//其他方法...
}每个⽅法中都有相同的⽤户登录验证权限缺点
每个⽅法中都要单独写⽤户登录验证的⽅法即使封装成公共⽅法也⼀样要传参调⽤和在⽅法中进⾏判断。 添加控制器越多调⽤⽤户登录验证的⽅法也越多这样就增加了后期的修改成本和维护成本。这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联但每个⽅法中都要写⼀遍。所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫。 1.2.Spring AOP用户统一登录验证的问题 能想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现 import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;Aspect
Component
public class UserAspect {// 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法Pointcut(execution(* com.example.demo.controller..*.*(..)))public void pointcut() { }// 前置⽅法Before(pointcut())public void doBefore() {}// 环绕⽅法Around(pointcut())public Object doAround(ProceedingJoinPoint joinPoint) {Object obj null;System.out.println(Around ⽅法开始执⾏);try {// 执⾏拦截⽅法obj joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println(Around ⽅法结束执⾏);return obj;}
} 如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能有两个问题 没办法获取到 HttpSession 对象。 要对⼀部分⽅法进⾏拦截⽽另⼀部分⽅法不拦截如注册⽅法和登录⽅法是不拦截的这样排除⽅法的规则很难定义甚至没办法定义。 如何解决呢 1.3.Spring拦截器 对以上问题 Spring 中提供了具体的实现拦截器对AOP的扩展HandlerInterceptor拦截器的实现分为以下两个步骤 1.3.1.创建自定义拦截器实现 HandlerInterceptor 接口并重写 preHandle执行具体方法之前的预处理方法 使⽤代码来实现⼀个⽤户登录的权限效验⾃定义拦截器是⼀个普通类 import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/*** 创建一个登录的拦截器*/
Component
public class LoginInterceptor implements HandlerInterceptor {//返回true表示验证通过可以执行后面的方法返回false表示验证失败后面的代码就不能执行了Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session request.getSession(false); //有会话使用会话没有会话不创建会话。因为验证可能发生在任何阶段有可能没有登录直接验证此时没有会话就不去创建新的会话。只有登录成功会创建会话其他情况都不创建会话。if(session ! null session.getAttribute(userinfo) ! null) {//表示用户已登录return true;}//执行到此行表示验证未通过自动跳转到登录页面response.sendRedirect(/login.html);return false;}
}1.3.2.将自定义拦截器加入到系统配置WebMvcConfigurer 的 addInterceptors 方法中并设置相应的拦截规则 import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;Configuration
public class AppConfig implements WebMvcConfigurer {Autowiredprivate LoginInterceptor loginInterceptor; //写法1 推荐使用/*** 给当前项目添加拦截器** param registry*/Overridepublic void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginInterceptor()); //写法2 不推荐使用registry.addInterceptor(loginInterceptor) //①拦截的具体业务实现.addPathPatterns(/**) //拦截所有接口 拦截所有的url ②拦截规则.excludePathPatterns(/user/reg) //排除接口 不拦截/user/reg ③不拦截规则.excludePathPatterns(/**/*.html); //不拦截所有的html页面//对应支持注册多个拦截器的若有多个拦截器继续按上面的写下去即可//registry.addInterceptor(loginInterceptor2)// .addPathPatterns(/**) // ...}
} addPathPatterns表示需要拦截的 URL“**”表示拦截任意⽅法也就是所有⽅法。 excludePathPatterns表示需要排除的 URL。 说明以上拦截规则可以拦截此项⽬中的使⽤ URL包括静态⽂件图⽚⽂件、JS 和 CSS 等⽂件。 验证未登录前 实体类 import lombok.Data;Data
public class UserInfo {private int id;private String username;private String password;
} UserController类 import com.example.demo.model.UserInfo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RequestMapping(/user)
RestController
public class UserController {/*** 用户注册功能** param userInfo* return*/RequestMapping(/reg)public boolean reg(UserInfo userInfo) {//伪代码return true;}/*** 上传头像功能** return*/RequestMapping(/upload)public UserInfo upload() {UserInfo userInfo new UserInfo();userInfo.setId(1);userInfo.setUsername(张三);userInfo.setPassword();return userInfo;}
}login.html文件 !doctype html
html langen
headmeta charsetUTF-8meta nameviewportcontentwidthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0meta http-equivX-UA-Compatible contentieedgetitle登录页面/title
/head
body
h1登录页面/h1
/body
/html验证登录后 import lombok.Data;Data
public class UserInfo {private int id;private String username;private String password;public UserInfo() {}public UserInfo(int id, String username, String password) {this.id id;this.username username;this.password password;}
} import com.example.demo.model.UserInfo;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;RequestMapping(/user)
RestController
public class UserController {/*** 用户注册功能** param userInfo* return*/RequestMapping(/reg)public boolean reg(UserInfo userInfo) {//伪代码return true;}/*** 上传头像功能** return*/RequestMapping(/upload)public UserInfo upload() {UserInfo userInfo new UserInfo();userInfo.setId(1);userInfo.setUsername(张三);userInfo.setPassword();return userInfo;}RequestMapping(/login)public boolean login(String username, String password, HttpServletRequest request) {//非空效验if(StringUtils.hasLength(username) StringUtils.hasLength(password)) {if(username.equals(admin) password.equals(admin)) {//用户名和密码正确登录成功//添加sessionHttpSession session request.getSession(true); //如果有session就用session没session就创建sessionsession.setAttribute(userinfo, new UserInfo(1, admin, admin)); //一定要和拦截器里的key值保持一致return true;}}return false;}
}import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;Configuration
public class AppConfig implements WebMvcConfigurer {Autowiredprivate LoginInterceptor loginInterceptor;/*** 给当前项目添加拦截器** param registry*/Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns(/**) //拦截所有的url.excludePathPatterns(/user/reg) //不拦截注册接口/user/reg.excludePathPatterns(/user/login) //不拦截登录接口 /user/login.excludePathPatterns(/**/*.html); //不拦截所有的html页面}
}排除所有的静态资源 Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns(/**) // 拦截所有接⼝.excludePathPatterns(/**/*.js).excludePathPatterns(/**/*.css).excludePathPatterns(/**/*.jpg).excludePathPatterns(/login.html).excludePathPatterns(/**/login); // 排除接⼝
} 1.4.拦截器实现原理 正常情况下的调⽤顺序 然⽽有了拦截器之后会在调⽤ Controller 之前进⾏相应的业务处理执⾏的流程如下图所示 1.4.1.实现原理源码分析 所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现这⼀点可以从 Spring Boot 控制台的打印信息看出 ⽽所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法doDispatch源码如下 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest request;HandlerExecutionChain mappedHandler null;boolean multipartRequestParsed false;WebAsyncManager asyncManager WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv null;Object dispatchException null;try {processedRequest this.checkMultipart(request);multipartRequestParsed processedRequest ! request;mappedHandler this.getHandler(processedRequest);if (mappedHandler null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha this.getHandlerAdapter(mappedHandler.getHandler());String method request.getMethod();boolean isGet HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) isGet) {return;}}//调用预处理 执行的就是拦截器 重点if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//执行Controller中的业务mv ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException var20;} catch (Throwable var21) {dispatchException new NestedServletException(Handler dispatch failed, var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException(Handler processing failed, var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler ! null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}从上述源码可以看出在开始执⾏ Controller 之前会先调⽤ 预处理⽅法 applyPreHandle⽽applyPreHandle ⽅法的实现源码如下 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i 0; i this.interceptorList.size(); this.interceptorIndex i) {HandlerInterceptor interceptor (HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;
} 从上述源码可以看出在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle ⽅法这样就和前⾯定义的拦截器对应上了 此时⽤户登录权限的验证⽅法就会执⾏这就是拦截器的实现原理。 1.4.2.拦截器小结 通过上⾯的源码分析可以看出Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的⼤体的调⽤流程如下 1.5.扩展统一访问前缀添加 import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 用户登录拦截器实现*/
Configuration
public class AppConfig implements WebMvcConfigurer {Autowiredprivate LoginInterceptor loginInterceptor;/*** 给当前项目添加拦截器** param registry*/Overridepublic void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(loginInterceptor)
// .addPathPatterns(/**) //拦截所有的url
// .excludePathPatterns(/user/reg) //不拦截注册接口/user/reg
// .excludePathPatterns(/user/login) //不拦截登录接口 /user/login
// .excludePathPatterns(/**/*.html); //不拦截所有的html页面}/*** 给所有的服务添加api前缀* param configurer*/Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix(api, c - true); //其中第⼆个参数是⼀个表达式设置为 true 表示启动前缀。}
}2.统一异常处理 ①算术异常ArithmeticException 前端是使用ajax请求的得到一个500前端不知道是什么情况。因为500的success不会走success的回调方法不会走回调方法意味着前端会一直等待后端的返回但后端一直没有返回。这样的报错信息前端懵了用户更懵。 实现了统一异常处理--不管后端出啥问题把这个问题以前端能够看得懂的方式json的方式返回给前端前端拿到这个消息至少会走success方法可判断success里的状态等等。还可以在拦截到某个异常之后把这个异常以短信的形式发送给研发/运维人员进而解决问题。 统⼀异常处理使⽤的是 ControllerAdvice ExceptionHandler 来实现的。 ControllerAdvice 表示控制器通知类对当前的控制器进行功能增强的ExceptionHandler 是异常处理器两个结合表示当出现异常的时候执⾏某个通知也就是执⾏某个⽅法事件。 2.1.创建统一异常类并标识当前的类为ControllerAdvice 2.2.实现异常处理的方法可以定义多个在方法上添加ExceptionHandler注解异常类型 还是出错了但此时出错信息被封装成了一个json数据前端会走success代码发现状态state不等于1说明操作失败了查看原因发现除数为0了用户就不会懵逼了。 ②空指针异常NullPointerException ③类型转换异常 可以把典型的一些异常分别定义出来其他的异常定义为默认异常/自定义异常。当遇到一个异常没有定义时会去走默认异常。 import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;/*** 统一异常错误处理类*/
ControllerAdvice //从业务来说表示当前类为统一异常封装类这个注解本身表示对当前的控制器进行功能增强的
ResponseBody
public class MyExceptionResult {/*** 算术异常** param e* return*/ExceptionHandler(ArithmeticException.class)public HashMapString, Object myArithmeticException(ArithmeticException e) {HashMapString, Object result new HashMapString, Object();result.put(state, -1);result.put(msg, 算术异常 e.getMessage());result.put(data, null);return result;}/*** 空指针异常** param e* return*/ExceptionHandler(NullPointerException.class)public HashMapString, Object myNullPointerException(NullPointerException e) {HashMapString, Object result new HashMapString, Object();result.put(state, -1);result.put(msg, 空指针异常 e.getMessage());result.put(data, null);return result;}/*** 默认异常** param e* return*/ExceptionHandler(Exception.class)public HashMapString, Object myException(Exception e) {HashMapString, Object result new HashMapString, Object();result.put(state, -1);result.put(msg, 默认异常 e.getMessage());result.put(data, null);return result;}
}注 ⽅法名和返回值可以⾃定义其中最重要的是 ExceptionHandler(Exception.class) 注解。 以上⽅法表示如果出现了异常就返回给前端⼀个 HashMap 的对象其中包含的字段如代码中定义的那样。 可以针对不同的异常返回不同的结果。当有多个异常通知时匹配顺序为当前类及其⼦类向上依次匹配即当既有空指针异常也有默认异常时会优先走空指针异常。也就是优先会走子类而不是父类。3.统一数据返回格式 3.1.为什么需要统一数据返回格式 统⼀数据返回格式的优点有很多比如以下几个 ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。 降低前端程序员和后端程序员的沟通成本按照某个格式实现就⾏了因为所有接⼝都是这样返回的。 有利于项⽬统⼀数据的维护和修改。 有利于后端技术部⻔的统⼀规范的标准制定不会出现稀奇古怪的返回内容。 3.2.统一数据返回格式的实现 统⼀的数据返回格式可以使⽤ ControllerAdvice ResponseBodyAdvice 的⽅式实现 3.2.1.标识当前的类为ControllerAdvice并重写ResponseBodyAdvice接口 3.2.2.实现重写的方法封装统一返回的方法 import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;/*** 统一数据返回封装*/
ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {/*** 内容是否需要重写通过此方法可以选择部分控制器和方法进行重写* 返回true表示重写* * param returnType* param converterType* return*/Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/*** 方法返回之前调用此方法* * param body* param returnType* param selectedContentType* param selectedConverterType* param request* param response* return*/Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMapString, Object result new HashMap();result.put(sate, 1);result.put(data, body);result.put(msg, );return result;}
}3.3.ControllerAdvice源码分析 通过对 ControllerAdvice 源码的分析可知上⾯统⼀异常和统⼀数据返回的执⾏流程先从 ControllerAdvice 的源码看起 可以看出 ControllerAdvice 派⽣于 Component 组件⽽所有组件初始化都会调⽤InitializingBean 接⼝。InitializingBean 有哪些实现类其中 Spring MVC 中的实现⼦类是 RequestMappingHandlerAdapter它⾥⾯有⼀个⽅法 afterPropertiesSet() ⽅法表示所有的参数设置完成之后执⾏的⽅法 ⽽这个⽅法中有⼀个 initControllerAdviceCache ⽅法查询此⽅法的源码如下 发现这个⽅法在执⾏是会查找所有使用 ControllerAdvice 注解的类这些类会在容器中当发⽣某个事件时调⽤相应的 Advice 方法⽐如返回数据前调⽤统⼀数据封装⽐如发⽣异常是调⽤异常的 Advice ⽅法实现。 4.总结 统⼀⽤户登录权限的效验使⽤ WebMvcConfigurer HandlerInterceptor来实现统⼀异常处理使⽤ ControllerAdvice ExceptionHandler 来实现统⼀返回值处理使⽤ ControllerAdvice ResponseBodyAdvice 来处理