设计模式中有常用的模式:代理模式。学术一些来讲,就是为某些对象的某种行为提供一个代理对象,并由代理对象完全控制该行为的实际执行。
数据埋点通俗来说就是基于业务需求或产品需求对用户在应用内产生行为的每一个事件对应的页面和位置植入相关代码,以便相关人员追踪用户行为和应用使用情况。
功能需求:在原有业务代码层面无侵入的加入埋点代码
直接上代码:
第一步 去实现InvocationHandler 接口,重写invoke方法,addPoint方法涉及俩个参数(一个是args是原来方法需要的参数,另一个是埋点接口需要的事件参数,具体要哪些参数跟着需求来)。
public class OperationDataPointProxy implements InvocationHandler {
private Object target ;
public OperationDataPointProxy(Object target) {
this.target = target;
}
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("埋点代理--------》"+args);
OperationDataPointService service = new OperationDataPointServiceImpl();
service.addPoint(args, JSON.toJSONString(args));
log.info(JSON.toJSONString(args));
Object result = method.invoke(target, args);
return result;
}
}
第二步 通过反射获取args里所有的属性和值,使用rocketmq异步发送,这样可以做到各个业务系统都可以直接使用。
这个过程出现一些问题,在上面的代码中,去new 了OperationDataPointServiceImpl就导致下面会出现注入对象为null的问题,原因不懂可以查,解决是通过实现ApplicationContextAware接口手动注入
@Override
public void addPoint(Object[] args,String eventParam) {
try {
OperationDataPointMessage dataPointMessage = new OperationDataPointMessage() ;
for (Object arg : args) {
Class<?> aClass = arg.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if ("applicationType".equals(field.getName())){
log.info(field.get(arg)+"");
dataPointMessage.setApplicationType((Integer) field.get(arg)); // 应用类型
}
if ("routUrl".equals(field.getName())){
log.info(field.get(arg)+"");
dataPointMessage.setRoutUrl((String) field.get(arg)); // url
}
if ("clickEvent".equals(field.getName())){
log.info(field.get(arg)+"");
dataPointMessage.setClickEvent((String) field.get(arg)); // 触发时机
}
}
}
dataPointMessage.setEventChName(OperationDataPointEventEnum.getItem(dataPointMessage.getEventType()).getDesc()); // 事件中文名称
dataPointMessage.setEventEnName(OperationDataPointEventEnum.getItem(dataPointMessage.getEventType()).getEnDesc()); // 事件英文文名称
dataPointMessage.setEventParam(eventParam); // 事件参数变量
dataPointMessage.setEventVersion("V1.0"); // 版本
UserInfoDTO userInfoDTO = AccountUserInfoThreadLocal.get();
dataPointMessage.setCreateName(userInfoDTO.getUserName()); // 创建者
dataPointMessage.setCreateId(String.valueOf(userInfoDTO.getUserId())); // 创建者id
dataPointMessage.setGmtCreated(LocalDateTime.now()); // 创建时间
rocketMQTemplate.asyncSend(OperationDataPointMessage.topic,dataPointMessage, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("MQ接收埋点消息是否成功:{}", sendResult.getSendStatus());
}
@Override
public void onException(Throwable e) {
log.info("MQ接收埋点消息异常:{}", e.getMessage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void mqAdd(OperationDataPointMessage operationDataPointMessage) {
OperationDataPointDO dataPointDO = new OperationDataPointDO() ;
BeanUtils.copyProperties(operationDataPointMessage,dataPointDO);
operationDataPointMapper.insert(dataPointDO) ;
}
手动注入使用是在之前需要注入的位置通过GetBeanUtil.getBean手动注入
@Component
public class GetBeanUtil implements ApplicationContextAware {
protected static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
if (applicationContext == null) {
applicationContext = arg0;
}
}
public static Object getBean(String name) {
//name表示其他要注入的注解name名
return applicationContext.getBean(name);
}
/**
* 拿到ApplicationContext对象实例后就可以手动获取Bean的注入实例对象
*/
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
第三步
@Component
@Slf4j
@RocketMQMessageListener(topic = OperationDataPointMessage.topic, consumerGroup = "operation_data_point")
public class OperationDataPointConsumer implements RocketMQListener<OperationDataPointMessage> {
@Autowired
private OperationDataPointService operationDataPointService;
@Override
public void onMessage(OperationDataPointMessage operationDataPointMessage) {
log.info("埋点mq接收消息为:{}", JSON.toJSONString(operationDataPointMessage));
operationDataPointService.mqAdd(operationDataPointMessage) ;
}
}
第四步 使用
在原来业务代码调用的地方通过埋点代理类代理该业务的实现
public ResultModel test(@RequestBody UserRequest userRequest) {
UserInfoDTO userInfoDTO = AccountUserInfoThreadLocal.get();
CustomerFinanceService proxy = new OperationDataPointProxy(new TestServiceImpl()).getProxy();
return ResultModel.success(proxy.test(userRequest,userInfoDTO));
}
才疏学浅,不对的地方大佬们多指点。。。