最近新項目要記錄行爲日誌,好久沒有用AOP,研究了一下。java
廢話很少說,先上個流程圖:spring
字段名稱 | 字段類型 | 註釋 |
LOG_ID | VARCHAR2(255) | |
LOG_LEVEL | NUMBER | 日誌級別 |
START_TIME | DATE | 開始時間 |
RUN_TIME | NUMBER | 運行時間(ms) |
OPERATION_MODULE | VARCHAR2(255) | 被操做的模塊 |
OPERATION_UNIT | VARCHAR2(255) | 被操做的單元 |
OPERATION_TYPE | VARCHAR2(255) | 操做類型 |
OPERATION_DETAIL | VARCHAR2(500 CHAR) | 操做詳情 |
USER_CODE | VARCHAR2(255) | 用戶編號 |
USER_NAME | VARCHAR2(255) | 用戶名稱 |
注:數據庫使用的Oracle數據庫
import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; @Data public class OperationLog { private String logId; private String userCode; private String userName; @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss") private Date startTime; private Long runTime; private String operationUnit; private String operationType; private String operationDetail; private String operationModule; private Integer logLevel; }
public enum OperationModule { /** * 被操做的模塊 */ UNKNOWN("XX系統"), USER("用戶模塊"), PRODUCT("產品模塊"), SALE("銷售信息模塊"); private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } OperationModule(String s) { this.value = s; } }
public enum OperationUnit { /** * 被操做的單元 */ UNKNOWN(""), /** * 用戶模塊 */ USER_INFO("用戶信息"), USER_ROLE("用戶角色"), USER_PERMISSION("用戶權限"), /** * 產品模塊 */ PRODUCT_INFO("產品信息"), PRODUCT_INV("產品庫存"), /** * 銷售信息模塊 */ SALE_INFO("銷售信息"), SALE_PLAN("銷售計劃"); private String value; OperationUnit(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
public enum OperationType { /** * 基本操做類型 */ UNKNOWN(""), LOGIN("登陸"), INPUT("導入"), QUERY("查詢"), EXPORT("導出"), DELETE("刪除"), INSERT("插入"), UPDATE("更新"), /** * 用戶 */ USER_SET_ROLE("設置用戶角色"), USER_SET_PERMISSION("設置用戶權限"), /** * 商品 */ PRODUCT_EXPORT_INFO("導出商品信息"), PRODUCT_SET_RANK("設置商品級別"), /** * 銷售 */ SALE_EXPORT_INFO("導出銷售信息"), SALE_SET_SALE_PLAN("設置銷售計劃"); private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } OperationType(String s) { this.value = s; } }
import com.XXX.XXX.domin.OperationModule; import com.XXX.XXX.domin.OperationType; import com.XXX.XXX.domin.OperationUnit; import java.lang.annotation.*; @Documented //代表這個註解應該被 javadoc工具記錄 @Target({ElementType.METHOD}) //聲明該註解做用於方法之上 @Retention(RetentionPolicy.RUNTIME) //聲明該註解不只被保存到class文件中,jvm加載class文件以後,仍然存在 public @interface OperationLogDetail { /** * 方法描述,可以使用佔位符獲取參數:{{param}} */ String operationDetail() default ""; /** * 日誌等級:1-9 */ int logLevel() default 1; /** * 操做類型(enum) */ OperationType operationType() default OperationType.UNKNOWN; /** * 被操做的對象(此處使用enum) */ OperationUnit operationUnit() default OperationUnit.UNKNOWN; /** * 被操做的系統模塊(此處使用enum) */ OperationModule operationModule() default OperationModule.UNKNOWN; }
@Aspect //代表該類是一個切面 @Component //實例化到spring容器中 public class OperationLogAop { @Autowired private OperationLogService operationLogService; //代表切點在加了OperationLogDetail註解的方法 @Pointcut("@annotation(com.topsports.adbuhuo.annotation.OperationLogDetail)") public void operationLog(){} //環繞通知 @Around("operationLog()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { Object res = null; //獲取系統當前時間 long time = System.currentTimeMillis(); try { //獲取切入點(要記錄日誌的方法)的參數 Object[] args = joinPoint.getArgs(); //調用要記錄日誌的方法 res = joinPoint.proceed(args); //獲取方法執行時長 time = System.currentTimeMillis() - time; return res; } finally { try { //方法執行完成後增長日誌 addOperationLog(joinPoint,res,time); }catch (Exception e){ System.out.println("LogAspect 操做失敗:" + e.getMessage()); e.printStackTrace(); } } } private void addOperationLog(JoinPoint joinPoint, Object res, long time){ //獲取當前登陸的用戶 UserInfo userInfo = SecurityUserUtil.getThisUserInfo(); //獲取方法的簽名,用來獲取加在方法上的註解 MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //建立日誌對象 OperationLog operationLog = new OperationLog(); operationLog.setRunTime(time); operationLog.setLogId(UUID.randomUUID().toString()); operationLog.setStartTime(new Date()); operationLog.setUserName(userInfo.getUserName()); operationLog.setUserCode(userInfo.getUserCode()); //獲取加在方法上的註解 OperationLogDetail annotation = signature.getMethod().getAnnotation(OperationLogDetail.class); if(annotation != null){ operationLog.setLogLevel(annotation.logLevel()); operationLog.setOperationDetail(getDetail(((MethodSignature)joinPoint.getSignature()).getParameterNames(),joinPoint.getArgs(),annotation)); operationLog.setOperationType(annotation.operationType().getValue()); operationLog.setOperationUnit(annotation.operationUnit().getValue()); operationLog.setOperationModule(annotation.operationModule().getValue()); } //保存日誌 operationLogService.insertSystemLog(operationLog); } /** * 對佔位符處理 * @param argNames 方法參數名稱數組 * @param args 方法參數數組 * @param annotation 註解信息 * @return 返回處理後的描述 */ private String getDetail(String[] argNames, Object[] args, OperationLogDetail annotation){ Map<Object, Object> map = new HashMap<>(4); for(int i = 0;i < argNames.length;i++){ map.put(argNames[i],args[i]); } //獲取詳情信息 String detail = annotation.operationDetail(); try { //遍歷傳入方法的參數 for (Map.Entry<Object, Object> entry : map.entrySet()) { Object k = entry.getKey(); Object v = entry.getValue(); //request和response不可序列化,XSSFWorkbook也不可序列化 if(!(v instanceof HttpServletRequest) && !(v instanceof HttpServletResponse) && !(v instanceof XSSFWorkbook)){ if(v instanceof JSONObject){ //處理JSONObject格式的參數 JSONObject jsonObject = (JSONObject) v; for (String jk : jsonObject.keySet()) { detail = detail.replace("{{" + jk + "}}", jsonObject.get(jk)!=null?jsonObject.get(jk).toString():""); } }else{ detail = detail.replace("{{" + k + "}}", JSON.toJSONString(v)); } }else if(v instanceof HttpServletRequest){ //處理HttpServletRequest JSONObject jsonObject = CommonUtil.request2Json((HttpServletRequest) v); for (String jk : jsonObject.keySet()) { detail = detail.replace("{{" + jk + "}}", jsonObject.get(jk)!=null?jsonObject.get(jk).toString():""); } } } }catch (Exception e){ e.printStackTrace(); } return detail; } }
public interface OperationLogService { //插入 void insertSystemLog(OperationLog operationLog); } -------------------------------------------------- @Service public class OperationLogServiceImpl implements OperationLogService { @Autowired private OperationLogMapper operationLogMapper; @Override public void insertSystemLog(OperationLog operationLog) { operationLogMapper.insertSystemLog(operationLog); } } -------------------------------------------------- @Mapper @Repository public interface OperationLogMapper { void insertSystemLog(OperationLog operationLog); } --------------------------------------------------- <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.XXX.XXX.dao.OperationLogMapper"> <insert id="insertSystemLog" parameterType="com.XXX.XXX.domin.OperationLog"> insert into SYSTEM_OPERATION_LOG( LOG_ID, USER_CODE, USER_NAME, START_TIME, RUN_TIME, OPERATION_UNIT, OPERATION_TYPE, OPERATION_DETAIL, LOG_LEVEL, OPERATION_MODULE ) values( #{logId}, #{userCode}, #{userName}, #{startTime}, #{runTime}, #{operationUnit}, #{operationType}, #{operationDetail}, #{logLevel}, #{operationModule} ) </insert> </mapper>
在須要記錄日誌的方法上添加建立的註解json
@OperationLogDetail( operationDetail = "{{userCode}}", //該佔位符將在建立日誌對象時掃描參數列表獲取 operationType = OperationType.QUERY, operationUnit = OperationUnit.USER_INFO, operationModule = OperationModule.USER) @PostMapping("/getUserInfo") public JSONObject getUserInfo(@RequestBody JSONObject jsonObject){ return userInfoService.getUserInfo(jsonObject); }