AOP行为日志

最近新项目要记录行为日志,很久没有用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)  用户名称
扫描二维码关注公众号,回复: 11207278 查看本文章

注:数据库使用的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);
}

猜你喜欢

转载自www.cnblogs.com/MrZhaoyx/p/12902190.html