1 業務需求:今日,公司要求對操作的業務和日誌統一做處理,需要把業務表數據相關信息存入日誌表中,比如表名,方法名,業務id,操作操作時間modifyTIme等等。
除了在業務主動插入日誌數據之外,有個比較好的方法就是用面向切面aop處理,明確跟業務邏輯分開,把業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。
2 業務開發,這邊處理的方式是用方式1【兩種方式 1 利用註解方式 2 通過xml配置】
- 2.1 定義切入點接口類
package com.hec.dup.facade.mgr.annotation;
import java.lang.annotation.*;
/**
* 標記需要做業務日誌的方法
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLogAnnotation {
/**
* 被修改的實體的唯一標識,例如:菜單實體的唯一標識爲"id"
*/
String key() default "id";
/**
* 業務模塊類型,例如:"01菜單管理"
*/
String moduleType() default "";
/**
* 業務模塊名稱,例如:"菜單管理"
*/
String moduleName() default "";
/**
* 業務模塊功能名稱,例如:"新增菜單功能"
*/
String functionName() default "";
/**
* 操作日誌內容,例如:"修改菜單"
*/
String operateContent() default "";
/**
* 業務模塊表名,例如:"SYS_MENU"
*/
String tableName() default "";
}
- 2.2 定義日誌記錄切面類
package com.hec.dup.facade.mgr.aspect;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hec.dup.beans.base.PrjUserEntity;
import com.hec.dup.beans.base.vo.PrjCurrUserInfo;
import com.hec.dup.beans.com.DupComOperateLogEntity;
import com.hec.dup.common.utils.UuidUtil;
import com.hec.dup.facade.base.IPrjUserService;
import com.hec.dup.facade.com.IDupComOperateLogService;
import com.hec.dup.facade.mgr.annotation.OperateLogAnnotation;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 日誌記錄切面
* @author
*/
@Aspect
@Component
public class OperateLogAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired(required = false)
private IDupComOperateLogService operateLogService;
@Autowired(required = false)
private IPrjUserService prjUserService;
/**
* 通過AOP方式,攔解註解的日誌
* 後置通知:如果需要訪問其他建議類型的連接點上下文,則應使用JoinPoint參數類型而不是ProceedingJoinPoint。
*/
@Pointcut(value = "@annotation(com.hec.dup.facade.mgr.annotation.OperateLogAnnotation)")
public void logAspect() {
}
@After("logAspect()")
public Object recordLog(JoinPoint point) throws Throwable {
//先執行業務
Object result = point.proceed();
try {
this.handle(point);
} catch (Exception e) {
e.printStackTrace();
log.error("日誌記錄出錯!", e);
}
return result;
}
/**
* 獲取攔截方法參數,處理日誌
* @param point
* @throws Exception
*/
private void handle(ProceedingJoinPoint point) throws Exception {
//獲取攔截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("該註解只能用於方法");
}
msig = (MethodSignature) sig;
Object[] params = point.getArgs();
Object target = point.getTarget();
Method method = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
//如果當前用戶未登錄,不做日誌
PrjCurrUserInfo userInfo = (PrjCurrUserInfo)SecurityUtils.getSubject().getPrincipal();
if (null == userInfo) {
return;
}
//獲取攔截方法的參數,獲取登錄用戶對象
PrjUserEntity userEntity = userInfo.getUserEntity();
/**
// 攔截的實體類
Object target = joinPoint.getTarget();
// 攔截的方法名稱
String methodName = joinPoint.getSignature().getName();
// 攔截的方法參數
Object[] args = joinPoint.getArgs();
// 攔截的參數類型
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
**/
//獲取註解日誌內容
OperateLogAnnotation annotation = method.getAnnotation(OperateLogAnnotation.class);
//字典,替換成用table查詢表註釋
//Class dictClass = annotation.dict();
//通過表名,動態獲取表字段名和註釋
/* Map<String,String> metaMap = new HashMap<>();
//MetaTableVo metaTableVo = metaTableService.getByTableName(table);
if(StringUtil.isNotEmpty(table)){
List<MetaColumVo> metaColumns = metaColumService.queryByTable(table);
if(metaColumns!=null && metaColumns.size()>0){
for(MetaColumVo vo:metaColumns){
metaMap.put(vo.getColumnName(),vo.getColumnAlias());
}
}
}*/
StringBuilder sb = new StringBuilder();
for (Object param : params) {
sb.append(param);
sb.append(" & ");
}
//如果涉及到修改,比對修改前後值的變化內容
String logmsg ="";
/*if (methodName.indexOf("update") != -1 || methodName.indexOf("modify") != -1
|| methodName.indexOf("edit") != -1) {
Map<String, String> newMap = HttpKit.getRequestParameters(); //對象被修改後的內容
Object oldObj = redisSupport.getObject(HttpKit.getRequest().getSession().getId());
//Object oldObj = LogObjectHolder.me().get(); //獲取爲null
logmsg = ContrastObjFactory.contrastObj(table,key,metaMap,newMap,oldObj);
} else {
logmsg=content;
}*/
//記錄操作日誌
DupComOperateLogEntity operateLogEntity = getOperaLog( userEntity, target, method, logmsg, params );
operateLogService.save(operateLogEntity);
}
/**
* 封裝操作日誌實體對象
*
* @Date 2017/3/30 18:45
*/
public static DupComOperateLogEntity getOperaLog(PrjUserEntity userEntity, Object target, Method method, String msg, Object[] params) {
String classPath = target.getClass().getName(); //類名稱,含路徑
String classMethod = method.getName(); //方法名(英文)
OperateLogAnnotation annotation = method.getAnnotation( OperateLogAnnotation.class );
DupComOperateLogEntity operaLog = new DupComOperateLogEntity();
operaLog.setId( UuidUtil.getUuid() ); //主鍵
operaLog.setClassPath( classPath ); //類名稱,含路徑
operaLog.setClassMethod( classMethod ); //方法名(英文)
operaLog.setIpHost( userEntity.getLastLoginIp() ); //IP地址
operaLog.setOperateTime( new Date() ); //操作時間
operaLog.setOperateUserId( userEntity.getId() );//用戶ID
operaLog.setOperateUserName( userEntity.getUserName() ); //用戶名稱
operaLog.setUserAccount( userEntity.getAccount() ); //用戶帳號
operaLog.setUserType( userEntity.getUserType() ); //帳號類型
operaLog.setModuleType( annotation.moduleType() );//業務模塊類型
operaLog.setModuleName( annotation.moduleName() );//業務模塊名稱
operaLog.setFunctionName( annotation.functionName() );//業務模塊名稱
operaLog.setTableName( annotation.tableName() ); //操作表名
JSONArray paramArray = JSONObject.parseArray( params.toString() ); //獲取第一個參數的id
operaLog.setTableId( paramArray.getJSONObject( 0 ).getString( "id" ) );
operaLog.setOperateContent( annotation.operateContent() ); //操作內容
return operaLog;
}
}
-
2.3 定義applicationContext.xml配置
- 2.4 接口添加註解,接口被調用的時候會調用被aop處理
@OperateLogAnnotation(moduleType = "01") //這邊添加切入點接口的註解
@RequestMapping(value = "testAop01")
@ResponseBody
public JsonResult testAop(BaseEntity baseEntity) {
JsonResult jr = new JsonResult();
baseEntity.setCreateTime( new Date() );
baseEntity.setDbUser( "123456789," );
baseEntity.setExport( true );
jr.setData( baseEntity );
return jr;
}