Spring AOP +自定义注解 + Spel表达式 实现审计日志

1,380 阅读2分钟

1-简介

  • 审计日记就是记录用户的操作记录
  • 基于AOP动态代理 实现自定义审计日志注解, 并支持Spel表达式解析

2-实现

2-1 定义日志存储实体类


@Data
@Builder
@ToString
public class AuditingLog {

    private String userId;  // 用户id

    private String userNickname; //用户昵称

    private String operationInfo; //操作信息

    private String interfaceName; // 调用的接口方法名

    private String applicationName; // 调用的服务名

    private LocalDateTime createTime; //操作时间
}

2-2 自定义审计日志注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AuditLog {
    String logInfo(); //日志信息
}

2-3 日志注解的AOP的切面

@Aspect
@Component
public class AuditLogAOP {

    @Value("${spring.application.name}")
    private String applicationName; //从配置文件获得服务名

    // spel表达式解析器
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    // 参数名发现器
    private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Before(value = "@annotation(enableAuditLog) || @within(enableAuditLog)")
    public void getAutiLogInfo(JoinPoint joinPoint, AuditLog enableAuditLog){

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        if (enableAuditLog == null) {
            enableAuditLog = signature.getMethod().getAnnotation(AuditLog.class);
        }

	// 构建日志存储对象
        AuditingLog auditlog = AuditingLog.builder().applicationName(applicationName).createTime(LocalDateTime.now()).build();

	auditlog.setUserId(xxx);  // 从上下文获取当前操作的用户信息
	auditlog.setUserNickname(xx);
        
	// 设置操作的接口方法名        
	auditlog.setInterfaceName(signature.getDeclaringTypeName()+"."+signature.getName());

	// 获得日志注解上自定义的日志信息
        String logInfo = enableAuditLog.logInfo();

	// Spel表达式解析日志信息
        // 获得方法参数名数组
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod());
        if (parameterNames != null && parameterNames.length > 0){
            EvaluationContext context = new StandardEvaluationContext();

            //获取方法参数值
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(parameterNames[i],args[i]); // 替换spel里的变量值为实际值, 比如 #user -->  user对象
            }

            // 解析出实际的日志信息
            String opeationInfo = spelExpressionParser.parseExpression(logInfo).getValue(context).toString();
            auditlog.setOperationInfo(opeationInfo);
        }

        // 打印日志信息
        log.info(auditlog.toString());

        //TODO 这时可以将日志信息auditlog进行异步存储,比如写入到文件通过logstash增量的同步到Elasticsearch或者DB

    }
}

2-4 开启审计日志功能

  • 在分布式项目中一般会将日志抽离出来公共调用, 所以为了方便的注入审计日志功能,可以编写对应 Enable注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({AuditLogAOP.class}) // 注入AOP切面到容器
public @interface EnableAuditLog {

}

3 使用

3-1 开启审计日志功能

  • 在要使用审计日志功能的服务的入口类开启审计日志功能, 这样便会自动注入 AuditLogAOP 切面

比如

@SpringBootApplication
@EnableDiscoveryClient
@EnableAuditLog //开启审计日志
public class UmsAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(UmsAdminApplication.class,args);
    }
}


3-2 在接口上使用

比如:

  • 这样在执行之前就会先动态获取接口参数user.username 然后输出日志
    @AuditLog(logInfo = "'新增管理员:'+ #user.username")
    @PostMapping
    public String addUser(@RequestBody User user){
    
        return null;
    }