SpringAOP註解方式記錄操作日誌(操作模組,操作功能,呼叫方法,主鍵資訊等)支援多筆操作時記錄
阿新 • • 發佈:2019-01-22
使用AOP切入的方式記錄操作日誌,本程式碼主要採用列舉作為記錄方式,具體程式碼如下.
首先先定義先關列舉:
/**
* 列舉公共介面
* @author LeiYong
*
*/
public interface EnumSuper {
/**
* 獲取值
* @return
*/
public String getValue();
/**
* 獲取描述資訊
* @return
*/
public String getDiscription();
}
public enum LogOperateEnum implements EnumSuper{ Select("0","查詢"),Save("1","新增"),Update("2","修改"),Delete("3","刪除"); private String value; private String discription; LogOperateEnum(String value, String discription) { this.value = value; this.discription = discription; } @Override public String getValue() { return value; } @Override public String getDiscription() { return discription; } }
public enum LogEnum implements EnumSuper{ Sys("系統",""), Activity("活動",""), Shop("商城",""), Usr("使用者",""), Scd("預約","") ; private String value; private String discription; LogEnum(String value, String discription) { this.value = value; this.discription = discription; } @Override public String getValue() { return value; } @Override public String getDiscription() { return discription; } }
接下來定義註解Logger
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface Logger { /** * 操作型別 0-查詢 1-新增 2-修改 3-刪除 * @return */ LogOperateEnum type(); /** * 所屬模組 * @return */ LogEnum model(); /** * 業務主鍵 * @return */ String title() default "#pk"; }
最後LoggerAspect切面類
此處keyGenerate為主鍵生成,參考另一篇博文,使用redis生成資料庫主鍵自增
package com.cykj.base.core.aop;
import java.lang.reflect.Method;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.cykj.base.common.annotation.Logger;
import com.cykj.base.common.system.SystemConfig;
import com.cykj.base.common.system.properties.AdminPropertiesConfig;
import com.cykj.base.common.util.PropertiesCacheUtil;
import com.cykj.base.common.util.StringUtil;
import com.cykj.base.core.model.log.LogOperater;
import com.cykj.base.core.model.sys.SysUser;
import com.cykj.base.core.model.usr.UsrUser;
import com.cykj.base.core.model.usr.UsrUserSlave;
import com.cykj.base.core.provide.log.LogOperaterService;
import com.cykj.base.core.util.AopUtil;
import com.cykj.base.core.util.WebUtils;
/**
* 操作日誌
* @author LeiYong
*
*/
@Aspect
@Component
public class LoggerAspect {
@Autowired
private LogOperaterService logOperaterService;
@Pointcut("@annotation(com.cykj.base.common.annotation.Logger)")
private void loggerMethod(){}//定義一個切入點,此處Logger為Logger類全路徑
/**
* 記錄操作日誌
*
* @param jp
* @return
* @throws Throwable
*/
@Around("loggerMethod()")
public Object logger(ProceedingJoinPoint jp) throws Throwable {
Object result = null;
Boolean logEnable = SystemConfig.LOG_ENABLE;
result = AopUtil.executeJoinPointMethod(jp, jp.getArgs());
if (logEnable&&result!=null) {
LogOperater lo = null;
SysUser su = getLoginSysUser();
if (su!=null) {
lo = getLogBySysUser(su, jp);
}else {
UsrUserSlave uu = getLoginUsrUser();
lo = getLogByUsrUser(uu, jp);
}
if (lo!=null) {
logOperaterService.insertSelective(lo);
}
}
return result;
}
/**
* 記錄系統使用者操作日誌
* @param user
* @param jp
* @return
*/
private LogOperater getLogBySysUser(SysUser user,ProceedingJoinPoint jp){
LogOperater logOperater = getLogger(jp);
if (logOperater!=null) {
logOperater.setCreateBy(user.getPk());
logOperater.setFrom("0");
logOperater.setOrgPk(user.getOrgPk());
}
return logOperater;
}
/**
* 記錄微信使用者操作日誌
* @param user
* @param jp
* @return
*/
private LogOperater getLogByUsrUser(UsrUser user,ProceedingJoinPoint jp){
LogOperater logOperater = getLogger(jp);
if (logOperater!=null) {
logOperater.setCreateBy(user.getPk());
logOperater.setFrom("1");
// logOperater.setOrgPk(user.getOrgPk());
}
return logOperater;
}
private LogOperater getLogger(ProceedingJoinPoint jp){
//獲取切入點方法及引數
Method method = AopUtil.getMethod(jp);
Logger t = AopUtil.getMethodAnnotation(method, Logger.class);
try {
Boolean flag = PropertiesCacheUtil.loadProjectProperties(AdminPropertiesConfig.LOGGER_CONFIG).getBoolean(t.type().toString());
//未配置或true都記錄日誌,僅為false時不記錄
if (flag!=null&&!flag) {
return null;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String[] paramNames = AopUtil.getMethodParamNames(method);
String pk = AopUtil.parseKeyByParam(t.title(),paramNames, jp.getArgs());
//主鍵,機構pk,建立人,建立時間,操作型別,模組,處理方法,主鍵,操作內容
//自增長主鍵
LogOperater logOperater = new LogOperater();
logOperater.setCreateTime(new Date());
logOperater.setType(t.type().getValue());
logOperater.setModel(t.model().getValue());
logOperater.setFunc(t.model().getDiscription());
logOperater.setMethod(method.getName());
if (StringUtils.isNotBlank(pk)) {
int index = pk.indexOf(",");
if (index<0) {
logOperater.setTitle(pk);
}else{
logOperater.setTitle(StringUtil.subString(pk, 0, index));
int count = StringUtils.countMatches(pk, ",");
String content = SystemConfig.LOG_CONTENT_LENGTH>pk.length()?pk:StringUtil.subString(pk, 0, SystemConfig.LOG_CONTENT_LENGTH)+"...";
logOperater.setContent(count+"筆:"+content);
}
}
return logOperater;
}
/**
* 獲取系統登入使用者
* @return
*/
private SysUser getLoginSysUser(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return WebUtils.getLoginUser(request);
}
/**
* 獲取微信登入使用者(未實現)
* @return
*/
private UsrUserSlave getLoginUsrUser(){
return null;
}
}
再在spring的xml中配置掃描到LoggerAspect所在package目錄,大功告成
使用起來也非常方便,在需要加操作日誌的方法上加個註解就OK了,如此則記錄為系統模組,執行查詢,主鍵為引數pk(此處使用springSpel表示式解析)
@Logger(model=LogEnum.Sys,type=LogOperateEnum.Select,title="#pk")
public Json get(String orgPk,String pk) {}
最後附上AOPUtil程式碼
package com.cykj.base.core.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class AopUtil {
private static ExpressionParser parser;
/**
* 獲取被攔截方法物件
*
* MethodSignature.getMethod() 獲取的是頂層介面或者父類的方法物件 而快取的註解在實現類的方法上
* 所以應該使用反射獲取當前物件的方法物件
*/
public static Method getMethod(ProceedingJoinPoint pjp) {
// 獲取引數的型別
Class<?>[] parameterTypes = ((MethodSignature)pjp.getSignature()).getMethod().getParameterTypes();
//此方法當引數傳遞null時會報錯,故而改為採用上述方式
// Object[] args = pjp.getArgs();
// Class[] argTypes = new Class[pjp.getArgs().length];
// for (int i = 0; i < args.length; i++) {
// if (args[i]!=null) {
// argTypes[i] = args[i].getClass();
// }
// }
Method method = null;
try {
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), parameterTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return method;
}
/**
* 通過spring spel解析引數獲取redis快取key
*
* @param keys
* 快取keys
* @param paramNames
* 引數名
* @param args
* 引數列表
* @return
*/
public static String parseKeyByParam(String keys, String[] paramNames, Object[] args) {
if (StringUtils.isBlank(keys)) {
return "";
}
ExpressionParser parser = getParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 把方法引數放入SPEL上下文中
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 獲取引數key
StringBuffer sb = new StringBuffer();
// for (int i = 0; i < keys.length; i++) {
sb.append(parser.parseExpression(keys).getValue(context, String.class));
// }
return sb.toString();
}
/**
* 獲取引數名列表
*
* @param method
* @return
*/
public static String[] getMethodParamNames(Method method) {
// 獲取被攔截方法引數名列表(使用Spring支援類庫)
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
return paraNameArr;
}
/**
* 獲取快取主鍵
* @param jp
* @param clazz
* @return
* @throws SecurityException
* @throws NoSuchFieldException
*/
// public static <T extends Annotation> String[] getCachedKey(T t,String[] paramNames, Class<T> clazz) throws NoSuchFieldException, SecurityException {
//// Method method = getMethod(jp);
//// T t = getMethodAnnotation(method, clazz);
// // String fieldKey = null;
// // if (cache.isParam()) {
// String fieldKey = parseKeyByParam(ReflectionUtil.getField(t.getClass().getField("cachekey"), t).toString(), paramNames, jp.getArgs());
// // }
// return fieldKey.split(",");
// }
/**
* 獲取方法註解
* @param method
* @param clazz 註解型別
* @return
*/
public static <T extends Annotation> T getMethodAnnotation(Method method,Class<T> clazz){
T t = method.getAnnotation(clazz);
return t;
}
/**
* 獲取redis快取key通過集合引數
*
* @param keys
* 快取keys
* @param params
* 引數名
* @param returnObj
* 引數列表
* @return
*/
private String parseKeyByReturn(String keys, Object returnObj) {
ExpressionParser parser = getParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 把方法引數放入SPEL上下文中
context.setVariable("obj", returnObj);
// 獲取引數key
StringBuffer sb = new StringBuffer();
sb.append(parser.parseExpression(keys).getValue(context, String.class));
return sb.toString();
}
/**
* 使用SPEL進行key的解析
*
* @return
*/
public synchronized static ExpressionParser getParser() {
if (parser == null) {
parser = new SpelExpressionParser();
}
return parser;
}
/**
* 執行切入點方法
* @param jp
* @param params 方法引數
* @return
*/
public static Object executeJoinPointMethod(ProceedingJoinPoint jp,Object[] params){
Object obj = null;
try {
if (params==null||params.length==0) {
obj = jp.proceed();
}else{
obj = jp.proceed(params);
}
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}