埋点方案实现

// 埋点信息分析

1. 埋点标识

  • eventId:事件唯一标识,如:【立即购买】标识为'click/detail/buy'
  • eventType:click点击事件、event访问事件

2. 埋点上报数据有哪些?

js 复制代码
report(eventId, options)

interface options {
    [key:string]: any    // 自定义的事件信息
    eventType            // 埋点类型
    userId               // 用户ID(登录才有,未登录为空)
    userAgent            // 浏览器信息,内核信息,设备品牌/版本
    netType              // 网络信息,4G WIFI 5G
    deviceId             // 当前设备唯一ID
    timestamp            // 埋点时间,非上报时间
    loacation            // 页面地址
    refer                // 访问来源,如:百度/抖音/微信导流
}

3. 如何上报信息?

延时上报,在浏览器空闲时间上报信息,可以使用web worker / requestIdelCallback 等技术实现。

实现步骤:

ts 复制代码
// 埋点
interface Params {
  [key: string]: unknown
  // eventType: string           // 埋点类型
  // userId: string              // 用户ID(登录才有,未登录为空)
  // userAgent: string           // 浏览器信息,内核信息,设备品牌/版本
  // netType: string             // 网络信息,4G WIFI 5G
  // deviceId: string            // 当前设备唯一ID
  // timestamp: number           // 埋点时间,非上报时间
  // loacation: string           // 页面地址
  // refer: string               // 访问来源,如:百度/抖音/微信导流
}
let image: HTMLImageElement | null
interface Storage {
  eventId: string
  data: string
}

const queryString = (data: Params): string => {
  return data + 'queryString' // 生成唯一ID
}

const uuid = () => {
  return 'uuid' // 生成唯一ID
}

class BaseTrack {
  constructor() {
    if (window.addEventListener) {
      // 页面卸载前/进入页面时,执行任务池
      window.addEventListener('load', this.todoTrackPool, false)
      window.addEventListener('beforeunload', this.todoTrackPool, false)
    }
  }
  private ServerUrl = '//上报埋点的地址API'

  // 记录埋点信息
  track(eventId: string, params: Params | string, fromPool: boolean = false) {
    if (!fromPool && typeof params !== 'string') {  // 需要处理的埋点(直接调用)
      const qs = queryString({
        ...params,
        timestamp: Date.now(),
        url: location.href,
        userId: '', // 用户ID
        deviceId: TrackStorage.getDeviceId()
      })
      const uniqueEventId = `${uuid()}_${eventId}}` // 埋点ID 增加唯一性,因为任务池可能有多个一样的event id
      TrackStorage.pushStorage(uniqueEventId, qs)
      this.report(uniqueEventId, qs)
    } else if (typeof params === 'string') {  // 任务池内未完成/上报失败的任务(非直接调用)
      this.report(eventId, params)
    }
  }
  // 上报埋点信息
  report(uniqueEventId: string, qs: string, times: number = 3) {
    const spend = () => {
      image = null;
      if (times > 0) {
        this.report(uniqueEventId, qs, times - 1)
      }
    }
    return new Promise(() => {
      try {
        const eventId = uniqueEventId.split('_')[1]
        image = new Image()
        image.onload = () => { // 上报成功
          TrackStorage.removeStorage(uniqueEventId)
        }
        image.onerror = () => {  // 上报失败,再次上报
          spend()
        }
        image.src = this.ServerUrl + qs + `&eventId=${eventId}`
      } catch (error) {
        spend()
        console.error(error);
      }
    }).catch((error) => {
      console.error(error);
    })
  }
  todoTrackPool() {
    const Events: Storage[] = TrackStorage.getStorage() || []
    Events.forEach((e: Storage) => {
      this.track(e.eventId, e.data, false)
    })
  }
}

class TrackStorage {
  private static readonly TRACK_KEY = 'track'
  public static pushStorage(eventId: string, data: string) {
    const Events: Storage[] = this.getStorage()
    localStorage.setItem(this.TRACK_KEY, JSON.stringify(Events.concat({ eventId, data })))
  }
  public static removeStorage(eventId: string) {
    const Events: Storage[] = this.getStorage()
    localStorage.setItem(this.TRACK_KEY, JSON.stringify(Events.filter(e => e.eventId !== eventId)))
  }
  public static getStorage(): Storage[] {
    const storage = localStorage.getItem(this.TRACK_KEY)
    return storage ? JSON.parse(storage) : []
  }
  public static getDeviceId() { // 生成唯一的设备ID
    try {
      const key = 'device_id'
      let device_id = localStorage.getItem(key)
      if (!device_id) {
        device_id = uuid()
        localStorage.setItem(key, device_id)
      }
      return device_id
    } catch (error) {
      console.log(error);
      return ''
    }
  }
}

const track = new BaseTrack()
track.track('click/buy', { nimei: '' })
相关推荐
OpenTiny社区几秒前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠30 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞34 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js