实现异步记录行为日志,并监控数据前后变化,判定高危操作行为预警。
此示例为了记录用户操作行为,与数据实际产生的变更,可通过注解控制是否记录。比如 用户张山通过部门管理功能 修改了部门A 为 部门B,修改了负责人A 为 负责人B
/**
}
/**
}
@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;
}
}
/**
* 包装日志
* @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";
}
}
/**
@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;
}
}
@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 "无";
}
}
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
}
@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;
}
}
@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);
@LogInfo(operationName = “用户修改”, bussiness = “用户管理”, requestType = “PUT”, clazz = “account”,handleType = “update”,
method = “updateByPrimaryKeySelective”,mapperPath = “com.ehualu.usercenter.business.account.dao.AccountMapper”)
int updateByPrimaryKeySelective(Account record);
@LogInfo(operationName = “用户删除”, bussiness = “用户管理”, requestType = “DELETE”, clazz = “account”,
handleType = “delete”, method = “deleteByPrimaryKey”,mapperPath = “com.ehualu.usercenter.business.account.dao.AccountMapper”)
int deleteByPrimaryKey(String accountId);
@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;
}