SpringBoot通過Aspect切面實現系統日誌及Mapper異常攔截(附帶日誌表設計)
阿新 • • 發佈:2018-12-20
最近專案中需要記錄服務端介面訪問日誌,所以在開發過程中回顧了一下AOP相關的內容,特此記錄,便於日後查閱。
1、引入依賴
<!-- 引入aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、系統日誌攔截器
@Aspect @Component public class SystemLogAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private SystemLogDAO systemLogDAO; @Pointcut("execution(public * com.cy.ops.api.*.controller..*(..))") public void systemLog() {} @Around(value = "systemLog()") public ResponseResult doAround(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); //1、從當前Session中獲取登入使用者資訊 UserInfoBO userInfo = WebSession.getUserInfo(request, response); if(userInfo == null) { return new ResponseResult().setSuccess(false).setMessage(ResultCode.RECORD_LOGIN_EXPIRE.getMessage()).setCode(ResultCode.RECORD_LOGIN_EXPIRE.getCOde()); } //2、記錄執行時間 long startTime = System.currentTimeMillis(); ResponseResult result = (ResponseResult) joinPoint.proceed(joinPoint.getArgs()); long endTime = System.currentTimeMillis(); long totalTime = endTime - startTime; //3、入系統日誌表 SystemLogDO systemLogDO = new SystemLogDO(); if(joinPoint.getArgs().length > 0){ systemLogDO.setPara(JsonToBeanUtil.beanToJSON(joinPoint.getArgs()[0])); } systemLogDO.setClientIp(IpUtil.getClientIp(request)); systemLogDO.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"); systemLogDO.setOperator(userInfo.getUserName()); systemLogDO.setReqUri(request.getRequestURI()); systemLogDO.setReturnData(JsonToBeanUtil.beanToJSON(result)); systemLogDO.setStartTime(String.valueOf(startTime)); systemLogDO.setEndTime(String.valueOf(endTime)); systemLogDO.setTotalTime(String.valueOf(totalTime)); systemLogDO.setGmtCreateUser(userInfo.getUserId()); systemLogDO.setGmtModifiedUser(userInfo.getUserId()); systemLogDO.setIsDelete(0); systemLogDAO.insert(systemLogDO); return result; } @Before("systemLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { logger.info("***********************進入前置切面***********************"); } @After("systemLog()") public void doAfter(JoinPoint joinPoint) throws Throwable { logger.info("***********************進入後置切面***********************"); } @AfterReturning((pointcut="systemLog()" returning="object") public void doAfterReturning(JoinPoint joinPoint, Object object) throws Throwable { logger.info("***********************進入返回切面***********************"); } @AfterThrowing(pointcut = "systemLog()", throwing = "e") public void doAfterThrowing(Exception e) throws Exception{ logger.info("***********************進入異常切面***********************"); } }
3、自定義SQL異常
package com.czgo.exception; /** * 自定義異常類(繼承執行時異常) * @author AlanLee * @version 2016/11/26 */ public class SqlException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 錯誤編碼 */ private String errorCode; /** * 訊息是否為屬性檔案中的Key */ private boolean propertiesKey = true; /** * 構造一個基本異常. * * @param cause * 異常資訊 */ public SqlException(Throwable cause) { super(cause); } /** * 構造一個基本異常. * * @param message * 資訊描述 */ public SqlException(String message) { super(message); } /** * 構造一個基本異常. * * @param errorCode * 錯誤編碼 * @param message * 資訊描述 */ public SqlException(String errorCode, String message) { this(errorCode, message, true); } /** * 構造一個基本異常. * * @param errorCode * 錯誤編碼 * @param message * 資訊描述 */ public SqlException(String errorCode, String message, Throwable cause) { this(errorCode, message, cause, true); } /** * 構造一個基本異常. * * @param errorCode * 錯誤編碼 * @param message * 資訊描述 * @param propertiesKey * 訊息是否為屬性檔案中的Key */ public SqlException(String errorCode, String message, boolean propertiesKey) { super(message); this.setErrorCode(errorCode); this.setPropertiesKey(propertiesKey); } /** * 構造一個基本異常. * * @param errorCode * 錯誤編碼 * @param message * 資訊描述 */ public SqlException(String errorCode, String message, Throwable cause, boolean propertiesKey) { super(message, cause); this.setErrorCode(errorCode); this.setPropertiesKey(propertiesKey); } /** * 構造一個基本異常. * * @param message * 資訊描述 * @param cause * 根異常類(可以存入任何異常) */ public SqlException(String message, Throwable cause) { super(message, cause); } public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } public boolean isPropertiesKey() { return propertiesKey; } public void setPropertiesKey(boolean propertiesKey) { this.propertiesKey = propertiesKey; } }
4、Mapper層異常攔截器
@Aspect @Component public class MapperLogAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(public * com.cy.ops.*.dal..*(..))") public void mapperLog() {} @AfterThrowing(pointcut = "mapperLog()", throwing = "e") public void doAfterThrowing(Exception e) throws Exception{ throw new SqlException(e); } }
5、IPUtil工具類
package com.gcloud.common;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Description: IP工具類
* @Author: zhangzhixiang
* @CreateDate: 2018/11/08 16:08:54
* @Version: 1.0
*/
public class IPUtil {
/**
* 檢查IP是否合法
* @param ip
* @return
*/
public static boolean ipValid(String ip) {
String regex0 = "(2[0-4]\\d)" + "|(25[0-5])";
String regex1 = "1\\d{2}";
String regex2 = "[1-9]\\d";
String regex3 = "\\d";
String regex = "(" + regex0 + ")|(" + regex1 + ")|(" + regex2 + ")|(" + regex3 + ")";
regex = "(" + regex + ").(" + regex + ").(" + regex + ").(" + regex + ")";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(ip);
return m.matches();
}
/**
* 獲取本地ip 適合windows與linux
*
* @return
*/
public static String getLocalIP() {
String localIP = "127.0.0.1";
try {
Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
NetworkInterface ni = (NetworkInterface) netInterfaces.nextElement();
InetAddress ip = ni.getInetAddresses().nextElement();
if (!ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {
localIP = ip.getHostAddress();
break;
}
}
} catch (Exception e) {
try {
localIP = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e1) {
e1.printStackTrace();
}
}
return localIP;
}
/**
* 獲取客戶機的ip地址
* @param request
* @return
*/
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("http_client_ip");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
// 如果是多級代理,那麼取第一個ip為客戶ip
if (ip != null && ip.indexOf(",") != -1) {
ip = ip.substring(ip.lastIndexOf(",") + 1, ip.length()).trim();
}
return ip;
}
/**
* 把ip轉化為整數
* @param ip
* @return
*/
public static long translateIP2Int(String ip){
String[] intArr = ip.split("\\.");
int[] ipInt = new int[intArr.length];
for (int i = 0; i <intArr.length ; i++) {
ipInt[i] = new Integer(intArr[i]).intValue();
}
return ipInt[0] * 256 * 256 * 256 + + ipInt[1] * 256 * 256 + ipInt[2] * 256 + ipInt[3];
}
public static void main(String[] args) {
System.out.println(getLocalIP());
}
}
6、結果返回實體類
public class ResponseResult implements Serializable {
private static final long serialVersionUID = 6054052582421291408L;
private String message;
private Object data;
private int code;
private boolean success;
private Long total;
public ResponseResult(){}
public ResponseResult(boolean success, Object data) {
this.success = success;
this.data = data;
}
public ResponseResult(boolean success, String message, Object data) {
this.success = success;
this.message = message;
this.data = data;
}
public String getMessage() {
return message;
}
public ResponseResult setMessage(String message) {
this.message = message;
return this;
}
public Object getData() {
return data;
}
public ResponseResult setData(Object data) {
this.data = data;
return this;
}
public boolean getSuccess() {
return success;
}
public ResponseResult setSuccess(boolean success) {
this.success = success;
return this;
}
public int getCode() {
return code;
}
public ResponseResult setCode(int code) {
this.code = code;
return this;
}
public Long getTotal() {
return success;
}
public ResponseResult setTotal(Long total) {
this.total = total;
return this;
}
}
7、日誌表結構設計
欄位名 | 註釋 | 型別 | 長度 | 是否必填 | 是否主鍵 |
id | 自增ID | int | 11 | 是 | 是 |
client_ip | 客戶端ip | varchar | 100 | 否 | 否 |
req_uri | 請求對映路徑 | varchar | 100 | 否 | 否 |
method | 方法名 | varchar | 200 | 否 | 否 |
param | 引數 | text | 0 | 否 | 否 |
operator | 操作人姓名 | varchar | 100 | 否 | 否 |
start_time | 介面請求時間 | varchar | 50 | 否 | 否 |
ent_time | 介面返回時間 | varchar | 50 | 否 | 否 |
total_time | 總消耗時間 | varchar | 50 | 否 | 否 |
return_data | 介面返回資料 | text | 0 | 否 | 否 |
gmt_create | 建立時間 | datatime | 50 | 是 | 否 |
gmt_create_user | 建立人 | int | 11 | 是 | 否 |
gmt_modified | 修改時間 | datatime | 0 | 是 | 否 |
gmt_modified_user | 修改人 | int | 11 | 是 | 否 |
is_delete | 是否刪除(0:否 1:是) | tinyint | 2 | 是 | 否 |
以上就是SpringBoot AOP日誌的實現以及Mapper層異常捕獲。