实现异步记录行为日志,并监控数据前后变化,判定高危操作行为预警

实现异步记录行为日志,并监控数据前后变化,判定高危操作行为预警。
此示例为了记录用户操作行为,与数据实际产生的变更,可通过注解控制是否记录。比如 用户张山通过部门管理功能 修改了部门A 为 部门B,修改了负责人A 为 负责人B

注解定义

/**

  • @author wxg
  • @date 2020年3月26日
  • @des 字段注释
    */
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FieldComment {
    // 中文注释
    String chn() default “”;
    // 是否显示明文
    String isShow() default “1”;
    // 指定即使无差异也显示
    String mustShow() default “0”;
    // 是否需要翻译
    String needTranslate() default “0”;
    // 翻译类型
    String translateType() default “”;

}

/**

  • @author wxg
  • @date 2020年3月23日
  • @des 自定义log注解
    /
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogInfo {
    /
    * 请求方式**/
    String requestType() default “POST”;
    /** mapper 路径**/
    String mapperPath() default “”;
    /** 主类 /
    String clazz();
    /
    方法 /
    String method();
    /
    操作名称 /
    String operationName();
    /
    业务名称 /
    String bussiness();
    /
    处理类型 **/
    String handleType();

}

环绕切点定义

@Aspect
@Component
public class LogAspect {

@Autowired
private LogAddService logAddService;
@Autowired
private SqlSession sqlSession;
@Autowired
private TranslateBean translateBean;

@Pointcut("@annotation(com.ehualu.usercenter.common.annotation.LogInfo)")
public void pointcut() { }

@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {

    MethodSignature signature = (MethodSignature) point.getSignature();
    Method method = signature.getMethod();
    LogInfo logAnnotation = method.getAnnotation(LogInfo.class);

    Object result = null;
    long beginTime = DateUtil2.getCurryTimeOfLong();
    LogAddReq logAddReq = new LogAddReq();
    Object oldBean = new Object();
    Object[] args = point.getArgs();

    try {
        if (logAnnotation != null) {
            // 注解上的描述
            if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.UPDATE.getEnName())) {
                //反射获取mapper,这里要写全类名
                Class interfaceImpl = Class.forName(logAnnotation.mapperPath());
                Method mapperMethod = interfaceImpl.getMethod("selectByPrimaryKey", String.class);
                // ,获取旧数据对象
                String id = ObjectUtil.getFieldValueByName(logAnnotation.clazz() + "Id", args[0]);
                // 为了适配 id命名不规则问题加上的
                if (null == id) {
                    id = ObjectUtil.getFieldValueByName("id", args[0]);
                    if (null == id) {
                        id = ObjectUtil.getFieldValueByName("safeRuleCode", args[0]);
                    }
                }
                oldBean = mapperMethod.invoke(sqlSession.getMapper(interfaceImpl), id);
            }else if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.DELETE.getEnName())) {
                Class interfaceImpl = Class.forName(logAnnotation.mapperPath());
                Method mapperMethod = interfaceImpl.getMethod("selectByPrimaryKey", String.class);
                // id要作为对象的第一属性值,获取旧数据对象
                oldBean = mapperMethod.invoke(sqlSession.getMapper(interfaceImpl), args[0]);
            }else if(logAnnotation.handleType().equalsIgnoreCase(MethodConstant.SELECT.getEnName())){
                RequestAttributes ra = RequestContextHolder.getRequestAttributes();
                ServletRequestAttributes sra = (ServletRequestAttributes) ra;
                HttpServletRequest httpServletRequest = sra.getRequest();
                // 当查询请求heard中 toLog不为“1”时,不记录日志
                if (null == httpServletRequest.getHeader("toLog") || !httpServletRequest.getHeader("toLog").equals("1")){
                    return point.proceed();
                }
                // 从header中获取funcUrl ,转化功能名称
                String funcUrl = httpServletRequest.getHeader("funcUrl").replace("/", "");
                Object paramsObj = ObjectUtil.dynamicGetValue(args[0], "searchInfo");
                if(null == paramsObj || paramsObj instanceof String){
                    paramsObj = args[0];
                }
                logAddReq.setParams(translateBean.objectToMapOfChnKey(paramsObj).toString());

                logAddReq.setBussiness(FuncUrlConstant.valueOf(funcUrl).getBussiness());
                logAddReq.setOperationName(FuncUrlConstant.valueOf(funcUrl).getOperationName());
            }

        }
        // 执行方法
        result = point.proceed();
        logAddReq.setHttpMethod(logAnnotation.requestType());
        LoginUser loginUser = UserInfoContext.getUser();
        logAddReq.setUserId(loginUser == null?"":loginUser.getUserId());
        // 执行时长(毫秒)
        long time = DateUtil2.getCurryTimeOfLong() - beginTime;
        logAddReq.setExcuteTime(time);
        logAddReq.setIp(loginUser == null?"":loginUser.getIp());
        logAddReq.setUsername(loginUser == null?"":loginUser.getWorker());

        logAddService.encapsulationLog(point, logAnnotation, args, oldBean, logAddReq);

    } catch (Exception e){
        // 执行方法
        return point.proceed();
    }
    return result;

}

}

异步包装日志,并写入es

/**
* 包装日志
* @param joinPoint
* @throws Exception
*/
@Async
public void encapsulationLog(ProceedingJoinPoint joinPoint, LogInfo logAnnotation,Object[] args, Object oldBean,
LogAddReq logAddReq) throws Exception{

    /** 判断操作行为 **/
    BoolQueryBuilder qb = QueryBuilders.boolQuery();
    qb.must(QueryBuilders.termQuery("clazz", logAnnotation.clazz().toLowerCase()));
    qb.must(QueryBuilders.termQuery("method", logAnnotation.method().toLowerCase()));

    // 加入es
    logAddReq.setClazz(logAnnotation.clazz().toLowerCase());
    logAddReq.setMethod(logAnnotation.method().toLowerCase());
    logAddReq.setBussiness(logAnnotation.bussiness());
    logAddReq.setOperationName(logAnnotation.operationName());
    logAddReq.setOperationType(logAnnotation.handleType());
    logAddReq.setIsWarning("0");
    // 更新
    if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.UPDATE.getEnName())) {
        try {

            if (null != oldBean) {
                String operationDescription = compareBean.contrastObj(oldBean, args[0]).toString();
                // 权限特殊处理
                if(logAnnotation.clazz().equalsIgnoreCase("funcPriv")){

// List funcPrivRelList, List orgFuncPrivRelList,
// List accountFuncPrivRelList
operationDescription = operationDescription + “。
权限更新为:”;
for (Object object: (List)args[2]) {
operationDescription += translateBean.objectToMapOfChnKey(object).toString();
}
for (Object object: (List)args[3]) {
operationDescription += translateBean.objectToMapOfChnKey(object).toString();
}
for (Object object: (List)args[4]) {
operationDescription += translateBean.objectToMapOfChnKey(object).toString();
}
}
logAddReq.setOperationDescription(operationDescription);
}
// 高危行为 是“1”; 否 “0”
logAddReq.setIsWarning(judgmentBehavior(qb));
} catch (Exception e) {
e.printStackTrace();
}
} else if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.DELETE.getEnName())) {
logAddReq.setOperationDescription(translateBean.objectToMapOfChnKey(oldBean).toString());
// 高危行为 是“1”; 否 “0”
logAddReq.setIsWarning(judgmentBehavior(qb));
} else if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.INSERT.getEnName())) {
logAddReq.setOperationDescription(translateBean.objectToMapOfChnKey(args[0]).toString());
} else if (logAnnotation.handleType().equalsIgnoreCase(MethodConstant.SELECT.getEnName())) {

    } else if(logAnnotation.handleType().equalsIgnoreCase(MethodConstant.IMPORT.getEnName())){

    } else if(logAnnotation.handleType().equalsIgnoreCase(MethodConstant.EXPORT.getEnName())) {

    }
    else{
        throw new Exception();
    }
    // 存入es 和 数据库
    this.addUserCenterLog(logAddReq);
}


/**
 * @des es查询日志行为
 * @param qb
 * @return boolean
 **/
private String judgmentBehavior(BoolQueryBuilder qb){
    // 近30分钟
    qb.must(QueryBuilders.rangeQuery("startTime")
            .gt(DateUtil2.localDateTimeToLong(LocalDateTime.now().minusMinutes(30))));
    Long hitsCount = es.getHitsCount(index, type, qb);
    // 如果近30分未进行过操作,则验证本日是否操作超过10次
    if(hitsCount == 0){
        qb.must(QueryBuilders.rangeQuery("startTime")
                .gt( DateUtil2.localDateTimeToLong(LocalDateTime.of(LocalDate.now(), LocalTime.MIN))));
        hitsCount = es.getHitsCount(index, type, qb);
        return hitsCount > 10?"1":"0";
    } else {
        return "1";
    }

}

对象差异性比较,并通过对DTO自定义注解进行字段中文转义,码值翻译、加密、翻译等处理

/**

  • @author wxg

  • @date 2020年3月23日

  • @des 比较两个bean之间差异性

  • @param
    */
    @Component
    public class CompareBean {

    // 静态单例
    private static CompareBean compareBean;
    @Autowired
    private TranslateService translateService;
    @PostConstruct
    private void init() {
    compareBean = this;
    compareBean.translateService = this.translateService;
    }

    public StringBuffer contrastObj(Object oldBean, Object newBean) {
    // 记录变更
    StringBuffer changeInfoSb = new StringBuffer();

     T pojo1 = (T) oldBean;
     T pojo2 = (T) newBean;
     try {
         Class clazz = pojo1.getClass();
         Field[] fields = pojo1.getClass().getDeclaredFields();
         int i=1;
         for (Field field : fields) {
             if("serialVersionUID".equals(field.getName())){
                 continue;
             }
             // 设置可以访问私有方法
             field.setAccessible(true);
             FieldComment fieldComment = field.getAnnotation(FieldComment.class);
             if(fieldComment!=null) {
                 PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
                 Method getMethod = pd.getReadMethod();
                 Object o1 = getMethod.invoke(pojo1);
                 Object o2 = getMethod.invoke(pojo2);
                 // 需记录的名称
                 if(fieldComment.mustShow().equals("1")){
                     changeInfoSb.insert(0, fieldComment.chn() + ": " + o1 + " ");
                 }
                 if (o1 == null || o2 == null) {
                     continue;
                 }
                 if (!o1.toString().equals(o2.toString())) {
                     if (i != 1) {
                         changeInfoSb.append(";");
                     }
                     if(fieldComment.isShow().equals("1")) {
                         // 翻译码值
                         if(fieldComment.needTranslate().equals("1")){
                             o1 = translateService.translateField(fieldComment.translateType(), o1.toString());
                             o2 = translateService.translateField(fieldComment.translateType(), o2.toString());
                         }
                         changeInfoSb.append(fieldComment.chn() + ":由“" + o1 + "”修改为“" + o2 + "”");
                     } else {
                         changeInfoSb.append(fieldComment.chn() + ":由 ****** 修改为 ******");
                     }
                     i++;
                 }
             }
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
    
     return changeInfoSb;
    

    }
    }

数据实例转map格式化输出

@Component
public class TranslateBean {

// 静态单例
private static TranslateBean translateBean;
@Autowired
private TranslateService translateService;
@PostConstruct
private void init() {
    translateBean = this;
    translateBean.translateService = this.translateService;
}


/**
 * Object转map 中文key
 * @param obj
 * @return
 * @throws Exception
 */
public Map objectToMapOfChnKey(Object obj) throws Exception {
    if(obj == null) {
        return null;
    }
    LinkedHashMap map = new LinkedHashMap<>();

    Field[] fields = obj.getClass().getDeclaredFields();
    for (Field field :fields) {
        field.setAccessible(true);
        FieldComment fieldComment = field.getAnnotation(FieldComment.class);
        if(fieldComment!=null){
            if(fieldComment.isShow().equals("1")){
                Object fieldVal = field.get(obj);
                if(fieldVal instanceof Date){
                    fieldVal = DateUtil2.format((Date) fieldVal,"yyyy-MM-dd HH:mm:ss");
                }
                String filedStringVal = fieldVal == null? "": fieldVal.toString();
                // 翻译码值
                if(fieldComment.needTranslate().equals("1")){
                    filedStringVal = translateService.translateField(fieldComment.translateType(), filedStringVal);
                }
                map.put(fieldComment.chn(), filedStringVal);
            } else {
                map.put(fieldComment.chn(), "******");
            }
        }
    }

    return map;
}

}

码值翻译服务类

/**

  • @des 码值翻译

  • @author wxg

  • @date 2020年3月30日
    */
    @Service
    public class TranslateService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrgMapper orgMapper;
    @Autowired
    private FuncMapper funcMapper;
    @Autowired
    private RegionMapper regionMapper;
    @Autowired
    private FuncPrivMapper funcPrivMapper;
    @Autowired
    private AccountMapper accountMapper;

    public String translateField(String translateType, String value){

     switch (translateType){
         case "userId": return this.translateUserId(value);
         case "orgId":  return this.translateOrgId(value);
         case "userType": return this.translateUserType(value);
         case "funcId": return this.translateFuncId(value);
         case "dspFlag": return this.translateDspFlag(value);
         case "idType": return this.translateIdType(value);
         case "areaId": return this.translateArea(value);
         case "isValid": return this.translateIsValid(value);
         case "status": return this.translateStatus(value);
         case "searchType": return this.translateSearchType(value);
         case "funcType": return this.translateFuncType(value);
         case "orgSearchType": return this.translateOrgSearchType(value);
         case "safeRuleType": return this.translateSafeRuleType(value);
         case "funcPrivId": return this.translateFuncPrivId(value);
         case "accountId": return this.translateAccountId(value);
         default: return "无";
     }
    

    }

    /**

    • 翻译账号
      */
      private String translateAccountId(String accountId){
      Account account = accountMapper.selectByPrimaryKey(accountId);
      if(null == account){
      return “无”;
      }
      return account.getUsername();
      }

    /**

    • 角色权限id
    • @param funcPrivId
    • @return
      */
      private String translateFuncPrivId(String funcPrivId){
      FuncPriv funcPriv = funcPrivMapper.selectByPrimaryKey(funcPrivId);
      if(null == funcPriv){
      return “无”;
      }
      return funcPriv.getFuncPrivName();
      }

    /**

    • 安全规则类型
    • @param safeRuleType
    • @return
      */
      private String translateSafeRuleType(String safeRuleType){
      if(safeRuleType.equalsIgnoreCase(“1”)){
      return “密码安全”;
      }else if(safeRuleType.equalsIgnoreCase(“2”)){
      return “登录安全”;
      }
      return “所有”;
      }

    /**

    • 机构查询类型
    • @return
      */
      private String translateOrgSearchType(String orgSearchType){
      if(orgSearchType.equalsIgnoreCase(“1”)){
      return “机构名称”;
      } else if(orgSearchType.equalsIgnoreCase(“2”)){
      return “组织机构编码”;
      } else if(orgSearchType.equalsIgnoreCase(“3”)){
      return “上级机构名称”;
      }
      return “查询所有”;
      }

    /**

    • 功能类型
    • @param funcType
    • @return
      */
      private String translateFuncType(String funcType){
      if(funcType.equalsIgnoreCase(“01”)){
      return “系统”;
      } else if(funcType.equalsIgnoreCase(“02”)){
      return “模块”;
      }
      return “无”;
      }

    /**

    • 查询类型
    • @param searchType
    • @return
      */
      private String translateSearchType(String searchType){
      if(searchType.equalsIgnoreCase(“1”)){
      return “功能编码”;
      } else if(searchType.equalsIgnoreCase(“2”)){
      return “功能名称”;
      }
      return “查询所有”;
      }

    /**

    • 安全规则状态
      */
      private String translateStatus(String status){
      if(status.equalsIgnoreCase(“0”)){
      return “开启”;
      } else if(status.equalsIgnoreCase(“1”)){
      return “关闭”;
      }
      return “所有”;
      }

    /**

    • 锁定状态
    • @param isValid
    • @return
      */
      private String translateIsValid(String isValid){
      if(isValid.equals(“1”)){
      return “锁定”;
      }
      return “正常”;
      }

    /**

    • 地区
      */
      private String translateArea(String areaId){
      Region region = regionMapper.selectByPrimaryKey(areaId);
      if(null != region){
      return region.getName();
      }
      return “无”;
      }

    /**

    • 证件类型
    • @param idType
    • @return
      */
      private String translateIdType(String idType){
      if(idType.equals(“0”)){
      return “身份证”;
      } else if(idType.equals(“1”)){
      return “护照”;
      } else if(idType.equals(“2”)){
      return “港澳台身份证”;
      }
      return “无”;
      }

    /**

    • 是否可见
      */
      private String translateDspFlag(String dspFlag){
      if(dspFlag.equals(“0”)){
      return “可见”;
      }
      return “不可见”;
      }

    /**

    • func功能
      */
      private String translateFuncId(String funcId){
      Func func = funcMapper.selectByPrimaryKey(funcId);
      if(null != func){
      return func.getFuncName();
      }
      return “无”;
      }

    /**

    • 账号类型
      */
      private String translateUserType(String userType){
      if(userType.equals(“0”)){
      return “政府”;
      } else if(userType.equals(“1”)){
      return “企业”;
      } else if(userType.equals(“2”)){
      return “个人”;
      }
      return “无”;
      }

    /**

    • 翻译机构
      */
      private String translateOrgId(String orgId){
      Org org = orgMapper.selectByPrimaryKey(orgId);
      if(null != org){
      return org.getOrgName();
      }
      return “无”;
      }

    /**

    • 翻译用户
    • @param userId
    • @return
      */
      private String translateUserId(String userId){
      User user = userMapper.selectByPrimaryKey(userId);
      if(null != user){
      return user.getName();
      }
      return “无”;
      }

}

查询类在controller层的使用,并记录查询参数

@PostMapping("/_search")
@LogInfo(operationName = “账号查询”, bussiness = “账号管理”, clazz = “account”,
handleType = “select”, method = “searchAccountByCondition”)
public String searchAccountByCondition(@RequestBody AccountSearchReq accountSearchReq) {
return JSONObject.toJSONString(accountSearchService.selectAccountByCondition(accountSearchReq));
}

查询参数

@Data
public class AccountSearchReq {
private SearchInfo searchInfo;
private Integer index;
private Integer num;
private Sort sort;

@Data
public class SearchInfo {
    @FieldComment(chn = "关联账号")
    private String name;
    @FieldComment(chn = "所属机构")
    private String orgName;
    @FieldComment(chn = "账号名")
    private String username;
    private String orgId;
}

}

dao层记录增、改、删

1、

@LogInfo(operationName = “用户修改”, bussiness = “用户管理”, clazz = “account”,handleType = “update”,
method = “updateByExampleSelective”,mapperPath = “com.ehualu.usercenter.business.account.dao.AccountMapper”)
int updateByExampleSelective(@Param(“record”) Account record, @Param(“example”) AccountExample example);

2、

@LogInfo(operationName = “用户修改”, bussiness = “用户管理”, requestType = “PUT”, clazz = “account”,handleType = “update”,
method = “updateByPrimaryKeySelective”,mapperPath = “com.ehualu.usercenter.business.account.dao.AccountMapper”)
int updateByPrimaryKeySelective(Account record);

3、

@LogInfo(operationName = “用户删除”, bussiness = “用户管理”, requestType = “DELETE”, clazz = “account”,
handleType = “delete”, method = “deleteByPrimaryKey”,mapperPath = “com.ehualu.usercenter.business.account.dao.AccountMapper”)
int deleteByPrimaryKey(String accountId);

增、改、删类记录中文字段名,属性变化、码值翻译转化,mustShow控制比对时,即使无差异,也记录,比如管理员张三 对 用户李四修改了 职务。

@Data
public class Account {
private String accountId;

private String accountCode;
@FieldComment(chn = "用户名", mustShow = "1")
private String username;
@FieldComment(chn = "密码", isShow = "0")
private String password;
@FieldComment(chn = "注册时间")
private Date regDate;
@FieldComment(chn = "关联人员", needTranslate = "1", translateType = "userId")
private String userId;

private Integer isFirstLogin;
@FieldComment(chn = "创建者")
private String optUser;
@FieldComment(chn = "锁定状态", needTranslate = "1", translateType = "isValid")
private Integer isValid;
@FieldComment(chn = "账号类型", needTranslate = "1", translateType = "userType")
private Integer userType;

private Date lastLoginTime;
@FieldComment(chn = "来源")
private String platformId;
@FieldComment(chn = "组织结构", needTranslate = "1", translateType = "orgId")
private String orgId;
@FieldComment(chn = "账号有效期")
private Integer accountValidity;
@FieldComment(chn = "密码有效期")
private Integer passwordValidity;
@FieldComment(chn = "密码更新时间")
private Date passwordUpdateTime;

}

你可能感兴趣的:(行为日志,切面,数据变化,java,spring,spring,boot)