天天看点

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

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

此示例为了记录用户操作行为,与数据实际产生的变更,可通过注解控制是否记录。比如 用户张山通过部门管理功能 修改了部门A 为 部门B,修改了负责人A 为 负责人B

注解定义

@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 “”;

}

String requestType() default “POST”;

String mapperPath() default “”;

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

@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自定义注解进行字段中文转义,码值翻译、加密、翻译等处理

@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<String, Object> objectToMapOfChnKey(Object obj) throws Exception {
    if(obj == null) {
        return null;
    }
    LinkedHashMap<String, Object> 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;
}
           

}

码值翻译服务类

@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();

}

private String translateFuncPrivId(String funcPrivId){

FuncPriv funcPriv = funcPrivMapper.selectByPrimaryKey(funcPrivId);

if(null == funcPriv){

return “无”;

}

return funcPriv.getFuncPrivName();

}

private String translateSafeRuleType(String safeRuleType){

if(safeRuleType.equalsIgnoreCase(“1”)){

return “密码安全”;

}else if(safeRuleType.equalsIgnoreCase(“2”)){

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 “查询所有”;

}

private String translateFuncType(String funcType){

if(funcType.equalsIgnoreCase(“01”)){

return “系统”;

} else if(funcType.equalsIgnoreCase(“02”)){

return “模块”;

}

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 “所有”;

}

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 “无”;

}

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 “不可见”;

}

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 “无”;

}

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;
           

}