栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

功能开发-用户行为管理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

功能开发-用户行为管理

背景

手上运维着一个MES系统,产线上用户瞎操作后,会告诉我们,系统用不了了,最后反馈到我们开发手上就是:你们系统BUG啊,快解决,花费大量时间的找到原因,多为用户操作不规范导致.于是就有了一个这样的需求,记录用户干了啥.

方案 1.手动写

在需要记录的功能处手动插入日志或者是写数据库,后续可以用类似于日志分析系统对相应数据进行解析

  • 公司没有日志分析系统,所有的日志都是基于单独系统的各种乱七八糟的日志,没法做解析,而且没有资源给我们自己做一个类似的系统
  • 手动插入很繁琐,而且基本上是重复的工作
2.基于AOP实现

简单点说就是:设置一个切点,在方法前后对方法进行拦截操作

2.1简单

依赖


   org.springframework.boot
   spring-boot-starter-aop

定义一个注解

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface DateLog {
    String operModul() default ""; // 操作模块
    String operType() default "";  // 操作类型
    String operDesc() default "";  // 操作说明
}

定义切点

@Aspect
@Component
public class DateLogAspect {
    
    @Pointcut(value = "@annotation(com.leiyuee.manage.Audit.DataLog)" )
    public void pointcut(){}
    //前置
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint){
    }

    //后置
    @After("pointcut()")
    public void doAfter(JoinPoint joinPoint){
        // 使用joinPoint得到方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("调用的方法名:"+methodName);

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        System.out.println(method.toString());
        //获取操作
        Audit audit = method.getAnnotation(Audit.class);
        if (audit != null) {
            System.out.println(audit.operModul()+"---"+audit.operType()+"---"+audit.operDesc());
        }
    }

**使用:**在需要使用的方法上添加注解 @Audit

//@DataLog
@DateLog(operModul = "角色管理",operType = "修改",operDesc = "修改角色")
@PutMapping("/{id}")
public R update(@RequestBody  SysRole sysRole){
    roleService.updateById(sysRole);
    return R.ok(200,"修改成功",null);
}

结果

调用的方法名:save
public com.leiyuee.manage.util.R com.leiyuee.manage.web.controller.manage.SysRoleController.save(com.leiyuee.manage.web.entity.manage.SysRole)
角色管理---新增---新增角色

可以直到用户什么时候是操作了什么方法,可以写到数据库表里面

2.2 增强

上面的简单方法只能记录到用户操作了哪些方法,传了哪些数据,无法记录到数据的变化.Mybatis允许在已映射语句执行过程中的某一点进行拦截调用,通过实现Interceptor接口,来指定拦截方法的签名.
修改注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataLog {
   
   String sPel1() default "";
   
   String sPel2() default "";
   
   String sPel3() default "";
   
   int type() default -1;
   
   String tag() default "";
   
   String note() default "";

修改切面

@Aspect
@Order(99)
@Component
@AllArgsConstructor
public class DataLogAspect {

    private final BaseDataLog baseDataLog;
    
    @PostConstruct
    public void init() {
        baseDataLog.setting();
    }
    
    @Before("@annotation(dataLog)")
    public void before(JoinPoint joinPoint, DataLog dataLog) {
        // 使用 ThreadLocal 记录一次操作
        BaseDataLog.DATA_CHANGES.set(new LinkedList<>());
        BaseDataLog.JOIN_POINT.set(joinPoint);
        BaseDataLog.DATA_LOG.set(dataLog);
        if (baseDataLog.isIgnore(dataLog)) {
            BaseDataLog.DATA_CHANGES.set(null);
        }
    }
    
    @AfterReturning("@annotation(dataLog)")
    public void after(DataLog dataLog) {
        List list = BaseDataLog.DATA_CHANGES.get();
        if (CollUtil.isEmpty(list)) {
            return;
        }
        list.forEach(change -> {
            List oldData = change.getOldData();
            if (CollUtil.isEmpty(oldData)) {
                return;
            }
            List ids = oldData.stream()
                    .map(o -> ReflectUtil.invoke(o, "getId").toString())
                    .filter(ObjectUtil::isNotNull)
                    .map(Long::parseLong)
                    .collect(Collectors.toList());
            SqlSession sqlSession = change.getSqlSessionFactory().openSession();
            try {
                Map map = new HashMap<>(1);
                map.put(Constants.WRAPPER, Wrappers.query().in("id", ids));
                List newData = sqlSession.selectList(change.getSqlStatement(), map);
                change.setNewData(Optional.ofNullable(newData).orElse(new ArrayList<>()));
            } finally {
                SqlSessionUtils.closeSqlSession(sqlSession, change.getSqlSessionFactory());
            }
            System.out.println("oldData:" + JSONUtil.toJsonStr(change.getOldData()));
            System.out.println("newData:" + JSONUtil.toJsonStr(change.getNewData()));
        });
        // 对比调模块
        this.compareAndTransfer(list);
    }
    
    public void compareAndTransfer(List list) {
        StringBuilder sb = new StringBuilder();
        StringBuilder rsb = new StringBuilder();
        list.forEach(change -> {
            List oldData = change.getOldData();
            List newData = change.getNewData();
            // 更新前后数据量不对必定是删除(逻辑删除)不做处理
            if (newData == null) {
                return;
            }
            if (oldData == null) {
                return;
            }
            if (oldData.size() != newData.size()) {
                return;
            }
            // 按id排序
            oldData.sort(Comparator.comparingLong(d -> Long.parseLong(ReflectUtil.invoke(d, "getId").toString())));
            newData.sort(Comparator.comparingLong(d -> Long.parseLong(ReflectUtil.invoke(d, "getId").toString())));
            for (int i = 0; i < oldData.size(); i++) {
                final int[] finalI = {0};
                baseDataLog.sameClazzDiff(oldData.get(i), newData.get(i)).forEach(r -> {
                    String oldV = r.getOldValue() == null ? "无" : r.getOldValue().toString();
                    String newV = r.getNewValue() == null ? "无" : r.getNewValue().toString();
                    if (ObjectUtil.equal(oldV.trim(), newV.trim())) {
                        return;
                    }
                    if (finalI[0] == 0) {
                        sb.append(StrUtil.LF);
                        sb.append(StrUtil.format("修改表:【{}】", change.getTableName()));
                        sb.append(StrUtil.format("id:【{}】", r.getId()));
                    }
                    sb.append(StrUtil.LF);
                    rsb.append(StrUtil.LF);
                    sb.append(StrUtil.format("把字段[{}]从[{}]改为[{}]",
                            r.getFieldName(), r.getOldValue(), r.getNewValue()));
                    rsb.append(StrUtil.indexedFormat(baseDataLog.getLogFormat(),
                            r.getId(), r.getFieldName(), r.getFieldComment(),
                            oldV, newV));
                    finalI[0]++;
                });
            }
        });
        if (sb.length() > 0) {
            sb.deleteCharAt(0);
            rsb.deleteCharAt(0);
        }
        // 存库
        System.err.println("1:"+sb.toString());
        BaseDataLog.DATA_CHANGES.set(list);
        BaseDataLog.LOG_STR.set(rsb.toString());
        baseDataLog.transfer();
    }
}

定义拦截器

@Slf4j
@AllArgsConstructor
@Intercepts({@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})})
public class DataUpdateInterceptor implements Interceptor {
//public class DataUpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {

    @Override
    @SneakyThrows
    public Object intercept(Invocation invocation) {
        // 判断是否需要记录日志
        if (BaseDataLog.DATA_CHANGES.get() == null) {
            return invocation.proceed();
        }
        Statement statement;
        Object firstArg = invocation.getArgs()[0];
        if (Proxy.isProxyClass(firstArg.getClass())) {
            statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
        } else {
            statement = (Statement) firstArg;
        }
        MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
        try {
            statement = (Statement) stmtMetaObj.getValue("stmt.statement");
        } catch (Exception e) {
            // do nothing
        }
        if (stmtMetaObj.hasGetter("delegate")) {
            //Hikari
            try {
                statement = (Statement) stmtMetaObj.getValue("delegate");
            } catch (Exception ignored) {

            }
        }

        String originalSql = statement.toString();
        originalSql = originalSql.replaceAll("[\s]+", StringPool.SPACE);
        int index = indexOfSqlStart(originalSql);
        if (index > 0) {
            originalSql = originalSql.substring(index);
        }
        System.err.println("执行SQL:" + originalSql);

        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
//        this.sqlParser(metaObject);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        // 获取执行Sql
        String sql = originalSql.replace("where", "WHERe");
        // 插入
        if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
        }
        // 更新
        if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())) {
            // 使用mybatis-plus 工具解析sql获取表名
            Collection tables = new TableNameParser(sql).tables();
            if (CollectionUtils.isEmpty(tables)) {
                return invocation.proceed();
            }
            String tableName = tables.iterator().next();
            // 排除表名判断
            if (BaseDataLog.excludeTableNames.contains(tableName)) {
                return invocation.proceed();
            }
            // 使用mybatis-plus 工具根据表名找出对应的实体类
            TableInfo tableInfo = Optional.ofNullable(TableInfoHelper.getTableInfo(tableName))
                    .orElse(new TableInfo(null));
            Class entityType = tableInfo.getEntityType();

            if (entityType == null || entityType.isAnnotationPresent(IgnoreDataLog.class)) {
                return invocation.proceed();
            }
           DataChange change = new DataChange();
            change.setTableName(tableName);
            change.setEntityType(entityType);
            // 设置sql用于执行完后查询新数据
            String selectSql = "AND " + sql.substring(sql.lastIndexOf("WHERe") + 5);
            // 同表对同条数据操作多次只进行一次对比
            if (BaseDataLog.DATA_CHANGES.get().stream().anyMatch(c -> tableName.equals(c.getTableName())
                    && selectSql.equals(c.getWhereSql()))) {
                return invocation.proceed();
            }
            change.setWhereSql(selectSql);
            Map map = new HashMap<>(1);
            map.put(Constants.WRAPPER, Wrappers.query().eq("1", 1).last(selectSql));
            // 查询更新前数据
            SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(entityType);
            change.setSqlSessionFactory(sqlSessionFactory);        change.setSqlStatement(tableInfo.getSqlStatement(SqlMethod.SELECT_LIST.getMethod()));
            SqlSession sqlSession = sqlSessionFactory.openSession();
            try {
                List oldData = sqlSession.selectList(change.getSqlStatement(), map);
                change.setOldData(Optional.ofNullable(oldData).orElse(new ArrayList<>()));
            } finally {
                SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
            }
           BaseDataLog.DATA_CHANGES.get().add(change);
        }
        // 删除
        if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) {
        }
        return invocation.proceed();
    }
    
    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        Set set = new HashSet<>();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (CollectionUtils.isEmpty(set)) {
            return -1;
        }
        List list = new ArrayList<>(set);
        list.sort(Comparator.naturalOrder());
        return list.get(0);
    }
}

对象类

@Data
public class CompareResult {
    
    private Long id;
    
    private String fieldName;
    
    private String fieldComment;
    
    private Object oldValue;
    
    private Object newValue;
}

@Data
public class DataChange {
    
    private SqlSessionFactory sqlSessionFactory;
    
    private String sqlStatement;
    
    private String tableName;
    
    private String whereSql;
    
    private Class entityType;
    
    private List oldData;
    
    private List newData;
}


@Data
public class LogData {
    
    private List dataChanges;
    
    private List compareResults;
    
    private String logStr;
}


public abstract class BaseDataLog {
    
    public static final ThreadLocal DATA_LOG = new ThreadLocal<>();
    
    public static final ThreadLocal JOIN_POINT = new ThreadLocal<>();
    
    public static final ThreadLocal> DATA_CHANGES = new ThreadLocal<>();
    
    public static final ThreadLocal> COMPARE_RESULTS = ThreadLocal.withInitial(ArrayList::new);
    
    public static final ThreadLocal LOG_STR = new ThreadLocal<>();

    
    @Setter
    @Getter
    private String logFormat = "将[{2}]由{3}修改为{4}";


    
    @Setter
    @Getter
    public static List excludeTableNames = new ArrayList<>();

    
    @Setter
    @Getter
    private List excludeFieldNames = new ArrayList<>();

    
    private final SpelExpressionParser parser = new SpelExpressionParser();;
    
    private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    
    public void transfer() {
        LogData data = new LogData();
        data.setDataChanges(DATA_CHANGES.get());
        data.setCompareResults(COMPARE_RESULTS.get());
        data.setLogStr(LOG_STR.get());
        this.change(DATA_LOG.get(), data);

        DATA_CHANGES.remove();
        COMPARE_RESULTS.remove();
        LOG_STR.remove();
        DATA_LOG.remove();
        JOIN_POINT.remove();
    }

    
    public boolean isIgnore(DataLog dataLog) {
        return false;
    }


    
    public BaseDataLog addExcludeTableName(String tableName) {
        excludeTableNames.add(tableName);
        return this;
    }

    
    public BaseDataLog addExcludeFieldName(String fieldName) {
        this.excludeFieldNames.add(fieldName);
        return this;
    }


    
    public abstract void setting();

    
    public abstract void change(DataLog dataLog,LogData data);

    
    public List sameClazzDiff(Object obj1, Object obj2) {
        List results = new ArrayList<>();
        Field[] obj1Fields = obj1.getClass().getDeclaredFields();
        Field[] obj2Fields = obj2.getClass().getDeclaredFields();
        Long id = null;
        for (int i = 0; i < obj1Fields.length; i++) {
            obj1Fields[i].setAccessible(true);
            obj2Fields[i].setAccessible(true);
            Field field = obj1Fields[i];
            if (this.excludeFieldNames.contains(field.getName()) || field.isAnnotationPresent(IgnoreDataLog.class)) {
                continue;
            }
            try {
                Object value1 = obj1Fields[i].get(obj1);
                Object value2 = obj2Fields[i].get(obj2);
                if ("id".equals(field.getName())) {
                    id = Long.parseLong(value1.toString());
                }
                if (value1 == null || StrUtil.isBlank(value1.toString())) {
                    value1 = null;
                }
                if (value2 == null || StrUtil.isBlank(value2.toString())) {
                    value2 = null;
                }
                if (!ObjectUtil.equal(value1, value2)) {
                    if (value1 != null && value2 != null && ObjectUtil.equal(value1.toString().trim(), value2.toString().trim())) {
                        continue;
                    }
                    CompareResult r = new CompareResult();
                    r.setId(id);
                    r.setFieldName(field.getName());
                    // 获取注释
                    r.setFieldComment(field.getName());
//                    ApiModelProperty property = field.getAnnotation(ApiModelProperty.class);
//                    if (property != null && StrUtil.isNotBlank(property.value())) {
//                        r.setFieldComment(property.value());
//                    }
                    r.setOldValue(value1);
                    r.setNewValue(value2);
                    results.add(r);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        COMPARE_RESULTS.get().addAll(results);
        return results;
    }

    
    protected  T getValueBySpEl(String spEl, Class clazz) {
        if (!spEl.contains("#")) {
            //注解的值非SPEL表达式,直接解析就好
            return JSONUtil.toBean(spEl, clazz);
        }
        JoinPoint joinPoint = JOIN_POINT.get();
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        if (paramNames == null || paramNames.length == 0) {
            return null;
        }
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spEl);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext(joinPoint);
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for(int i = 0 ; i < args.length ; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        // 表达式从上下文中计算出实际参数值
        
        return expression.getValue(context, clazz);
    }
}

使用: 在需要记录的位置使用@DataLog注解

@DataLog
@PutMapping("/{id}")
public R update(@RequestBody  SysRole sysRole){
    roleService.updateById(sysRole);
    return R.ok(200,"修改成功",null);
}

结果

oldData:[{"dataScope":"1","delFlag":"0","roleSort":11,"deptCheckStrictly":true,"createBy":"","updateBy":"","menuCheckStrictly":true,"roleName":"打工人11","roleKey":"coder11","id":1540213822982119425,"status":"1"}]
newData:[{"dataScope":"1","delFlag":"0","roleSort":11,"deptCheckStrictly":true,"createBy":"","updateBy":"","menuCheckStrictly":true,"roleName":"打工人","roleKey":"coder","id":1540213822982119425,"status":"1"}]
Time:138 ms - ID:com.leiyuee.manage.web.mapper.manage.SysRoleMapper.updateById
Execute SQL:UPDATE sys_role SET role_name='打工人', role_key='coder', role_sort=11, data_scope='1', menu_check_strictly=1, dept_check_strictly=1, status='1', del_flag='0', create_by='', update_by='' WHERe id=1540213822982119425

 Time:0 ms - ID:com.leiyuee.manage.web.mapper.manage.SysRoleMapper.selectList
Execute SQL:SELECT id,role_name,role_key,role_sort,data_scope,menu_check_strictly,dept_check_strictly,status,del_flag,create_by,create_time,update_by,update_time,remark FROM sys_role WHERe (id IN (1540213822982119425))


可以将相应的修改数据写入数据库,最终实现记录用户详细操作

附上项目地址: manage https://gitee.com/leiyuee/manage.git

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007961.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号