likes
comments
collection
share

微信小程序《埋点系统设计》

作者站长头像
站长
· 阅读数 18

简介

现在的小程序埋点的方案有很多优秀的企业提供了平台,比如阿拉丁小程序统计、微信官方的 WE 分析,而这些有个共同点就是需要在相应的后台中查看,而且这些只能满足所有行业产品中一些基本的的数据埋点。当你的运营脑门开始增大时,你就会意识到这些平台已经满足不了,这时候就需要我们自己写一个埋点系统

一. 埋点系统

微信小程序《埋点系统设计》

埋点系统是业务闭环一个关键点,任何产品都是需要运营的,而运营需要根据这个产品的各种使用情况来分析产品的拓展或优化,所以埋点系统都会包含以下几点:

  • 用户在某个页面的访问、点击、分享次数
  • 用户在某个功能的点击、使用次数
  • 用户的行为轨迹

二. 微信小程序埋点设计

站在用户的角度上可以分析出微信小程序收集的方式有两种:

  • 生命周期:用户打开小程序或进入页面时通过生命周期函数自动收集
  • 自定义事件:用户触发某次点击时通过自定义事件去收集

2.1 前端埋点 SDK 设计思路

微信小程序《埋点系统设计》

  • 入口模块:负责与小程序进行通讯、负责把定义好的自定义事件导出给小程序使用、负责把数据传递给数据收集模块
  • 自定义事件模块:负责定义好业务拓展需要的自定义事件模块
  • 数据收集模块:负责定义好服务端需要的数据接口并上报给服务端

三. 入口模块(index.js)

入口模块首先要拦截 AppPage 的生命周期函数,然后插入自己的埋点代码,这里需要注意的是 pageonShareAppMessage 事件,它通常会有返回值

import { gatherOrDispatchStat } from './stat'
import { statClick } from './event'

const app = App // 小程序全局实例
const page = Page // 小程序页面实例

/**
 * 拦截 Page | App 实例的方法
 * @param {Object} instanceData Page | App 实例初始化传入的 option
 * @param {String} name option 的 key
 * @param {Function} callback 回调函数
 */ 
const _handleInstanceMethod = function(instanceData, name, callback) {
  if (instanceData[name]) {
    const e = instanceData[name]
    instanceData[name] = function(arg) {
      callback.call(this, arg)
      e.call(this, arg)
    }
  } else {
    instanceData[name] = function(arg) {
      callback.call(this, arg)
    }
  }
}

// 拦截 App
App = function (appData) {
  _handleInstanceMethod(appData, 'onLaunch', function(e) {
    gatherOrDispatchStat({
      pagePath: e.path,
      eventName: '小程序启动'
    })
  })
  app(appData)
}

// 拦截 Page
Page = function (pageData) {
  const shareFunction = pageData.onShareAppMessage
  _handleInstanceMethod(pageData, 'onShow', function(e) {
    gatherOrDispatchStat({
      eventName: '进入页面'
    })
  })

  /**
   * 分享事件比较特殊,因为它有返回值
   */ 
  if (shareFunction) {
    pageData.onShareAppMessage = function(e) {
      let shareParam = shareFunction.call(this, e)
      gatherOrDispatchStat({
        eventName: '页面分享'
      })
      return shareParam || {}
    }
  }

  page(pageData)
}

// 暴露一些自定义事件
export default {
  statClick
}

四. 数据模块(stat.js)

数据模块负责收集和派发数据,一般用户操作速度较快,如果每收集一个数据都向服务端上报的话会产生性能问题,所以我们可以做一个节流处理,或者是合并日志请求

// 生成 uuid
const _generatorUuid = () => {
  let uuid = wx.getStorageSync('stat_uuid')
  if (!uuid) {
    uuid = `user_${Date.now()}`
    wx.setStorageSync('stat_uuid', uuid)
  }
  return uuid
}

// 埋点数据结构定义
const statData = {
  time: '', // 日志收集时间
  uuid: _generatorUuid(), // 当前设备id
  pagePath: '', // 小程序路径
  eventName: '', // 事件名称
}

const duration = 5000 // 间隔
let timer = null // 延时器
let time = 0 // 最后一次上报时间
let statList = [] // 日志收集缓存

/**
 * 收集并派发函数
 * @param {Object} param 收集数据
 */ 
export const gatherOrDispatchStat = (param) => {
  const pages = getCurrentPages()
  const data = { ...statData, ...param }
  const now = Date.now()
  data.time = now.toString()
  data.pagePath = pages[pages.length - 1]?.route || param.pagePath || ''

  // 避免多次请求频繁发送请求,做个节流处理
  if (now - time > duration) {
    const patchData = [data, ...statList]
    statList = []
    time = now
    clearTimeout(timer)
    timer = setTimeout(() => {
      /**
       * ...这里省略派发日志操作
       */
      console.log(patchData);
    }, duration)
  } else {
    statList.push(data)
  }
}

五. 自定义事件模块(event.js)

自定义事件的业务场景实在太多了,特别是点击派发的事件,比如订阅、购买商品、点赞等,自定义事件需要保留拓展的行为,设计一定要保持灵活性,毕竟运营需要统计的需求会不断增加

import { gatherOrDispatchStat } from './stat'

/**
 * 点击事件
 * @param {String} eventName 事件名称
 */ 
export const statClick = (eventName) => {
  gatherOrDispatchStat({
    eventName,
  })
}

总结

上面的代码只是我个人模拟的一些场景,只为了提供思路,这些代码可以复制到微信开发工具运行,使用的方式为以下:

// app.js
import statInstance from './utils/stat/index'

App({
  globalData: {
    statInstance,
  }
})
// pages/index/index.js
const app = getApp()
Page({
    onclick() {
        app.globalData.statInstance.statClick('点击事件')
    }
})