穿越:從0開始,構建先後端分離應用html
攔截器是web項目不可或缺的組成部分,通常使用攔截器實現如下功能java
一、登陸session驗證web
防止瀏覽器端繞過登陸,直接進入到應用spring
或者session超時後,返回到登陸頁面數據庫
二、記錄系統日誌apache
一個完善的應用系統,應該具有監控功能,經過完善的系統日誌記錄系統運行過程當中都經歷了什麼,當發生錯誤的時候及時通知管理人員,將損失降到最低。同時經過系統日誌的監控,也能監控每次訪問的響應時長,做爲性能調優的參考後端
三、對請求進行前置或後置的操做跨域
好比對於服務端返回的異常信息,能夠經過攔截器統一的進行後處理,使其格式統一瀏覽器
有兩種方式spring-mvc
下面分享一下攔截器,在個人項目中是如何使用的。
我分別用基於Spring AOP的攔截器實現了登陸驗證及系統日誌
使用基於Servlet規範的攔截器實現了跨域請求
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
1 package com.wt.common.security.interceptor; 2 3 import com.wt.common.core.annotations.IgnoreAuth; 4 import com.wt.common.core.result.HttpResultEntity; 5 import com.wt.common.core.result.HttpResultHandle; 6 import com.wt.common.core.utils.ServletNativeObjectUtil; 7 import com.wt.common.security.handler.HttpSessionHandler; 8 import com.wt.common.security.model.SysUser; 9 import org.aspectj.lang.ProceedingJoinPoint; 10 import org.aspectj.lang.annotation.Around; 11 import org.aspectj.lang.annotation.Aspect; 12 import org.aspectj.lang.reflect.MethodSignature; 13 import org.springframework.core.annotation.Order; 14 import org.springframework.stereotype.Component; 15 16 import javax.servlet.http.HttpServletRequest; 17 import java.lang.reflect.Method; 18 19 /** 20 * @ProjectName: syInfo 21 * @Package: com.wt.common.core.interceptor 22 * @Description: 23 * @Author: lichking2017@aliyun.com 24 * @CreateDate: 2018/5/16 上午8:20 25 * @Version: v1.0 26 */ 27 28 @Component 29 @Order(1) 30 @Aspect 31 public class LoginInterceptor { 32 // Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); 33 34 @Around("@within(org.springframework.web.bind.annotation.RestController)") 35 public HttpResultEntity loginCheck(ProceedingJoinPoint pjp) throws Throwable { 36 HttpServletRequest request = ServletNativeObjectUtil.getRequest(); 37 SysUser loginUser = (SysUser) request.getSession().getAttribute(HttpSessionHandler.Items.LOGINUSER.name()); 38 39 final MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 40 final Method method = methodSignature.getMethod(); 41 boolean ignoreAuth = method.isAnnotationPresent(IgnoreAuth.class); 42 43 if ((null == loginUser)&&!ignoreAuth) { 44 return new HttpResultEntity(HttpResultHandle.HttpResultEnum.NOTLOG); 45 } 46 return (HttpResultEntity) pjp.proceed(); 47 } 48 }
在上述過程當中須要理解一下的有如下兩點
一、切點表達式
@within(org.springframework.web.bind.annotation.RestController)
它的意思表明了,通知的範圍是只要有類添加了@RestController的註解,那麼類中的方法,只要被調用,都會執行相應的通知
二、爲何這麼配置呢?
爲何這麼配置:由於個人項目是基於SpringMVC框架的,而且使用的請求都是基於Restful規範的。因此全部的Action都會配置@RestController這個註解,也就是說,全部的後臺請求,
三、上述配置要完成的功能是什麼?
若是用戶沒有登陸,那麼請求就會被打回,並在頁面上給與用戶提示
四、對於@Around環繞通知的執行過程是什麼樣的?
正常流:瀏覽器發起請求-》通知被執行-》在通知的內部,根據業務邏輯判斷,該請求是否合法,也就是前置的一些處理,若是合法調用pjp.proceed()方法-》進入controller的方法執行,執行完成後-》返回到通知內部,繼續執行pjp.proceed()後面的代碼-》返回客戶端
異常流:瀏覽器發起請求-》通知被執行-》在通知的內部,根據業務邏輯判斷,該請求是否合法,也就是前置的一些處理,若是不合法,直接return-》瀏覽器顯示處理結果
關於@AspectJ的相關知識就再也不這裏介紹了,感興趣的朋友能夠查看:@Aspect註解教程
1 package com.wt.common.security.interceptor; 2 3 4 import com.google.gson.Gson; 5 import com.wt.common.core.exception.BaseErrorException; 6 import com.wt.common.core.exception.BaseLogicException; 7 import com.wt.common.core.result.HttpResultEntity; 8 import com.wt.common.core.result.HttpResultHandle; 9 import com.wt.common.core.utils.ServletNativeObjectUtil; 10 import com.wt.common.security.handler.HttpSessionHandler; 11 import com.wt.common.security.model.SysUser; 12 import com.wt.common.security.model.SyslogPerformance; 13 import com.wt.common.security.service.SyslogPerformanceService; 14 import org.apache.commons.lang3.StringUtils; 15 import org.aspectj.lang.ProceedingJoinPoint; 16 import org.aspectj.lang.annotation.Around; 17 import org.aspectj.lang.annotation.Aspect; 18 import org.slf4j.Logger; 19 import org.slf4j.LoggerFactory; 20 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.core.annotation.Order; 22 import org.springframework.stereotype.Component; 23 24 import javax.servlet.http.HttpServletRequest; 25 26 /** 27 * @ProjectName: syInfo 28 * @Package: com.wt.common.core.interceptor 29 * @Description: 30 * @Author: lichking2017@aliyun.com 31 * @CreateDate: 2018/5/16 下午4:14 32 * @Version: v1.0 33 */ 34 35 @Component 36 @Aspect 37 @Order(2) 38 public class LogInterceptor { 39 40 Logger logger = LoggerFactory.getLogger(LoginInterceptor.class); 41 42 @Autowired 43 private SyslogPerformanceService syslogPerformanceService; 44 45 46 @Around("@within(org.springframework.web.bind.annotation.RestController)") 47 public HttpResultEntity logRecord(ProceedingJoinPoint pjp) { 48 Gson gson = new Gson(); 49 HttpServletRequest request = ServletNativeObjectUtil.getRequest(); 50 SyslogPerformance syslogPerformance = this.setLog(request); 51 syslogPerformance.setParameters(gson.toJson(pjp.getArgs())); 52 53 long startTime = System.currentTimeMillis(), endTime = 0, consume = 0; 54 55 String requestInfo = String.format("⭐️{User-Agent:[%s],Protocol:[%s],Remote Addr:[%s],Method:[%s],uri:[%s],Cookie:[%s],operator:[%s],parameters:[%s]}⭐️", 56 request.getHeader("User-Agent"), request.getProtocol(), request.getRemoteAddr(), 57 request.getMethod(), request.getRequestURI(), request.getHeader("Cookie"), 58 "ceshi", 59 gson.toJson(pjp.getArgs())); 60 try { 61 HttpResultEntity result = (HttpResultEntity) pjp.proceed(); 62 endTime = System.currentTimeMillis(); 63 logger.info(requestInfo); 64 return result; 65 } catch (Throwable throwable) { 66 endTime = System.currentTimeMillis(); 67 if (throwable instanceof BaseLogicException) { 68 String errorMessage = ((BaseLogicException) throwable).getExceptionBody().getMessage(); 69 String errorCode = ((BaseLogicException) throwable).getExceptionBody().getMessage(); 70 logger.error(StringUtils.join(requestInfo, errorMessage), throwable); 71 return HttpResultHandle.getErrorResult(errorCode, errorMessage); 72 } 73 if (throwable instanceof BaseErrorException) { 74 logger.error(StringUtils.join(requestInfo, throwable.getMessage()), throwable); 75 return HttpResultHandle.getErrorResult(); 76 } 77 78 logger.error(StringUtils.join(requestInfo, throwable.getMessage()), throwable); 79 return HttpResultHandle.getErrorResult(); 80 81 } finally { 82 consume = endTime - startTime; 83 syslogPerformance.setTimeConsuming(String.valueOf(consume)); 84 syslogPerformanceService.save(syslogPerformance); 85 } 86 } 87 88 private SyslogPerformance setLog(HttpServletRequest request) { 89 SysUser currentUser = (SysUser) request.getSession().getAttribute(HttpSessionHandler.Items.LOGINUSER.name()); 90 SyslogPerformance syslogPerformance = new SyslogPerformance(); 91 syslogPerformance 92 .setRemoteHost(request.getRemoteHost()) 93 .setRemotePort(request.getRemotePort()) 94 .setRequestType(request.getMethod()) 95 .setRequestURI(request.getRequestURI()); 96 if(currentUser!=null){ 97 syslogPerformance.setOperatorId(currentUser.getUserId()).setOperatorName(currentUser.getUserName()); 98 } 99 return syslogPerformance; 100 } 101 }
一、若是後臺的請求執行正常,那麼放行並記錄日誌
二、若是出現錯誤,同一處理結果,並返回結果到瀏覽器
三、不管處理過程是否異常,都會記錄到數據庫表當中
一、功能以下圖,每當一次請求被執行,在日誌表中都會進行記錄,包括時長,及時間。能夠再擴展一下,加上操做人
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/index.html"/> <bean class="com.wt.common.core.interceptor.CrossDomainInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
1 package com.wt.common.core.interceptor; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.core.annotation.Order; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 /** 12 * @ProjectName: syInfo 13 * @Package: com.wt.common.core.interceptor 14 * @Description: 15 * @Author: lichking2017@aliyun.com 16 * @CreateDate: 2018/5/15 下午11:21 17 * @Version: v1.0 18 */ 19 @Order(1) 20 public class CrossDomainInterceptor implements HandlerInterceptor { 21 Logger logger = LoggerFactory.getLogger(CrossDomainInterceptor.class); 22 @Override 23 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 24 response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, accept, content-type, xxxx"); 25 response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH"); 26 response.setHeader("Access-Control-Allow-Origin", "*"); 27 response.setHeader("Access-Control-Allow-Credentials", "true"); 28 return true; 29 } 30 }
一、這個比較簡單,沒什麼太多說的地方,注意方法的返回值便可,根據項目的業務邏輯,若是請求通行,那麼就return true,不然返回false。二、若是有多個攔截器,執行順序會按照攔截器在spring配置文件中聲明的前後順序執行,執行過程以下 若是有A、B兩個攔截器,A聲明在先,B聲明在後,執行順序爲 A.preHandle-》B.preHandle-》B.postHandle-》A.postHandle