l1uqi / blog

4 stars 0 forks source link

前端业务埋点SDK实践 #12

Open l1uqi opened 1 year ago

l1uqi commented 1 year ago

title: 前端业务埋点SDK实践 date: 2022-11-07 categories:

参考资料:

腾讯二面:现在要你实现一个埋点监控 SDK,你会怎么设计? 为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?

最近项目需要采集用户的行为数据, 从而进行分析得到页面点击量、访问量、访问路径等重要数据, 为运营和业务人员提供精准数据,为产品优化和精细化运营提供数据支持。

111

埋点方式选择

这里采用代码侵入式埋点的方式进行、 SDK 提供 点击事件、曝光事件、页面时长进行上报

埋点方式实现

点击事件

如何让前端同事更轻松的捕获数据、这里实现了 dom 自定义事件实现自动上报, 在 vue 中也提供了指令 如有特殊业务场景也可以使用手动进行上报。

// 自动上报
// js

// vue
<button v-track:click="{
  'page_id': 1,
  'event_type': 12, // 事件类型
  'objs' // 业务数据
  }"
/>

<button v-track:keyup="{
  'page_id': 2,
  'event_type': 12, // 事件类型
  'objs' // 业务数据
  }"
/>

// 手动上报
trakerSDK.sendTracker({
  'page_id': 3,
  'event_type': 12,
  'object_ids': [1, 2, 3]
});

页面时长

页面时长统计, 我们可以用时间戳来算出用户停留的页面时长。 页面离开 - 页面进入 = 停留时长。

// 单页应用
router.beforeEach((to, from, next) => {
  // 获取当前时间
  const timestamp = new Date().getTime();
  // 上次时间
  const pretimestamp = getCache(LocalStoreEnum.PRE_TIMESTAMP);
  // 存入当前时间
  setCache(LocalStoreEnum.PRE_TIMESTAMP, timestamp);

  // 停留时间(s)
  let secound = (timestamp - pretimestamp) / 1000;
  next();
});

这样的话大多数场景都能够满足, 但是还有特殊场景无法满足。 如 PC 端 浏览器 Tab 选项卡切换、 APP 切换应用等。 这时候就需要另外的方案, Page Visibility API 能够解决这个问题

document.addEventListener("visibilitychange", () => {
  const state = document.visibilityState;
  let callbackData: any = null;
  if (state === "hidden") {
    // 页面不可见
  }
  if (state === "visible") {
    // 页面可见
  }

  // 如果业务需要 时间超过1小时 则算是新开页面 自行判断
});

曝光

... 未完待续

埋点上报

我们采集了埋点数据后, 就需要把采集的数据交给后端。 那么我们应该如何上报? 我们得考虑接口跨域、上报异常(正在进行上报时, 用户关闭了页面, 这样上报就会失败)、性能要求(不能应用应用使用)

基于以上要求, 提供了三种方式供用户自行选择

默认情况下优先级 sendBeacon > img > post

// 判断上传长度 2000 个字符
const urlIsLong = (url: string) => {
  let totalLength = 0,
    charCode = 0;
  for (var i = 0; i < url.length; i++) {
    charCode = url.charCodeAt(i);
    if (charCode < 0x007f) {
      totalLength++;
    } else if (0x0080 <= charCode && charCode <= 0x07ff) {
      totalLength += 2;
    } else if (0x0800 <= charCode && charCode <= 0xffff) {
      totalLength += 3;
    }
  }
  return totalLength < 2000 ? false : true;
};

if (navigator.sendBeacon) {
  sendBeacon(url, params);
} else if (method === "POST" || urlIsLong(str)) {
  xmlRequest(url, params);
} else {
  const img = new Image();
  img.src = `${url}?${str}`;
}

初始化

参数

参数 必填 默认值 类型
debug false bool 开启调试模式
config object {} 你的配置文件, 会在上报时传给后端
url '' string 请求地址
method img string 请求方式 GET、POST、SEND_BEACON
enableHeatMap false bool 开启坐标上传 position
enableVisibilitychange false bool 开启页面可见监听, 如开启此功能 registerVueRouterEvent 传参可能为 null

方法

方法名 说明 参数
setConfig 设置全局参数 Options
sendTracker 手动上报 {自定义}
initDirectives 初始化 vue2 指令 Vue
registerVueRouterEvent 初始化 VueRouter 监听 VueRouter, callback({to, from , secound,...}, callback)
registerErrorEvent 全局异常报错 vm: Vue 对象, errorCallback((errorMsg, pageInfo) => {}) 异常回调
import SimpleJsTracker from "simple-js-tracker";

const simpleJsTracker = new SimpleJsTracker({
  debug: true,
  url: "", // 服务地址
  enableHeatMap: true, // 开启热力图
  enableHashTracker: true,
  config: {
    ...
  }
});

// 更新传参
simpleJsTracker.setConfig(options);

// 自定义上传
simpleJsTracker.sendTracker(params);

// 初始化自定义vue2/3指令
simpleJsTracker.initDirectives(Vue);

// 初始化 VueRouter 监听
// 页面跳转监听, 上报的参数让用户自行提供 report
simpleJsTracker.registerVueRouterEvent(router, (res, report) => {
   const { to, from, secound } = res;
   // 页面进入
  if(to.meta.tracking) {
    const fromParams = {
      'event_type': 5,
      ...to.meta.tracking,
    }

    report(fromParams);
  }
  // 页面离开
  if(from.meta.tracking) {
    const fromParams = {
      'event_type': 6,
      ...from.meta.tracking,
    }
    report(fromParams);
  }
});

未完待续...

项目完整代码Github SDK使用 npm