审计日志,即用户操作日志,用于将用户的全部或关键操作持久化的记录下来,以备数据出现问题或者系统重要数据发生泄露,反向查找或追责。

创建审计日志实体类

@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";
    }
}