日志审计功能实现
Posted 孤丨焰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日志审计功能实现相关的知识,希望对你有一定的参考价值。
1. 前言
日志审计功能就是将用户进行的增加、修改和删除操作内容、操作方法、操作人以及操作时间等统一格式后集中放入数据库存储,这样做是为了提高系统的安全性,方便系统发生事故后的溯源和恢复。
2. 日志审计实现
2.1 设计数据库
下图为数据库中的结构:
2.2 自定义注解
这里我们需要自己设计一个注解,作为AOP的切点,当执行被注解标记的方法时,则会进入AOP切面执行日志入库的操作。
示例如下:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.lang.annotation.*;
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface LogRecord {
String module() default ""; // 操作模块
String type() default ""; // 操作类型
String desc() default ""; // 操作说明
Class mapperClass() default BaseMapper.class; // 服务类
}
2.3 设计获取IOC容器的工具类
具体IOC容器获取的方法请参考我的上一篇博客:Springboot获取IOC容器的方式。
示例如下:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public SpringContextUtil() {
}
/**
* 设置上下文
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
}
/**
* 获取上下文
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过名字获取上下文中的bean
* @param name
* @return
*/
public static Object getBean(String name){
return applicationContext.getBean(name);
}
/**
* 通过类型获取上下文中的bean
* @param requiredType
* @return
*/
public static Object getBean(Class<?> requiredType){
return applicationContext.getBean(requiredType);
}
}
2.4 标记需要审计的方法
我们选择将LogRecord
注解标注在Service
层的方法上,只要Service
层被标注的方法执行就会进行日志审计并入库。
示例如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
/**
* 根据id修改testEntity中的数据
* @param testEntity 测试用的实体类
* @return
*/
@LogRecord(module = "测试模块", type = "修改", desc = "根据testEntity中的id修改数据", mapperClass = TestMapper.class)
public boolean updateTest(TestEntity testEntity) {
int result = 0;
try {
result = testMapper.updateById(testEntity);
} catch (Exception e) {
log.error("updateTest insert error:{}", e.toString());
}
return result > 0;
}
}
2.5 使用AOP将操作记录入库
最后,我们只需要利用前面铺垫的IOC容器即可实现入库操作。
示例如下:
import com.google.gson.Gson;
import com.jd.jdt.kg.domain.entity.OperLog;
import com.jd.jdt.kg.service.context.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class ValidationAspect {
@Autowired
private LogAuditMapper logAuditMapper; // 用于向日志审计库中存放数据
@Pointcut("@annotation(com.guyan.entity.LogRecord)")
public void logRecordPointCut() {
}
@AfterReturning(value = "logRecordPointCut()", returning = "result")
public void afterReturning(ProceedingJoinPoint joinPoint, Object result) throws Throwable {
log.info("logRecord after begin!");
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperLog operLog = method.getAnnotation(OperLog.class);
// 用于json格式转换
Gson gson = new Gson();
// Mapper层类类
Class mapperClass = operLog.mapperClass();
// mapper层实例名
String simpleNameToLower = mapperClass.getSimpleName().substring(0,1).toLowerCase().concat(mapperClass.getSimpleName().substring(1));
// 从Spring的容器中获取实例
Object instance = SpringContextUtil.getApplicationContext().getBean(simpleNameToLower);
// 获取到要执行的方法
Method selectById = mapperClass.getMethod("selectById", Serializable.class);
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 方法名
String methodName = method.getName();
// 创建日志实体
LogAuditEntity logAuditEntity = new LogAuditEntity();
logAuditEntity.setMethod(className + "." + methodName);
logAuditEntity.setMethodReq(gson.toJson(joinPoint.getArgs()));
logAuditEntity.setModule(operLog.operModule());
logAuditEntity.setType(operLog.operType());
logAuditEntity.setDesc(operLog.operDesc());
// 根据方法返回值截取id值
Long id = getIdByReturn(result);
Object invoke = selectById.invoke(instance, id);
if (invoke == null) {
invoke = "";
}
logAuditEntity.setReturn(gson.toJson(invoke));
try {
logAuditMapper.insert(operLogEntity);
} catch (Throwable e) {
log.error("logRecord Around error:{}", gson.toJson(e));
}
log.info("logRecord after end!");
}
/**
* 根据方法返回值截取id
* @param result
* @return
*/
private Long getIdByReturn(Object result) {
Long id = null;
// 由于每个人的方法返回值结构不同,故此方法体内容根据返回值结构自己更改
return id;
}
}
大家可以根据自己需求自定义getIdByReturn
方法、LogAuditMapper
类和LogAuditEntity
类。
3. 结尾
都看到最后了,求求大家点个赞再走吧!你的支持是博主创作的最大动力。
以上是关于日志审计功能实现的主要内容,如果未能解决你的问题,请参考以下文章