【解决方案】数据埋点的一点思路与vue的SPA单页面实践

一、前言

数据埋点是监控用户在应用中的表现行为,对于TO C的产品迭代来说越来越重要。

数据埋点是产品需求分析的来源,检验功能是否达到预期。前端是更贴近用户,我来说说数据埋点在系统开发中的方案。

二、数据埋点方案分析

不同的产品对于数据的关注的角度不同,根据需求来采集和设计不同的方案。比如信息流的产品抖音,关注用户的停留时间更高。比如商品类的注重的是“复购率”,统计新老用户。

数据埋点统计通常分为:

(1)页面访问量统计

(2)功能点击量统计

我们今天只讨论页面访问量统计,在开发系统中自己定义页面访问量相关数据的统计。

通常我们接入的是第三方的服务,比如百度统计,就有相关页面访问统计,以及用户的画像等等。但是作为开发人员来说,如果在自己做的系统中,按照自己的需求定制化数据埋点是不是很cool。

1、页面访问量相关统计

页面访问量通常分为:PV和UV。

(1)PV:页面访问人次。

(2)UV:页面访问人数。

页面访问量,并非仅仅是内容吸引力决定的,影响因素有:入口,页面位置,到主页面深度等等。入口主要是UI视觉相关人员设计。入口位置可以通过数据分析后进行调整,到主页面深度可以分析用户的访问路径进行调整。

采集页面加载 from、to 以获知用户访问路径:

【解决方案】数据埋点的一点思路与vue的SPA单页面实践

分析可以知道用户普遍访问深度,每一层和每一个页面的流失率可以很直观看出来,从而调整核心页面入口源和深度。

还有一些特殊情况出现:比如PV稳定的页面访问量突然暴跌,可能是加载失败或者报错。

三、数据埋点方案实际操作

接下来我通过自己的系统接入数据埋点:https://chat.chengxinsong.cn

技术架构:vue+vuex+koa2+mysql+websocketIO+redis等。

1、方案一:全局定义Router.beforeEach方法

在main.js中全局定义

/*全局PV统计*/
router.beforeEach((to, from, next) => {
  let flag = localStorage.getItem("HappyChatUserInfo") !== null ? true: false;
  let data = {
    type: 'visit',
    user_id: flag ? JSON.parse(localStorage.getItem("HappyChatUserInfo")).user_id: '获取不到userId',
    time: (new Date()).getTime(),
    params: {
      from: {
        name: from.name || '',
        path: from.path || '',
        query: from.query || ''
      },
      to: {
        name: to.name || '',
        path: to.path || '',
        query: to.query || ''
      }
    }
  }
  App.methods.logEvent(data);
  next()
})

停留时间可以通过from和to页面的时间(跳转页time - 当前页time)。关闭应用的时候如何统计?可以考虑window.onunload方法

window.onunload = function unloadPage() {
  let data = {
    type: 'close',
    user_id: localStorage.getItem("HappyChatUserInfo") !== null ? JSON.parse(localStorage.getItem("HappyChatUserInfo")).user_id: '获取不到userId',
    time: (new Date()).getTime(),
    params: {
      from: {
        name: '关闭前',
        path: router.currentRoute.path || '',
        query: router.currentRoute.params || ''
      },
      to: {
        name: '关闭',
        path: '',
        query: ''
      }
    }
  }
  App.methods.logEvent(data);
}

这时候我们需要去定义接口传参,接口方法是logEvent。

我们自定义Vue插件App的method,用于埋点数据向服务器发送。

我们在App.vue中定义

具体方法

/* 数据埋点 */
      logEvent(opts) {
        let data = {
          type: opts.type,
          user_id: opts.user_id,
          time: opts.time,
          params: opts.params || {}
        }
        return Api.pvLog(data).then(res => {
          console.log(res)
        }).catch(function (error) {
          console.log(error);
        });
      }

其中Api是axios的接口统一封装的方法。

数据到了后端怎么保存,保存哪些参数,根据需求来定义,比如常见的:客户端IP,数据类型type,用户id,访问时间,from中的页面名字name,路径path,查询茶树query等等。

后端接口的控制层。(接口需不需要验证,根据需求来设计。)

let pvLog = async (ctx, next) => {
    const data = ctx.request.body;
    let req = ctx.req;
    let clientIP = req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
    userModel.logPV([clientIP, data.type, data.user_id, data.time,
        data.params.from.name || '', data.params.from.path || '', JSON.stringify(data.params.from.query) || '',
        data.params.to.name || '', data.params.to.path || '', JSON.stringify(data.params.to.query) || '']);
    ctx.body = {
        success: true
    }
};

接下来就是数据入库操作,代码就不放了,源码地址:

1、后端代码:https://github.com/saucxs/hap...

2、前端代码:https://github.com/saucxs/hap...

欢迎fork和start。

2、方案二:全局注册混入beforeRouteEnter和beforeRouteLeave

虽然官方说,慎用全局混入对象。

放一下示例代码

import Vue from 'vue'

Vue.mixin({
    beforeRouteEnter (to, from, next) {
        next(vm => {
            vm.$app.logEvent({
                type: 'visit',
                name: to.name,
                time: new Date().valueOf(),
                params: {
                    from: {
                        name: from.name,
                        path: from.path,
                        query: from.query
                    },
                    to: {
                        name: to.name,
                        path: to.path,
                        query: to.query
                    }
                }
            })
        })
    },
    beforeRouteLeave (to, from, next) {
        this.$app.logEvent({
            type: 'visit',
            name: to.name,
            time: new Date().valueOf(),
            params: {
                from: {
                    name: from.name,
                    path: from.path,
                    query: from.query
                },
                to: {
                    name: to.name,
                    path: to.path,
                    query: to.query
                }
            }
        })
        next()
    }
})

我们需要考虑是否在应用关闭的时候触发beforeRouteLeave方法?

还有两个问题:

(1)每一个页面都有钩子函数beforeRouteEnter,beforeRouteLeave方法,如何进行合并。

(2)有时候涉及到子路由问题,子路由的页面会调用2次beforeRouteEnter和beforeRouteLeave方法,PV会翻倍。

所以觉得方案二仅供参考,慎用。

其中,this.$app.logEvent(vm.$app.logEvent)等同于方案一的App.logEvent。

四、全局PV统计方案总结

根据实际需求和评估使用不同的方案:

(1)SPA应用,单入口,在入口文件全局定义Router.beforeEach就可以,就是方案1。

(2)MPA应用,多入口,可以封装公用的逻辑,免去重复构造entry成本。

(3)SPA+MPA混合应用:采用MPA方案就行。

(4)SSR应用:追求SEO采用服务端渲染(SSR),采用不同的模板渲染页面,直接统计调用模板的次数就可以知道PV相关数据。

至于 UV,统计 PV 时采集 userId 去重即可。若应用无用户管理体系,采集 IP、deviceId 也可粗略得知 UV(不精准)。

作者简介

昵称:saucxs | songEagle | 松宝写代码

github:https://github.com/saucxs

一、技术产品

二、开源作品:

相关推荐