AOP操作日志


前言

业务背景:

为了实现CRUD可控记录日志操作,并且记录数据修改的详情操作。

解决方案:

使用AOP的方式,以注解为切点灵活控制是否生成日志。
注解中包含了数据库表名,数据库名可用于日志业务存储(业务内容已删减)。


一、切面注解

注解五个参数:
基础参数:accessType记录操作类型(CRUD)、accessTable访问数据库表名、accessDatasource要访问的数据库的常量名称。
更新参数:serviceClass用于查询更新前后的mapper类、parseClass调用查询方法的类

	@Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface DBAccessLogger {
        /** 要执行的访问类型比如:CREATE/UPDATE/DELETE/SELECT **/   
        //    public DBOperationType accessType() default DBOperationType.SELECT;
        public DBOperationType accessType() ;
       
        /** 要访问的数据库表名: **/ 
        //    public String accessTable() default "t_user"; 
        public String accessTable() ;//必填
       
    
        /** 要访问的数据库的常量名称: **/
        //    public DatabaseEnum accessDatasource() default DatabaseEnum.DB_SYS;
        public DatabaseEnum accessDatasource();//必填
        
        /** 用于执行id查询 **/
        //    public Class<?>  serviceClass() default Object.class;
        public Class<?>  serviceClass() default Object.class;//更新必填项
       
        /**
         *  获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写
         *       则使用默认解析类
         */
        Class<? extends ContentParser> parseClass() default DefaultContentParse.class;
    }

要访问的数据库的常量名称

存储数据库的连接SqlSessionFactory,用于parseClass调用查询时获取SqlSessionFactory执行mapper指定的方法
多数据源配置需要

public enum DatabaseEnum {
        DB_SYS("****数据库", "DB_Shiro","shiroSqlSessionFactory"),
        ;
    
        /**"数据源中文名称"**/
        public final String tableChinaName;
        /**"数据常量"**/
        public final String constName;
        /**sql来链接名称(多数据源的sqlSessionFactory)**/
        public final String sqlsessionName;

        DatabaseEnum(String tableChinaName, String constName,String sqlsessionName) {
            this.tableChinaName = tableChinaName;
            this.constName = constName;
            this.sqlsessionName = sqlsessionName;
        }
        public static List<DatabaseEnum> getDBLIST() {
            return Arrays.asList(DatabaseEnum.values());
        }
    }

parseClass调用查询方法的类(查询数据工具类)

//抽象类
public interface ContentParser {
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getOldResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getNewResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
    }

实现类 :通过该实现类获取更新前后的数据。
该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数据。

    @Component
    public class DefaultContentParse implements ContentParser {
        /**
         * 获取更新方法的第一个参数解析其id
         * @param joinPoint       查询条件的参数
         * @param enableModifyLog 注解类容
         * @return
         */
        @Override
        public Object getOldResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            Object info = joinPoint.getArgs()[0];
            Object id = ReflectionUtils.getFieldValue(info, "id");
            Assert.notNull(id,"未解析到id值,请检查入参是否正确");
            Class<?> aClass = enableModifyLog.serviceClass();
            Object result = null;
            try {
                SqlSessionFactory sqlSessionFactory = SpringUtil.getBean(sqlSessionFactoryName);
                Object instance = Proxy.newProxyInstance(
                        aClass.getClassLoader(),
                        new Class[]{aClass},
                        new MyInvocationHandler(sqlSessionFactory.openSession().getMapper(aClass))
                );
                Method selectByPrimaryKey = aClass.getDeclaredMethod("selectByPrimaryKey", Long.class);
                //调用查询方法
                result = selectByPrimaryKey.invoke(instance, id);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return  result;
        }
    
        @Override
        public Object getNewResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            return getOldResult(joinPoint,enableModifyLog,sqlSessionFactoryName);
        }
    
    }

二、切面操作


	//环绕通知切面,切点:DBAccessLogger 更新拦截数据
	@Aspect
    @Component
    @Order(1)
    public class DBAccessLoggerAspect {
    
        // 注入Service用于把日志保存数据库
        @Autowired
        private LogService logService;
        
        @Around("@annotation(com.****.log.DBAccessLogger)") // 环绕通知
        public Object execute(ProceedingJoinPoint pjp) throws Exception {
            // 获得当前访问的class
            Class<?> className = pjp.getTarget().getClass();
            // 获得访问的方法名
            String methodName = pjp.getSignature().getName();
            @SuppressWarnings("rawtypes")
            Class[] argClass = ((MethodSignature) pjp.getSignature()).getParameterTypes();
            // 操作结果,默认为成功
            Long operResult = DictLogConstant.LOGS_OPER_SUCCESS;
            //返回值
            Object rvt = null;
            Method method = className.getMethod(methodName, argClass);
            DBAccessLogger dbAcessLoggerannotation = method.getAnnotation(DBAccessLogger.class);
            String accessTable = dbAcessLoggerannotation.accessTable();
            DBOperationType accessType = dbAcessLoggerannotation.accessType();
            DatabaseEnum databaseEnum = dbAcessLoggerannotation.accessDatasource();
            String accessDatasource = databaseEnum.constName;
            //crd操作直接执行方法
            if (accessType == DBOperationType.DELETE || accessType == DBOperationType.SELECT || accessType == DBOperationType.CREATE) {
                try {
                    rvt = pjp.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                    throw new RuntimeException(e.getMessage());
                }
                // 如果没有返回结果,则不将该日志操作写入数据库。
                if (rvt == null) return rvt;
            }
            if ((accessType == DBOperationType.DELETE)
                    && ((CUDResult) rvt).getReturnVal() == 0) {
                operResult = DictLogConstant.LOGS_OPER_FAILURE;
            }
            if (accessTable != null) {
                if (accessType == DBOperationType.SELECT) {
                    Log sysLog = new Log();
					/**
					设置要存放的日志信息
					**/
                    logService.createLog(sysLog);
    
                }
                else if (accessType == DBOperationType.DELETE || accessType == DBOperationType.CREATE) {
                    for (Long recordId : ((CUDResult) rvt).getRecordIds()) {
                        Log sysLog = new Log();
                      	/**
						设置要存放的日志信息
						**/
                        logService.createLog(sysLog);
                    }
                }
                else {
                    //更新操作
                    Log sysLog = new Log();
                    /**
                   设置日志信息
                   **/
                    //获取更行前的数据
                    Map<String, Object> oldMap = null;
                    Object oldObject;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //获取更行前的数据
                        oldObject = contentParser.getOldResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                        oldMap = (Map<String, Object>) objectToMap(oldObject);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //执行service
                    Object serviceReturn;
                    try {
                        serviceReturn = pjp.proceed();
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                        throw new RuntimeException(throwable.getMessage());
                    }
                    CUDResult crudResult = (CUDResult) serviceReturn;
                    Object afterResult = null;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //更新后的数据
                        afterResult = contentParser.getNewResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //修改前后的变化
                    sysLog.setOperation(accessType.getValue());
                    try {
                        String updatedefrent = defaultDealUpdate(afterResult, oldMap);
                        sysLog.setParams(updatedefrent);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                   /**
                   设置日志信息
                   **/
                    logService.createLog(sysLog);
                    return serviceReturn;
                }
            }
    
            if (operResult.longValue() == DictLogConstant.LOGS_OPER_FAILURE) {
                // 当数据库的UPDATE 和 DELETE操作没有对应的数据记录存在时,抛出异常
                throw new DBAccessException(accessType.getValue() + "的数据记录不存在!");
            }
            return rvt;
        }
        private Map<?, ?> objectToMap(Object obj) {
            if (obj == null) {
                return null;
            }
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //如果使用JPA请自己打开这条配置
            //mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
            Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
            return mappedObject;
        }
    
        /**
         *
         * @param newObject 更新过后的结果
         * @param oldMap 更新前的结果
         * @return
         */
        private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) {
            try {
                Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
                StringBuilder str = new StringBuilder();
                Object finalNewObject = newObject;
                oldMap.forEach((k, v) -> {
                    Object newResult = newMap.get(k);
                    if (v != null && !v.equals(newResult)) {
                        Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                        //获取类上的注解
                        DataName dataName = field.getAnnotation(DataName.class);
                        if (field.getType().getName().equals("java.util.Date")) {
                            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            }
                        } else {
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            }
                        }
                    }
                });
                return str.toString();
            } catch (Exception e) {
                throw new RuntimeException("比较异常", e);
            }
        }
    }

总结

该方法只能获取单表数据变化,对复杂的业务处理不足。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要定义一个注解来标识需要记录操作日志的方法: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable { String value() default ""; } ``` 然后,定义一个切面类来实现记录操作日志的功能: ```java @Aspect @Component public class LoggingAspect { @Autowired private LogService logService; @Around("@annotation(loggable)") public Object logMethodExecution(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); String message = loggable.value().isEmpty() ? methodName : loggable.value(); Object[] args = joinPoint.getArgs(); String argString = Arrays.toString(args); Object result = null; try { result = joinPoint.proceed(); logService.log(className, methodName, message, argString, result.toString(), LogType.INFO); } catch (Exception e) { logService.log(className, methodName, message, argString, e.getMessage(), LogType.ERROR); throw e; } return result; } } ``` 在切面类中,我们使用@Around注解来标识切面类型为Around,同时使用@annotation(loggable)来指定需要拦截的方法必须被@Loggable注解修饰。在切面方法中,我们可以获取到目标方法的类名、方法名、参数列表以及返回值,并把这些信息记录到数据库中。 最后,我们需要定义一个LogService类来实现将操作日志记录到数据库的功能。具体实现可以根据项目的具体情况而定。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值