0

    埋点代码的基本思路

    2023.06.10 | admin | 137次围观

    数据模型的建立

    神策的基本模型包括事件(Event)和用户(User)两个;比如说要统计今天注册小程序的人数。区分注册事件还是别的事件,用到了事件模型。每个用户启动N次只能算一次,用到了用户模型。

    标识用户 distinct_id

    每个用户启动N次只能算一次,区分A用户和B用户,这些要根据用户的标识来做。常用的稳定的标识用户的几个东西可以用: 手机号,微信号(openid),后端系统注册后分配的id(ssoid)。用手机号和ssoid来标识,需要用户注册后才有,所以这两个不能去标识游客。所以只能用openid去标识游客。现在的需求是标识注册用户要用ssoid来标识,方便接入其他系统所以我们对游客用openid来标识,对注册用户用ssoid来标识。这会带来一个坑定。比如说一个未注册用户做了一些埋点动作后再去注册,这个过程会产生两个distinct_id在做数据分析的时候如果根据distinct_id来去重,结果是会比真实的数据要多的。所以要把有ssoid的用户和他的openid关联起来,在神策后台面板选择的是用户数来去重。

    埋点代码的基本思路

    埋点代码的基本思路和‘追踪某个用户的某个行为’这件事的描述是一样的。首先建立一个人物模型,然后追踪这个人物的行为。这个先后顺序在代码中的体现:

    import sensors from './sensors/config.js'
    // 在后台接口回调中通过openid标识建立一个游客用户模型
    login(isRegister, openid, ssoid) {
     // 非注册用户
     if (!isRegister) {
      sensors.setOpenid(openid)
      // 给这个模型完善描述信息
      setProfile({
        sex: '男',
        city: '深圳',
      })
      // 初始化神策埋点,开始给神策后台发送数据
      sensors.init()
     } else {
      sensors.setOpenid(openid)
      // 给这个模型完善描述信息
      setProfile({
        sex: '男',
        city: '深圳',
      })
      // 调用login会把ssoid和之前的openid关联起来
      sensors.login(ssoid)
      // 初始化神策埋点,开始给神策后台发送数据
      sensors.init()
     }
    }
    // 用户触发了注册动作时这里会执行
    register(ssoid) {
      // 调用login会把ssoid和之前的openid关联起来
      sensors.login(ssoid)
      // 自定义事件register,追踪用户的注册行为
      sensors.track('register', {
        title: '首页',
        share_mobile: 13590035000,
        time: '2020-03-13 16:00:00'
      })
    }
    

    前端埋点代码的设计案例

    接到一个需求,所有的页面都要采集页面的切换动作,比如当前页面停留的时间,下个页面的路径,标题。重点是所有页面

    痛点和难点

    写代码之前,先思考下蒙头写代码会带来哪些痛点。而认识并且规避掉这些痛点,就是接下来的要做的事情和思路

    而这些问题,神策源码其实也遇到过,所以可以参考下神策源码的思路,解决这些痛点。

    神策源码是怎么解决的

    看了半天神策源码,终于发现了关键所在:

    var oldApp = App;
    App = function(option) {
      mp_proxy(option, "onLaunch", 'appLaunch');
      mp_proxy(option, "onShow", 'appShow');
      mp_proxy(option, "onHide", 'appHide');
      oldApp.apply(this, arguments);
    };
    var oldPage = Page;
    Page = function(option) {
      mp_proxy(option, "onLoad", 'pageLoad');
      mp_proxy(option, "onShow", 'pageShow');
      if (typeof option.onShareAppMessage === 'function') {
        sa.autoTrackCustom.pageShare(option);
      }
      oldPage.apply(this, arguments);
    };
    

    源码是劫持了微信小程序提供的App, Page函数微信小程序组件传值,然后往里面添加埋点代码,而启动小程序,App函数必然会执行,加载某个页面,Page函数必然会执行,这样预设的埋点代码就在App函数和Page函数执行的时候静默的执行了劫持了App,Page函数解决了N个页面要写N遍的问题。劫持了App,Page函数,可以实现‘一键埋点’,N个页面只要写一遍就可以了。

    虽然劫持了App,Page函数,但是还是有一些问题。

    集成和分离举个例子,比如说造一个机器,如果是集成的,机器的某一个地方坏了,可能会导致整个机器都不能用。而分离是把机器拆分成很多个零件模块,零件坏了可以单独修换。

    这时候我想到了vue中常常使用的mixins的技巧(有策略性的选择合并或者继承或者替代的混入)可以解决代码复用,抽离的问题。来看看mixins的一个例子:

    // 混入的属性
    const mixins = {
      // 页面的变量
      data: {
        apple: 0,
        banner: 1,
        cat: 3
      },
      // 页面显示会触发这个函数
      onShow() {
        console.log('mixins')
      },
      // 自定义方法1
      method1() {
        console.log('mixins method1')
      },
      // 自定义方法2
      method2() {
        console.log('mixins method2')
      }
    }
    // 原有的属性
    export default {
      mixins: [
        mixins
      ],
      // 页面的变量
      data: {
        apple: 100,
      },
      // 页面显示会触发这个函数
      onShow() {
        console.log('origin')
      },
      // 自定义方法1
      method1() {
        console.log('origin method1')
      },
    }
    

    最终会合并成:

    export default {
      // 页面的变量
      data: {
        apple: 100,
        banner: 1,
        cat: 3
      },
      // 页面显示会触发这个函数
      onShow() {
        console.log('mixins')
        console.log('origin')
      },
      // 自定义方法1
      method1() {
        console.log('origin method1')
      },
      // 自定义方法2
      method2() {
        console.log('mixins method2')
      }
    }
    

    入口处劫持Page函数,在需要埋点的时机比如onShow这个时机用mixins的方式静默注入埋点的代码。这样就可以不影响到业务代码的情况下,实现业务逻辑和埋点逻辑的分离,不同需求的埋点逻辑的分离,同时支持了集成和按需。 并且可以"一键埋点"。然后剩下的事件就变成劫持App,Page函数,写一个小程序版的mixins扩展,把不同的埋点需求拆分一个单独的模块,既可以一键集成,又可以按需使用。

    前后对比

    没改造之前:

    // N个这样的页面
    import { getData } from 'api/getData.js'
    Page({
      onShow() {
        // 业务逻辑
        getData().then((res) => {
          if (res.data && res.data.code === 1) {
            const data = res.data.data
            for (let i = 0; i < data.length; i++) {
              console.log(i)
            }
          }
        })
        // 埋点代码
        const title = '首页'
        const url = 'pages/index/index’
        sensors.track('login', {
          title,
          url
        })
      }
    })
    

    改造后的一键埋点:

    // 入口app.js,只要引入一次,所有的页面都不用单独写
    import './sensors/one-key.js'
    

    import { getData } from 'api/getData.js'
    // 零侵入业务逻辑
    Page({
      onShow() {
        // 业务逻辑
        getData().then((res) => {
          if (res.data && res.data.code === 1) {
            const data = res.data.data
            for (let i = 0; i < data.length; i++) {
              console.log(i)
            }
          }
        })
      }
    })
    

    改造后的按需埋点:

    import pageSwitchMixin from './sensors/mixins/page-switch.js'
    import pageClickMixin from './sensors/mixins/page-click.js'
    import { getData } from 'api/getData.js'
    // 同样零侵入业务逻辑
    Page({
      // 按需埋点的插槽
      mixins: [
        pageSwitchMixin,
        pageClickMixin,
      ],
      onShow() {
        // 业务逻辑
        getData().then((res) => {
          if (res.data && res.data.code === 1) {
            const data = res.data.data
            for (let i = 0; i < data.length; i++) {
              console.log(i)
            }
          }
        })
      }
    })
    

    Mixins需要注意的地方

    mixins虽然解决了代码复用的问题,但是也会带来变量命名冲突的问题,所以使用mixins的时候,如果是独立于业务逻辑的变量,应该有命名空间。

    总结

    劫持App,Page函数在小程序中非常有用微信小程序组件传值,可以集成扩展功能。而mixins是非常好用且通用的代码技巧,不仅仅是在埋点的逻辑,在业务逻辑上同样非常好用。放上mixins的地址 wx-miniapp-mixins

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论