审计日志,即用户操作日志,用于将用户的全部或关键操作持久化的记录下来,以备数据出现问题或者系统重要数据发生泄露,反向查找或追责。
创建审计日志实体类
@Getter
@Setter
public class ActionMetaData {
private boolean needActionRecord = true;
/**
* 因为新增和修改公用的都是一个方法,如果都用这个actionRecord的话,没办法重新判断赋予新的类型,所以冗余出其相关字段
* 下面的4个
*/
private ActionRecord actionRecord;
/**
* 系统
*/
private SystemConsts system;
/**
* 操作类型
*/
private OperationTypeConsts operationType;
/**
* 具体操作
*/
private OperationConsts operation;
/**
* 参数
*/
private String parameter;
/**
* 处理之前的数据
*/
private Object beforeData;
/**
* 方法执行之后返回的数据
*/
private Object afterData;
/**
* 个性化处理类
*/
private ActionRecordBuilder actionRecordBuilder;
/**
* 删除等操作前,查出关键信息名称,用于显示
*/
private String tempName;
/**
* 查询的表名
*/
private String tableName;
/**
* 查询的字段名
*/
private String colunmName;
/**
* 操作描述
*/
private String operationDesc;
}
创建自定义注解
/**
* ActionRecord
* @description 记录用户操作日志 对数据库的关键性操作日志
* 【用户】于【操作日期:年月日时分秒】【操作】【具体内容】
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActionRecord {
/**
* 系统
* @return
*/
SystemConsts system() default SystemConsts.DEFAULT;
/**
* 操作类型
* @return 操作类型
*/
OperationTypeConsts operationType() default OperationTypeConsts.DEFAULT;
/**
* 具体操作步骤
* @return 具体操作步骤
*/
OperationConsts operation() default OperationConsts.DEFAULT;
/**
* 参数名称
* @return 参数名称
*/
String parameter();
}
创建注解切面处理方法
@Aspect
@Component
public class ActionRecordAspect {
@Autowired
private ISecurityService securityService;
@Autowired
private ActionRecordBuilderFactory builderFactory;
@Autowired
private AuditLogUtil auditLogUtil;
@Resource
private JdbcTemplate jdbcTemplate;
private static final ThreadLocal<ActionMetaData> actionMetaDataContainer = ThreadLocal.withInitial(ActionMetaData::new);
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Pointcut("@annotation(ActionRecord)")
public void controllerAspect() {
//ignore
}
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) {
try {
ActionMetaData actionMetaData = ActionRecordAspect.actionMetaDataContainer.get();
Signature sig = joinPoint.getSignature();
if (!(sig instanceof MethodSignature)) {
log.warn("添加操作记录失败! 原因:@ActionRecord 注解只能用于方法!");
return;
}
Method method = ((MethodSignature) sig).getMethod();
actionMetaData.setBeforeData(((Object[]) joinPoint.getArgs())[0]);
actionMetaData.setActionRecord(method.getAnnotation(ActionRecord.class));
actionMetaData.setSystem(actionMetaData.getActionRecord().system());
actionMetaData.setOperationType(actionMetaData.getActionRecord().operationType());
actionMetaData.setOperation(actionMetaData.getActionRecord().operation());
actionMetaData.setParameter(actionMetaData.getActionRecord().parameter());
try {
builderFactory.initBuilder(actionMetaData);
} catch (Exception e) {
log.error("操作记录数据处理失败!", e);
}
Object beforeData = actionMetaData.getBeforeData();
//id类型的需要去提前查询名称!
//……
}
} catch (Exception e){
log.error("审计日志前置处理异常!", e);
}
}
@AfterReturning(returning = "object", pointcut = "controllerAspect()")
public void doAfter(Object object) {
ActionMetaData actionMetaData = ActionRecordAspect.actionMetaDataContainer.get();
try {
if (actionMetaData.getAfterData() == null) {
actionMetaData.setAfterData(object);
}
String operationDesc = actionMetaData.getActionRecordBuilder().buildActionRecord();
actionMetaData.setOperationDesc(operationDesc);
saveToAuditLog(actionMetaData);
} catch (Exception e) {
log.error("审计日志后置处理异常!", e);
} finally {
ActionRecordAspect.actionMetaDataContainer.remove();
}
}
/**
* 保存审计日志
*
* @param actionMetaData
* @return
*/
private boolean saveToAuditLog(ActionMetaData actionMetaData) {
if(!actionMetaData.isNeedActionRecord()){
return false;
}
AuditLog auditLog = new AuditLog();
IUser currUserInfo = securityService.getCurrUserInfo();
auditLog.setUserId(currUserInfo.getId());
auditLog.setLoginId(currUserInfo.getLoginId());
auditLog.setUser(currUserInfo.getName());
auditLog.setCorpId(currUserInfo.getCorpId());
auditLog.setSystem("xxxx");
auditLog.setModule(actionMetaData.getSystem().getSystemName());
auditLog.setFunction(actionMetaData.getOperationType().getOperationTypeName());
String operationDescPre = currUserInfo.getName() + " 于 " + SIMPLE_DATE_FORMAT.format(new Date()) + " ";
auditLog.setContent(operationDescPre + actionMetaData.getOperationDesc());
auditLog.setResult("成功");
return auditLogUtil.saveAuditLog(auditLog);
}
}
创建【系统类型】枚举类
@Getter
public enum SystemConsts {
DEFAULT("DEFAULT", "未知系统"),
A_XT("A_XT", "A系统"),
B_XT("B_XT", "B系统");
private final String system;
private final String systemName;
SystemConsts(String system, String systemName){
this.system = system;
this.systemName = systemName;
}
}
创建【操作类型】枚举类
@Getter
public enum OperationTypeConsts {
DEFAULT("DEFAULT", "未知操作"),
CREATE("CREATE", "创建"),
UPDATE("UPDATE", "编辑"),
DELETE("DELETE", "删除"),
UPLOAD("UPLOAD", "上传"),
DOWNLOAD("DOWNLOAD", "下载"),
PRINT("PRINT", "打印"),
DEPLOY("DEPLOY", "发布"),
IMPORT("IMPORT", "导入"),
EXPORT("EXPORT", "导出");
private final String operationType;
private final String operationTypeName;
OperationTypeConsts(String operationType, String operationTypeName){
this.operationType = operationType;
this.operationTypeName = operationTypeName;
}
}
创建【详细操作内容】枚举类
@Getter
public enum OperationConsts {
//【用户】于【操作日期:年月日时分秒】【操作】【具体内容】
DEFAULT("DEFAULT", "未知操作"),
/****************************************** 专用方案 ********************************************************/
CREATE_FIGHTPLAN("CREATE_FIGHTPLAN", "创建 专用方案,方案名称: %s"),
UPDATE_FIGHTPLAN("UPDATE_FIGHTPLAN", "编辑 专用方案,方案名称: %s"),
DELETE_FIGHTPLAN("DELETE_FIGHTPLAN", "删除 专用方案,方案名称: %s"),
DOWNLOAD_FIGHTPLAN("DOWNLOAD_FIGHTPLAN", "下载 专用方案,方案名称: %s"),
PRINT_FIGHTPLAN("PRINT_FIGHTPLAN", "打印 专用方案,方案名称: %s"),
/****************************************** 文件管理 ********************************************************/
UPLOAD_FILE("UPLOAD_FILE", "上传 文件《%s》"),
DELETE_FILE("DELETE_FILE", "删除 文件《%s》"),
BATCH_DELETE_FILE("BATCH_DELETE_FILE", "批量删除 文件"),
DOWNLOAD_FILE("DOWNLOAD_FILE", "下载 文件《%s》"),
BATCH_DOWNLOAD_FILE("BATCH_DOWNLOAD_FILE", "批量下载 文件");
private final String operation;
private final String operationName;
OperationConsts(String operation, String operationName){
this.operation = operation;
this.operationName = operationName;
}
}
构造审计日志内容工厂类
@Slf4j
@Component
public class ActionRecordBuilderFactory {
private static final String ACTION_DEFAULT = ActionRecordBuilder.class.getName();
private static final String ACTION_FIGHT_PLAN = ActionRecordBuilderFightPlan.class.getName();
private static final String ACTION_USER_MANUAL = ActionRecordBuilderFileManger.class.getName();
private static final Map<String, String> AFTER_ACTION_MAP = Maps.newHashMap();
static {
//专用方案
AFTER_ACTION_MAP.put(OperationConsts.CREATE_FIGHTPLAN.getOperation(), ACTION_FIGHT_PLAN);
AFTER_ACTION_MAP.put(OperationConsts.UPDATE_FIGHTPLAN.getOperation(), ACTION_FIGHT_PLAN);
AFTER_ACTION_MAP.put(OperationConsts.DELETE_FIGHTPLAN.getOperation(), ACTION_FIGHT_PLAN);
AFTER_ACTION_MAP.put(OperationConsts.DOWNLOAD_FIGHTPLAN.getOperation(), ACTION_FIGHT_PLAN);
AFTER_ACTION_MAP.put(OperationConsts.PRINT_FIGHTPLAN.getOperation(), ACTION_FIGHT_PLAN);
//文件管理
AFTER_ACTION_MAP.put(OperationConsts.UPLOAD_FILE.getOperation(), ACTION_USER_MANUAL);
AFTER_ACTION_MAP.put(OperationConsts.DELETE_FILE.getOperation(), ACTION_USER_MANUAL);
AFTER_ACTION_MAP.put(OperationConsts.DOWNLOAD_FILE.getOperation(), ACTION_USER_MANUAL);
AFTER_ACTION_MAP.put(OperationConsts.BATCH_DELETE_FILE.getOperation(), ACTION_USER_MANUAL);
AFTER_ACTION_MAP.put(OperationConsts.BATCH_DOWNLOAD_FILE.getOperation(), ACTION_USER_MANUAL);
}
/**
* 初始化builder
* @param actionMetaData
* @return
* @throws ReflectiveOperationException
*/
ActionRecordBuilder initBuilder(ActionMetaData actionMetaData) throws ReflectiveOperationException {
String operation = actionMetaData.getActionRecord().operation().getOperation();
String className = AFTER_ACTION_MAP.getOrDefault(operation, ACTION_DEFAULT);
Class<?> clazz = Class.forName(className);
ActionRecordBuilder builder = (ActionRecordBuilder) ConstructorUtils.invokeConstructor(clazz,
new Object[]{actionMetaData}, new Class[]{ActionMetaData.class});
actionMetaData.setActionRecordBuilder(builder);
return builder;
}
}
默认的实现处理类
public class ActionRecordBuilder {
protected ActionMetaData actionMetaData;
protected ActionRecord actionRecord;
public ActionRecordBuilder() {
}
public ActionRecordBuilder(ActionMetaData actionMetaData){
this.actionMetaData = actionMetaData;
this.actionRecord = actionMetaData.getActionRecord();
}
/**
* 进入切面前的数据处理
*/
public void processBeforeActionRecordData(){
}
/**
* 执行方法后,构造审计日志描述信息
* @return
* @throws JsonProcessingException
*/
public String buildActionRecord() throws JsonProcessingException {
return actionMetaData.getOperation().getOperationName();
}
}
自定义实现处理类(例:文件管理相关审计日志)
public class ActionRecordBuilderFileManger extends ActionRecordBuilder {
public ActionRecordBuilderFileManger(ActionMetaData actionMetaData) {
super(actionMetaData);
setTempInfo();
}
public String buildActionRecord() throws JsonProcessingException {
if (actionMetaData.getOperation() == OperationConsts.UPLOAD_FILE) {
String str = JSONObject.toJSONString(actionMetaData.getBeforeData());
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> map = objectMapper.readValue(str, Map.class);
String filename = map.getOrDefault(actionMetaData.getParameter(), "未知");
return String.format(actionMetaData.getOperation().getOperationName(), filename);
} else {
return String.format(actionMetaData.getOperation().getOperationName(), actionMetaData.getTempName());
}
}
}
在Controller使用注解添加审计日志
@Controller
@RequestMapping("dataManagement/userManual/addUserManual")
public class AddUserManualController {
@Autowired
private UserManualService userManualService;
/**
* 表单提交时脚本(atyFormAimcp)
* @return
*/
@RequestMapping("/atyFormAimcp/onSubmitUrl")
@ResponseBody
@ActionRecord(system = SystemConsts.GLFX, operationType = OperationTypeConsts.UPLOAD,
operation = OperationConsts.UPLOAD_FILE, parameter = "filename")
public Object atyFormAimcpOnSubmitUrl(UserManual userManual) {
userManualService.storeFile(userManual);
return "ok";
}
}