2.AOP拦截收集设备绑定信息

116 阅读4分钟

1.业务背景

我们的运营平台项目中的设备用户统计模块,需要统计展示每天绑定设备的用户数。因此,我们需要搜集设备绑定用户记录并存储,作为基础数据,然后定时调度系统每天跑当天绑定设备的用户数并存储到每日汇总表中。

那么,首当其中的就是需要在设备绑定接口执行完毕之后,收集到绑定信息并存储。为了避免对原来绑定接口逻辑的侵入,我们使用AOP切面类对接口方法执行之后进行拦截搜集设备绑定信息,并且发送设备绑定信息到MQ异步消费存储设备绑定用户记录,作为基础数据。

同时,由于拦截接口,搜集响应数据和请求参数等操作在我们项目的其他统计模块,例如:新增用户数统计中,也需要在调用注册接口之后,收集注册用户信息,异步存储作为基础数据,然后定时调度汇总生成每日新增用户数。这一些列的逻辑似乎是都是一些置身于核心业务逻辑之外的通用公共的逻辑,我们可以基于AOP切面类把他们(即收集接口响应数据或者请求参数等数据,异步存储作为基础数据)封装在一处集中统一处理,而不是散落到业务的各个地方。因此,我们需要AOP的思想来处理这种情况。

2.具体实现

2.1定义注解

定义注解:标记需要搜集接口响应数据、请求数据的接口方法。

/**
 * @description: 注解:标记搜集接口响应数据、请求数据
 * @author:xg
 * @date: 2025/1/24
 * @Copyright:
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableCollect {

    String busiDesc() default ""; //业务场景

}
@RequestMapping(value = "/bind2",method = RequestMethod.POST)
@EnableCollect(busiDesc = "收集绑定信息")
public int bind2(@RequestBody  @Validated  SaveDeviceInfoVo saveDeviceInfoVo,
                 @RequestHeader("Authorization") String accessToken){
   log.info("设备绑定信息:{}", JSON.toJSONString( saveDeviceInfoVo));
        
   // 具体的绑定逻辑...
        
   // 绑定接口执行结果
   return 1;
}

2.2编写AOP切面类

数据发布抽象类中定义切点,用于匹配标注了@EnableCollect收集注解的方法。通用的公共方法定义到抽象父类中。



/**
 * @description: 数据发布抽象类:定义公共通用的方法
 * @author:xg
 * @date: 2025/1/25
 * @Copyright:
 */
public class AbstractCollectAspect {

     private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义切点:用于匹配标注了@EnableCollect收集注解的方法
     */
    @Pointcut(value = "@annotation(com.smart.product.device.app.aop.EnableCollect)")
    public void pointcut() {
    }

    /**
     * 发送消息到MQ
     * @param msg
     */
    protected void sendMQ(String msg) {
        // 发送消息到MQ
        logger.info("发送信息到MQ, msg:{}", msg);
    }
}

具体的子类(设备绑定收集类CollectBindInfoAspect),拦截绑定接口的响应和请求参数中的设备绑定信息,发送到MQ。

使用@AfterReturning通知注解,在接口方法执行完毕之后,进行一些额外的处理:判断bind接口方法调用的结果,并获取请求参数即设备绑定信息,然后发送到MQ



/**
 * @description: 搜集绑定信息,并发布到MQ
 * @author:xg
 * @date: 2025/1/24
 * @Copyright:
 */
@Aspect
@Component
@Slf4j
public class CollectBindInfoAspect extends AbstractCollectAspect{


    /**
     * 搜集设备绑定信息,并发送到MQ
     * @return
     * @throws Throwable
     */
    @AfterReturning(value = "pointcut()", returning = "retValue")
    public void afterReturningProcess(JoinPoint joinPoint , int retValue) {
        // 根据绑定接口返回的响应,判断是否绑定成功
        if(retValue != 1) {
            log.error("绑定接口执行失败");
            return;
        }

        // 从接口参数中获取设备绑定信息,并发送到MQ
        Object[] args = joinPoint.getArgs();
        SaveDeviceInfoVo bindInfo = (SaveDeviceInfoVo)args[0];
        log.info("发送设备绑定信息到MQ, bindInfo:{}", JSON.toJSONString(bindInfo));
        sendMQ(JSON.toJSONString(bindInfo));
    }

}

2.3debug调试下

最后,我们debug调试下,调用设备绑定接口

调用设备绑定接口

我们看到绑定接口执行完毕之后,已经被拦截处理:获取到设备绑定信息并发布到MQ

拦截到设备绑定信息并发布到MQ

3.总结

我们看到使用AOP拦截接口搜集请求数据和响应数据(本例中是搜集设备绑定信息):

  • 避免对业务接口侵入和耦合
  • 同时收集数据操作作为通用公共的逻辑,统一封装到AOP切面类中统一处理,避免了通用公共逻辑的重复编码以及到处散落到各个业务逻辑中,不利于维护和扩展