最近新專案要記錄行為日誌,很久沒有用AOP,研究了一下。
廢話補多少,先上個流程圖:
資料庫日誌表設計
欄位名稱 | 欄位型別 | 註釋 |
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
JAVA端
1、建立日誌實體類
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; }
2、建立日誌操作型別、單元、模組等列舉類
(1)操作模組列舉類
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; } }
(2)操作單元列舉類
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; } }
(3)操作型別列舉類
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; } }
3、建立日誌註解
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; }
4、建立AOP方法,使用了環繞通知
@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; } }
5、建立Service與Mapper
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>
使用
在需要記錄日誌的方法上新增建立的註解
@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); }