SpringBoot+Mybatis-plus+aop实现一个类似JPA的@Audited注解(字段审计)功能

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/qq_27948811/article/details/88918021

SpringDataJPA中有个注解@Audited

只需在Entity中加上注解@Audited,就会自动帮你记录下Entity对应的表的所有操作记录insert,update,delete,会在数据库帮你生成一张表xxx_AUD;

这是在SpringDataJPA框架才能使用的一个注解,如果使用mybatis怎么办呢?

废话不多说,下面直接上代码:

需要添加的依赖主要有下面这些:

    compile('org.springframework.boot:spring-boot-starter-web')
    compile('com.baomidou:mybatis-plus-boot-starter:2.2.0')
    compile('org.apache.logging.log4j:log4j-1.2-api')
    compile('org.springframework.boot:spring-boot-starter-aop')


    compile('com.alibaba:druid:1.1.6')
    compile('org.projectlombok:lombok:1.16.10')
    compile('com.alibaba:fastjson:1.2.47')
    compile('org.springframework.boot:spring-boot-starter-undertow')
    compile('mysql:mysql-connector-java:5.1.21')

 数据库用的sql server,然后新建一张表来记录其他表的操作日志: 

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("SqlOperateLog")
public class SqlOperationLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableField("ID")
    private Long id;

    @TableField("Type")
    private String type;

    @TableField("AfterValue")
    private String afterValue;

    @TableField("BeforeValue")
    private String beforeValue;

    @TableField("CreateTime")
    private Date createTime;

    @TableField("TableName")
    private String tableName;

    @TableField("UserID")
    private Integer userId;

}

与之对应的mapper:

@Repository
@Mapper
public interface SqlOperationLogMapper extends BaseMapper<SqlOperationLog> {
}

注意: SqlOperationLog 和他的mapper不要放mapper包下了,下面aop会扫描这个包;

新建一个类实现ApplicationContextAware,方便从spring容器中获取bean

@Component
public class RecordBeanFactory implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (this.applicationContext == null) {
            this.applicationContext = applicationContext;
        }
    }

    public static Object getBean(Class<?> clazz) {
        if (applicationContext == null) {
            return null;
        }
        return applicationContext.getBean(clazz);
    }

}

拦截器记录操作用户信息:

public class MyInterceptor implements HandlerInterceptor {

    public static final ThreadLocal<UserInfo> USER_INFO = new ThreadLocal<>();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从security的context中获取登陆用户信息
        UserVO vo = (UserVO) SecurityContextHolder
                .getContext()
                .getAuthentication()
                .getPrincipal();
        UserInfo userInfo = UserInfo.builder()
                .userId(vo.getUserId())
                .userName(vo.getNickName())
                .userIp(request.getRemoteAddr())
                .build();
        USER_INFO.set(userInfo);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        USER_INFO.remove();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

因为这里用了Spring security框架,可以直接从SecurityContextHolder上下文中获取登陆用户的信息;如果你用了别的框架,换别的实现也是一样的;这里不再细说

最后就是最重要的aop的代码:

@Aspect
@Component
public class SqlAspect {

    private SqlOperationLog sqlOperationLog;

    @Autowired
    private SqlOperationLogMapper sqlOperationLogMapper;

    /**
     * mybatis-plus表操作都返回的是integer,表达式根究自己项目包结构来写,这里不展示我的包结构了
     */
    @Pointcut("execution(Integer com.demo.demo.mapper.*.*(..))")
    private void mapper() {
    }

    /**
     * 记录表的操作日志,insert,update,delete(逻辑删除)
     *
     * @param joinPoint
     */
    @Around("mapper()")
    public Object recordOperate(ProceedingJoinPoint joinPoint) throws Exception {
        // 获取被代理对象
        Object target = joinPoint.getTarget();
        MapperProxy invo = (MapperProxy) Proxy.getInvocationHandler(target);
        Class<? extends MapperProxy> mapperProxy = invo.getClass();
        Field mapperInterface1 = mapperProxy.getDeclaredField("mapperInterface");
        mapperInterface1.setAccessible(true);
        Class proxyClass = (Class) mapperInterface1.get(invo);

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 被代理方法的参数
        Object[] args = joinPoint.getArgs();
        sqlOperationLog = new SqlOperationLog();
        sqlOperationLog.setType(method.getName());
        sqlOperationLog.setUserId(MyInterceptorConfig.USER_INFO.get().getUserId());

        String beforeValue = beforeOperate(args, proxyClass);
        sqlOperationLog.setBeforeValue(beforeValue);
        // 执行sql
        Integer result = 0;
        try {
            result = (Integer) joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        // 执行成功
        if (result > 0) {
            String afterValue = afterOperate(args);
            sqlOperationLog.setAfterValue(afterValue);
        }
        sqlOperationLogMapper.insert(sqlOperationLog);
        return result;
    }

    /**
     * 操作前的值
     *
     * @param args       参数
     * @param proxyClass 被代理类
     * @return 修改器的值, json格式
     * @throws Exception
     */
    private String beforeOperate(Object[] args, Class proxyClass) throws Exception {
        for (Object arg : args) {
            Class<?> clazz = arg.getClass();
            String entityName = clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
              
            Object bean = RecordBeanFactory.getBean(proxyClass);
            // 获取mapper的selectById方法
            Method selectById = proxyClass.getMethod("selectById", Serializable.class);
            Method[] methods = clazz.getDeclaredMethods();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                // 每个entity对应表的主键ID都有@TableId注解
                if (declaredField.getAnnotation(TableId.class) != null) {
                    System.out.println("主键:" + declaredField.getName());
                    // 获取主键的值,因为每个表的主键名字不一定都是id
                    String primaryKeyMethod = "get" + declaredField.getName();
                    for (Method method : methods) {
                        if (primaryKeyMethod.equalsIgnoreCase(method.getName())) {
                            // 调用getId方法
                            Object id = method.invoke(arg);
                            // 调用对应mapper的selectById
                            Object invoke = selectById.invoke(bean, id);
                            System.out.println("修改前==" + JSON.toJSONString(invoke));
                            return JSON.toJSONString(invoke);
                        }
                    }

                }
            }
        }
        return null;
    }

    /**
     * 数据库操作后
     *
     * @param args 修改的对象
     * @return 修改后的对象json字符串
     */
    public String afterOperate(Object[] args) throws Exception {
        if (args == null) {
            return null;
        }
        // 遍历参数对象
        for (Object arg : args) {
            // 获取对象类型
            Class<?> clazz = arg.getClass();
            TableName annotation = clazz.getAnnotation(TableName.class);
            if (annotation == null) {
                return null;
            }
            String tableName = annotation.value();
            sqlOperationLog.setTableName(tableName);
            sqlOperationLog.setCreateTime(new Date());
            return JSON.toJSONString(arg);
        }
        return null;
    }
}

以上就是全部的相关代码,如有问题,欢迎交流

猜你喜欢

转载自blog.csdn.net/qq_27948811/article/details/88918021
今日推荐