AOP行爲日誌

最近新項目要記錄行爲日誌,好久沒有用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數據庫

JAVA端

一、建立日誌實體類

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;
}

二、建立日誌操做類型、單元、模塊等枚舉類

(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;
    }
}

三、建立日誌註解

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;

}

四、建立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;
    }

}

五、建立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>

使用

在須要記錄日誌的方法上添加建立的註解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);
}
相關文章
相關標籤/搜索