• 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)  用户名称

    注:数据库使用的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);
    }
  • 相关阅读:
    Requests发送带cookies请求
    Python3 + requests + unittest接口测试
    「完结篇」网络爬虫+实时监控+推送微信
    「爬虫」从某网站爬取数据
    定时从某网站爬取压缩包
    如何转载文章...............
    数据库连接远程服务器报错
    记录常用的Java方法
    链接服务器 不同服务器查询,插入数据
    【学习笔记】树状数组
  • 原文地址:https://www.cnblogs.com/MrZhaoyx/p/12902190.html
Copyright © 2020-2023  润新知