关于java:对功能业务流程性的业务埋点需求-该如何有扩展性的设计

41次阅读

共计 8069 个字符,预计需要花费 21 分钟才能阅读完成。

最近接了一个埋点的需要, 与咱们平时接触的埋点不同, 我平时接触的埋点或是对用户的点击率或登录状况等用户侧进行埋点 , 而这一次接触的埋点是: 对某一个性能的业务流程进行埋点统计, 次要是提供 剖析在业务流程操作中, 个别出错率最高是在哪一个流程之中。

基于这一需要联合现有的成熟产品,咱们应该怎么设计能力达到:高效、高质量、易扩大三结合为一体的性能?
实现的形式有很多,但需与以后我的项目的构造相匹配才是最好的实现方。

我剖析方向问题是: 什么人在什么中央操作了什么?

针对这一问题剖析如下:
1. 业务性能的操作,操作的是接口,那么性能的实现是针对接口进行埋点(因我的项目已实现了登录权限, 则可获取以后操作人,个别的我的项目都是必须得有)

2. 同一个操作可能针对不同的业务端, 比方, 上传用户数据, 而在操作端这一块, 可能有分 B 端, 治理端或 C 端等, 那么需对操作端进行汇总记录。(因我的项目的接口有对业务端进行辨别模块,则我可晓得是哪一端)

3. 不同的操作可能操作不同的业务性能,那么需对这些操作进行业务性能汇总: 比方, 在不同的业务性能中, 都有导入 execl 数据, 那么能够把这些业务性能归类为: 上传数据(自定义)。

好,剖析完,上设计图:

次要应用的技术:自定义注解,切面,多线程异步解决
设计模式:策略模式,模板模式
代码如下:

/**
 * 事件埋点注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventTrackingAnnotation {


    /**
     * 事件埋点策略枚举
     * @return
     */
    EventTrackingEnum eventTrackingEnum();

    /**
     * 模块标识
     * @return
     */
    BuriedPointModule buriedPointModule();

    /**
     * 平台标识
     * @return
     */
    BuriedPointPlatform buriedPointPlatform();

    /**
     * 事件埋点节点
     */
    BuriedPointNode buriedPointNode();
/**
 * 事件埋点切面
 */
@Slf4j
@Component
@Aspect
@Order(1)
public class EventTrackingAspect {

    @Resource
    private ApplicationEventPublisher publisher;


    @Pointcut("@annotation(com.test.test.strategy.event.tracking.annotation.EventTrackingAnnotation)")
    public void eventTracking() {}

    /**
     * 盘绕
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("eventTracking()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取注解的入参参数
        EventTrackingAnnotation annotation = method.getAnnotation(EventTrackingAnnotation.class);
        // 事件埋点策略枚举
        EventTrackingEnum eventTrackingEnum = annotation.eventTrackingEnum();
        // 模块标识
        BuriedPointModule buriedPointModule = annotation.buriedPointModule();
        // 平台标识
        BuriedPointPlatform buriedPointPlatform = annotation.buriedPointPlatform();
        // 事件埋点节点
        BuriedPointNode buriedPointNode = annotation.buriedPointNode();

        // 获取业务参数
        Object[] args = joinPoint.getArgs();
        Map<String, Object> fieldsName = getFieldsName(method,args);
        boolean flag = true;
        if (fieldsName != null) {for (Map.Entry<String, Object> obj : fieldsName.entrySet()) {Object value = obj.getValue();
                // 这里须要过滤导入导出等参数接口 如果不过滤则打印 json 时会报错 踩坑后补上的逻辑
                // 测试只须要将接口中的参数退出 一下三个参数中的一个即可 (正文到上面的判断) 测试出后果
                // 当然这个目前我的项目中只有这三种不能打印, 其余的没有一一测试, 以理论为准
                if (value instanceof MultipartFile || value instanceof HttpServletRequest || value instanceof HttpServletResponse) {flag = false;}
            }
        }
        if (flag) {log.info("申请参数为:{}", JSON.toJSONString(fieldsName));
        }
        boolean isSuccess = true ;
        String message = null;
        try {
            // result 的值就是被拦挡办法的返回值
            Object result = joinPoint.proceed();
            log.info("申请完结,controller 的返回值是:{}", JSON.toJSONString(result));
            return result;
        } catch (Throwable throwable){
            isSuccess = false;
            message = throwable.getMessage();
            throw throwable;
        } finally {
            // 异步处理事件埋点记录
            EventTrackingEvent event = new EventTrackingEvent();
            event.setEventTrackingEnum(eventTrackingEnum);
            event.setBuriedPointModule(buriedPointModule);
            event.setBuriedPointPlatform(buriedPointPlatform);
            event.setBuriedPointNode(buriedPointNode);
            event.setState(isSuccess);
            event.setFailMsg(message);
            event.setFieldsName(fieldsName);
            publisher.publishEvent(event);

        }

    }


    /**
     * 依据切面获取申请参数列表
     */
    private static Map<String, Object> getFieldsName(Method method,Object[] args) {ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();

        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(32);
        if (parameterNames != null) {for (int i = 0; i < parameterNames.length; i++) {paramMap.put(parameterNames[i], args[i]);
            }
        }
        return paramMap;
    }
}
/**
 * 事件埋点异步处理器
 */
@Slf4j
@Component
public class EventTrackingListener {

    @Autowired
    private EventTrackingFactory eventTrackingFactory;


    @Async
    @TransactionalEventListener(fallbackExecution = true, phase = TransactionPhase.AFTER_COMMIT)
    public void touchTrackingFactory(EventTrackingEvent event) {log.info("事件埋点异步处理器,event={}",event);
        EventTrackingEnum eventTrackingEnum = event.getEventTrackingEnum();
        BuriedPointModule buriedPointModule = event.getBuriedPointModule();
        BuriedPointPlatform buriedPointPlatform = event.getBuriedPointPlatform();
        BuriedPointNode buriedPointNode = event.getBuriedPointNode();
        boolean state = event.isState();
        String failMsg = event.getFailMsg();
        Map<String, Object> fieldsName = event.getFieldsName();

        // 初始化具体的策略
        EventTrackingStrategy strategy = eventTrackingFactory.getStrategy(eventTrackingEnum);

        // 各自的实现策略依据接口入参, 各自封装本人须要的业务参数
        Map<String, Object> stringObjectMap = strategy.initParam(fieldsName);

        // 触发埋点
        TrackingDTO trackingDTO = new TrackingDTO();
        trackingDTO.setBuriedPointModule(buriedPointModule);
        trackingDTO.setBuriedPointPlatform(buriedPointPlatform);
        trackingDTO.setBuriedPointNode(buriedPointNode);
        trackingDTO.setState(state);
        trackingDTO.setFailMsg(failMsg);
        trackingDTO.setData(stringObjectMap);
        strategy.tracking(trackingDTO);
    }

}
/**
 * 事件埋点工厂类
 */
@Slf4j
@Component
public class EventTrackingFactory {Map<EventTrackingEnum,EventTrackingStrategy> handlerMap = new HashMap<>(4);


    @PostConstruct
    private void init(){Map<String, EventTrackingStrategy> beansMap = SpringHelper.getApplicationContext().getBeansOfType(EventTrackingStrategy.class);
        beansMap.forEach((k,v)->{this.handlerMap.put(v.getStrategy(),v);
        });

    }



    public EventTrackingStrategy getStrategy(EventTrackingEnum strategyEnum){return handlerMap.get(strategyEnum);
    }

}
/**
 * 事件埋点策略抽象类
 */
public interface EventTrackingStrategy {

    /**
     * 获取指定策略
     * @return
     */
    EventTrackingEnum getStrategy();

    /**
     * 封装参数
     * @return 用 Map 的起因是针对不同的接口, 有对应的业务参数, 可对业务参数解决完后返回本身须要的参数
     */
    Map<String,Object> initParam(Map<String,Object> paramMap);

    /**
     * 埋点记录
     * @return
     */
    void tracking(TrackingDTO trackingDTO);


}
/**
 * 事件埋点基类
 */
public class EventTrackingBase {

  


    /**
     * 埋点记录
     * @param signBuriedPointLog
     */
    public void sendSls(SignBuriedPointLog signBuriedPointLog) {// 依据需要对埋点数据进行入库操作}


    /**
     * Object 转成指定的类型
     * @param obj
     * @param type
     * @param <T>
     * @return
     */
    public static<T> T convert(Object obj, Class<T> type) {if (obj != null && StringUtils.isNotBlank(obj.toString())) {if (type.equals(Integer.class)||type.equals(int.class)) {return (T)Integer.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Long.class)||type.equals(long.class)) {return (T)Long.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Boolean.class)||type.equals(boolean.class)) {return (T)Boolean.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Short.class)||type.equals(short.class)) {return (T)Short.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Float.class)||type.equals(float.class)) {return (T)Float.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Double.class)||type.equals(double.class)) {return (T)Double.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Byte.class)||type.equals(byte.class)) {return (T)Byte.valueOf(StringUtils.trim(obj.toString()));
            } else if (type.equals(Character.class)||type.equals(char.class)) {return (T)Character.valueOf(obj.toString().charAt(0));
            } else if (type.equals(String.class)) {return (T) obj;
            } else if (type.equals(BigDecimal.class)) {return (T) new BigDecimal(StringUtils.trim(obj.toString()));
            } else if (type.equals(LocalDateTime.class)) {return (T) LocalDateTime.parse(obj.toString());
            } else if (type.equals(Date.class)) {
                try {SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                    return (T) formatter.parse(obj.toString());
                } catch (ParseException e) {throw new RuntimeException(e.getMessage());
                }
            }else{return null;}
        } else {return null;}
    }


}

以下为策略具体的实现类, 我就放一个了, 其余可自行实现

/**
 * 数据上传埋点策略实现类
 */
@Slf4j
@Component
public class DataUploadEventTrackingStrategy extends EventTrackingBase implements EventTrackingStrategy {


    @Override
    public EventTrackingEnum getStrategy() {return EventTrackingEnum.DATA_UPLOAD;}

    
    @Override
    public Map<String,Object> initParam(Map<String,Object> paramMap) {return null;}

    @Override
    public void tracking(TrackingDTO trackingDTO) {BuriedPointModule buriedPointModule = trackingDTO.getBuriedPointModule();
        BuriedPointPlatform buriedPointPlatform = trackingDTO.getBuriedPointPlatform();
        BuriedPointNode buriedPointNode = trackingDTO.getBuriedPointNode();
        boolean state = trackingDTO.isState();
        String failMsg = trackingDTO.getFailMsg();
        Map<String, Object> data = trackingDTO.getData();

        Date date = new Date();
        String traceId = TraceContext.traceId();

        // 触发埋点记录
        SignBuriedPointLog signBuriedPointLog = SignBuriedPointLog.builder()
                .bizTime(date)
                .traceId(traceId)
                .module(buriedPointModule.name())
                .platform(buriedPointPlatform.name())
                .node(buriedPointNode.name())
                .resultFlag(state)
                .failReason(failMsg)
                .build();
        sendSls(signBuriedPointLog);


    }
}

好, 总体思路是这样, 局部实体类对象就不提供了, 分享的是一种解决该相似需要的计划。

集体能力无限,各位大佬可一起探讨更多更好的实现

不喜勿喷哈

正文完
 0