AOP自定义注解记录日志

AOP自定义注解记录SQL日志

前言

本文使用aop自定义注解记录日志(主要记录sql语句)入库


一、如何使用

1.创建一个LogTestController

@GetMapping("recordOperateLogTest")
@ApiOperation (value = "测试切面日志记录效果:处理订单信息")
public Map<String, Object> recordOperateLogTest(){
    Map<String, Object> resMap = new HashMap<String, Object>();
    try
    {
    	//调用service
        logTestService.executeOrderInfo();
        resMap.put("result", "successful" );
    }
    catch (Exception e)
    {
        e.printStackTrace();
        resMap.put("result", "failure" );
    }
    return resMap;
}

2.创建service

注意:如果要实现切面必须动态代理,拿到bean或者通过Controller调service。

@Transactional (rollbackFor = Exception.class)
@Override
public void executeOrderInfo () throws Exception
{
    //新增的数据
    OrderEntity dbEntity = new OrderEntity ();
    dbEntity.setOrderNo ("订单编号01");
    dbEntity.setOrderName ("葡萄糖注射液订单");
    //修改数据
    OrderEntity updateEntity = new OrderEntity ();
    updateEntity.setOrderNo ("订单编号400");
    updateEntity.setOrderName ("头孢拉定订单");

    //动态代理切面
    LogTestServiceImpl logTestService =  SpringContextUtil.getBean (LogTestServiceImpl.class);
    List<Integer> list= new ArrayList<> ();
    logTestService.addAndUpdate (dbEntity, updateEntity, new UpdateWrapper<OrderEntity>(), 14L, (byte) 1,list,new TSsDatasource());
    logTestService.update (dbEntity,1L);
    //测试事务回滚
    //throw new Exception ();
}

3.service中调用新增或者删除方法

注意:如果要实现切面必须动态代理,拿到bean或者通过Controller调service。
mapperId为mapper.xml里的方法
方法的形参要与mapper方法里面的方法形参一致,有@Param注解用注解,没有就用名字就好

/**
 * 新增和更新用的mybatisPlus
 * 参数是对应mapper方法的参数,取名:有@Param注解的取注解里面的值,没有注解的取和mapper方法里参数名字一样
 * @param entity
 * @param et
 * @param ew
 */
@OperateLogs ({
        @OperateLog (model = 10, childModel = 1001, type = 0 , mapperId = "com.uindata.dao.ss.TSsProjectRuleMapper.findRuleTimeAndName"),
        @OperateLog (model = 10, childModel = 1001, type = 0 , mapperId = "com.uindata.dao.ss.TSsProjectRuleMapper.findProjectRule"),
        @OperateLog (model = 10, childModel = 1001, type = 1 , mapperId = "com.uindata.dao.test.OrderMapper.insert"),
        @OperateLog (model = 10, childModel = 1001, type = 2 , mapperId = "com.uindata.dao.test.OrderMapper.update"),
        @OperateLog (model = 10, childModel = 1001, type = 3 , mapperId = "com.uindata.dao.test.OrderMapper.deleteById"),
        @OperateLog (model = 10, childModel = 1001, type = 1 , mapperId = "com.uindata.dao.ss.TSsDatasourceMapper.myInsertDatasource"),
        @OperateLog (model = 10, childModel = 1001, type = 3 , mapperId = "com.uindata.dao.test.OrderMapper.deleteByIds")
})
@Transactional (rollbackFor = Exception.class)
public void addAndUpdate (OrderEntity entity, OrderEntity et, UpdateWrapper<OrderEntity> ew,Long id,Byte type,List<Integer> ids,TSsDatasource tSsDatasource)
{
    //自己写的查询
    tSsProjectRuleMapper.findRuleTimeAndName (id,type);
    tSsProjectRuleMapper.findProjectRule (id);

    //MP的新增
    orderMapper.insert (entity);
    //MP的修改
    ew.eq ("orderNo", "400");
    orderMapper.update (et, ew);
    //MP的删除
    orderMapper.deleteById (id);

    //自己写的新增
    tSsDatasource.setDbname ("xxxx");
    tSsDatasource.setDbusername ("用户名");
    tSsDatasource.setDbpassword ("密码");
    //自己写的删除
    ids.add (1);
    ids.add (4);
    orderMapper.deleteByIds (ids);
}

/**
 * 自己写的修改
 * @param entity
 * @param id
 */
@OperateLog (model = 10, childModel = 1001, type = 2, mapperId = "com.uindata.dao.test.OrderMapper.updateById")
@Transactional (rollbackFor = Exception.class)
public void update (OrderEntity entity, Long id)
{
    entity.setOrderNo ("mapper.xml订单号");
    entity.setOrderName (null);
    orderMapper.updateById (id, entity);
}

4.注解类

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLogs
{
    OperateLog[] value();
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog
{
    //模块
    int model ();

    //子模块
    int childModel ();

    //类型
    int type ();

    //mapper id
    String mapperId ();
}

5.切面类

@Aspect
@Component
@Slf4j
public class OperateLogAspect
{

    @Pointcut ("@annotation(com.uindata.common.annotation.OperateLogs)||@annotation(com.uindata.common.annotation.OperateLog)")
    public void logPointCut ()
    {
    }

    @AfterReturning (pointcut = "logPointCut()")
    public void doAfter (JoinPoint joinPoint) throws Exception
    {
        try
        {
            //获取方法名
            String methodName = joinPoint.getSignature ().getName ();

            //获取方法
            Method method = currentMethod (joinPoint, methodName);

            //获取方法上OperateLogs注解
            OperateLogs operateLogs = method.getAnnotation (OperateLogs.class);

            //获取所有OperateLog注解
            OperateLog[] operateLog=null;

            //如果是单个注解@OperateLog
            if(Objects.isNull (operateLogs)){
                OperateLog operateItem=method.getAnnotation (OperateLog.class);
                operateLog=new OperateLog[1];
                operateLog[0]=operateItem;
            }
            //否则是多个注解的@OperateLogs
            else {
                operateLog = operateLogs.value ();
            }

            // ================================ 收集形参 ================================
            Map<String, Object> allParams = getArgs (joinPoint);

            for (OperateLog log : operateLog)
            {
                //定义模块,子模块,类型,实体类型,mapperid
                int logModel = log.model ();
                int logChildModel = log.childModel ();
                int logType = log.type ();
                String logMapperId = log.mapperId ();

                // ================================ 获取执行sql ================================
                String realSql = getSql (allParams, logMapperId);

                // ================================ 记录操作日志 ================================
                tLogOperateService.recordOperateLog (joinPoint, methodName, logModel, logChildModel, logType, realSql);
            }
        }catch (Exception e){
            log.error ("日志记录异常",e.getMessage ());
            throw new UindataException ("日志记录异常");
        }
    }

    /**
     * 获取方法参数列表
     *
     * @param mapperId
     * @return
     * @throws ClassNotFoundException
     */
    private Map<String, Object> getParameter (String mapperId, Map<String, Object> allParams)
            throws ClassNotFoundException
    {
        //从分隔符最后一次出现的位置向前截取,截取到mapper类
        String mapper = StringUtils.substringBeforeLast (mapperId, ".");
        //从分隔符最后一次出现的位置向后截取,截取到mapper里面的方法
        String method = StringUtils.substringAfterLast (mapperId, ".");

        //通过反射获取到类
        Class<?> mapperClass = Class.forName (mapper);
        //获取类中所有的方法
        Method[] methods = mapperClass.getMethods ();

        Map<String, Object> parameterMap = new HashMap<> ();
        for (Method methodItem : methods)
        {
            //判断是否是指定方法
            if (methodItem.getName ().equals (method))
            {
                // 获取方法的所有参数
                Parameter[] parameters = methodItem.getParameters ();
                for (Parameter parameter : parameters)
                {
                    // 判断是否存在注解
                    if (parameter.isAnnotationPresent (Param.class))
                    {
                        String annotationVlue = parameter.getAnnotation (Param.class).value ();
                        parameterMap.put (annotationVlue, allParams.get (annotationVlue));
                    }
                    else
                    {
                        parameterMap.put (parameter.getName (), allParams.get (parameter.getName ()));
                    }
                }
                break;
            }
        }
        return parameterMap;
    }

    /**
     * 获取实际sql语句
     *
     * @param allParams
     * @param logMapperId
     * @return
     * @throws IllegalAccessException
     */
    private String getSql (Map<String, Object> allParams, String logMapperId) throws ClassNotFoundException
    {
        Map<String, Object> map = getParameter (logMapperId, allParams);

        Configuration con = sqlSessionFactory.getConfiguration ();
        MappedStatement ms = con.getMappedStatement (logMapperId);
        BoundSql boundSql = null;
        //判断方法为一个参数,并且sql的执行语句是insert,针对新增的操作
        if (map.size () == BaseConstants.NUM.ONE &&
            StringUtils.containsIgnoreCase (ms.getBoundSql (map).getSql (), "insert"))
        {
            for (String key : map.keySet ())
            {
                //参数就是map的一个值
                boundSql = ms.getBoundSql (map.get (key));
            }
        }
        else
        {
            boundSql = ms.getBoundSql (map);
        }

        return showSql (ms.getConfiguration (), boundSql);
    }

    /**
     * SQL语句进行?的替换
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    public static String showSql (Configuration configuration, BoundSql boundSql)
    {
        // 获取参数
        Object parameterObject = boundSql.getParameterObject ();
        List<ParameterMapping> parameterMappings = boundSql
                .getParameterMappings ();
        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql ().replaceAll ("[\\s]+", " ");

        if (CollectionUtils.isNotEmpty (parameterMappings) && parameterObject != null)
        {
            // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换<br>       
            // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
            TypeHandlerRegistry typeHandlerRegistry = configuration
                    .getTypeHandlerRegistry ();
            if (typeHandlerRegistry.hasTypeHandler (parameterObject.getClass ()))
            {
                sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (parameterObject)));

            }
            else
            {
                // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                MetaObject metaObject = configuration.newMetaObject (
                        parameterObject);

                for (ParameterMapping parameterMapping : parameterMappings)
                {
                    String propertyName = parameterMapping.getProperty ();
                    if (metaObject.hasGetter (propertyName))
                    {
                        Object obj = metaObject.getValue (propertyName);
                        sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (obj)));
                    }
                    else if (boundSql.hasAdditionalParameter (propertyName))
                    {
                        // 该分支是动态sql
                        Object obj = boundSql.getAdditionalParameter (propertyName);
                        sql = sql.replaceFirst ("\\?", Matcher.quoteReplacement (getParameterValue (obj)));
                    }
                    else
                    {   //打印出缺失,提醒该参数缺失并防止错位
                        sql = sql.replaceFirst ("\\?", "缺失");
                    }
                }
            }
        }
        return sql;
    }

    /**
     * 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理<br>
     *
     * @param obj
     * @return
     */
    private static String getParameterValue (Object obj)
    {
        String value = null;
        if (obj instanceof String)
        {
            value = "'" + obj.toString () + "'";
        }
        else if (obj instanceof Date)
        {
            DateFormat formatter = DateFormat
                    .getDateTimeInstance (DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format (new Date ()) + "'";
        }
        else
        {
            if (obj != null)
            {
                value = obj.toString ();
            }
            else
            {
                value = "";
            }

        }
        return value;
    }

    /**
     * @Description: 收集形参
     * @auther: xiaoyi
     * @date: 11:52 2020/10/22
     * @param: [joinPoint]
     * @return: java.util.Map
     */
    private Map<String, Object> getArgs (JoinPoint joinPoint)
    {
        Map<String, Object> allParams = new HashMap ();
        Object[] paramValues = joinPoint.getArgs ();
        String[] paramNames = ((CodeSignature) joinPoint.getSignature ()).getParameterNames ();
        for (int i = 0; i < paramNames.length; i++)
        {
            allParams.put (paramNames[i], paramValues[i]);
        }
        return allParams;
    }

    /**
     * 获取当前执行的方法
     *
     * @param joinPoint  连接点
     * @param methodName 方法名称
     * @return 方法
     */
    private Method currentMethod (JoinPoint joinPoint, String methodName)
    {
        /**
         * 获取目标类的所有方法,找到当前要执行的方法
         */
        Method[] methods = joinPoint.getTarget ().getClass ().getMethods ();
        Method resultMethod = null;
        for (Method method : methods)
        {
            if (method.getName ().equals (methodName))
            {
                resultMethod = method;
                break;
            }
        }
        return resultMethod;
    }

    @Autowired
    private TLogOperateService tLogOperateService;

    @Autowired
    SqlSessionFactory sqlSessionFactory;
}

6.记录操作日志

@Override
public void recordOperateLog(JoinPoint joinPoint, String methodName, Integer model, Integer childModel, Integer type, String sql) throws Exception
{

    // 获取request
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

    // ======================= insert log to table =======================
    TLogOperate tLogOperate = new TLogOperate();
    tLogOperate.setId(null);
    tLogOperate.setModel(model);
    tLogOperate.setChildModel(childModel);
    tLogOperate.setType(type.byteValue());
    tLogOperate.setSqlDetaile(sql);
    //tLogOperate.setDataDetaile();
    tLogOperate.setOperationTime(new Date());
    tLogOperate.setResultType(BaseConstants.ResultType.SUCCESSFULLY);
    tLogOperate.setDescription("");
    tLogOperate.setDataDetaile("");
    tLogOperate.setIp(IPUtil.getIpAddress(request));
    tLogOperate.setCreateTime(new Date());
    tLogOperate.setCreateUser(sessionUtil.getUserName());
    tLogOperate.setUpdateTime(new Date());
    tLogOperate.setUpdateUser(sessionUtil.getUserName());
    tLogOperateMapper.insert(tLogOperate);

}

6.结果

插入数据库的结果

总结

流程是Controller调用service,service调用切面日志方法 (此处调用要用动态代理getBean形式,切面才会生效),通过注解进入切面类,然后拿到注解上方法的形参(所以注解方法上的形参名字必须是和mapper方法上的名字一致,@Parmer注解的优先注解里面的值),进行sqlSerssion组装sql。

欢迎大神来指点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值