AOP行為日誌

MrZhaoyx發表於2020-05-16

最近新專案要記錄行為日誌,很久沒有用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);
}

 

相關文章