某些状况下咱们须要对小程序某些用户的行为进行数据进行统计,好比统计某个页面的UV,
PV等,统计某个功能的使用状况等。好让产品对于产品的整个功能有所了解。
在网页里,咱们不少人都用过谷歌统计,小程序里也有一些第三方数据统计的库, 好比腾讯的MTA等等。
可是,第三方的数据统计库要么功能太简单,知足不了需求,要么就是要收费。(留下了贫穷的泪水。)
等等,又不是你出钱,怕啥? 贵一点就贵一点呀。javascript
嗯,说的没错。可是,公司团队内部想实现一套完整的本身的数据统计系统以知足本身的需求。因此,仍是没有用第三方的。java
因此,具体要统计些啥?git
针对产品经理的需求,咱们能够知道,Ta想要的是就是数据统计要实现的功能。对于开发来讲,咱们关注的更多就是错误统小程序性能这块的东西。github
好,到这里,咱们需求是明白了。就是要实现一套既能统计普通的埋点数据,也要能统计到小程序里一些特殊触发的事件,好比appLaunch, appHide 等,还要能够统计错误。小程序
好,那先来看看如何实现产品的需求吧缓存
用户进入小程序能够在 小程序 onLaunch 回调里拿到参数 的scene 值,这样就能够知道用户是怎么进入小程序的了。小case, 难不到我。微信
嗯,第一个需求实现了,那如何统计第二个呢?如何统计某个页面的停留时间呢?网络
这也难不倒我,用户在进入页面时会触发onShow 事件, 一样,在离开页面(或者切后台时)会触发onHide事件,我只须要在onShow里记录一下时间,同时在onHide 里也记录一下时间,把两个时间一减就能够了。数据结构
Page({ data: { beginTime: 0, endTime: 0 }, onShow: function() { // Do something when page show. this.setData({ beginTime: new Date().getTime() }) }, onHide: function() { // Do something when page hide. let stayTime = new Date().getTime() - this.beginTime; // 这个就是用户在这个页面的停留时间了 }, })
等等,这样确实实现了需求,万一产品要统计全部也面的停留时长? 那咱们岂不要在每个页面都这样写一遍?有没有更好的方法呢?app
好,接下来就是数据统计实现的要点了,即拦截微信原生事件,这样能够在某个特殊事件触发时,作一些咱们统计的事情。同时,还要拦截微信发生网络请求的方法,这样能够拿到网络请求相关的数据,最后,为了能统计到错误,还须要拦截微信发生错误的方法。
注册小程序。接受一个 Object
参数,其指定小程序的生命周期回调等。
App() 必须在 app.js 中调用,必须调用且只能调用一次。否则会出现没法预期的后果。
App({ onLaunch (options) { // Do something initial when launch. }, onShow (options) { // Do something when show. }, onHide () { // Do something when hide. }, onError (msg) { console.log(msg) }, globalData: 'I am global data' })
假如咱们要在小程序onLaunch 时打印一句hello Word,咱们有哪些方法实现?
方法1:
直接写在onLaunch方法里
onLaunch (options) { console.log('hello World') }
方法2:
使用 monkey patch方法 猴子补丁(monkey patch)
猴子补丁主要有如下几个用处:
- 在运行时替换方法、属性等
- 在不修改第三方代码的状况下增长原来不支持的功能
- 在运行时为内存中的对象增长patch而不是在磁盘的源代码中增长
举个栗子,假如咱们在console.log 方法里都先打印出当前的时间戳,咱们能够这样:
var oldLog = console.log console.log = function() { oldLog.call(this, new Date().getTime()) oldLog.apply(this, arguments) }
同理,咱们针对onLaunch 进行猴子补丁
var oldAp = App App = function(options) { var oldOnLaunch = options.onLaunch options['onLaunch'] = function(t) { // 作一些咱们本身想作的事情 console.log('hello word....') // 调用原来的onLaunch 方法 oldOnLaunch.call(this, t) } // 调用原来的App 方法 oldApp(options) // 想像一下,小程序内部调用onLaunch 方法应该是这样子的: options.onLaunch(params) } // 问题,有的时候,咱们可能没有注册某一个事件,好比页面的onShow, 全部,咱们在替换的时候还须要判断一下参数是否传了对应的方法 Page({ onLoad (options) {}, onHide (options) {} }) // 针对这种状况,咱们须要这样写 var oldPage = Page Page = function(options) { if (options['onShow']) { // 如过有注册onShow 这个回调 var oldOnShow = options.onShow // onShow 方法调用时都是 传了一个对象 options['onShow'] = function(t) { // doSomething() oldOnShow.call(this, t) } } // 调用原来的Page 方法。 oldPage.apply(null, [].slice.call(arguments)) // 注意: 下面这两种写都会报错: VM23356:1 Options is not object: {"0":{}} in pages/Badge.js 问题具体缘由暂时未找到。 // oldPage.call(null, arguments) // oldPage(arguments) }
经过上面的方法,咱们能够拦截了 App 方法注册的一些全局方法,好比 onLaunch , onShow, onHide, 和Page 注册的事件如 onShow, onHide, onLoad, onPullDownRefresh, 等页面注册事件。
思路: 拦截微信的请求事件。
let Request = { request: function (e) { let success = e[0].success, fail = e[0].fail, beginTime = smaUtils.getTime(), endTime = 0 // 拦截请求成功方法 e[0].success = function () { endTime = smaUtils.getTime() const performance = { type: constMap.performance, event: eventMap.wxRequest, url: e[0].url, status: arguments[0].statusCode, begin: beginTime, end: endTime, total: endTime - beginTime } smaUtils.logInfo('success performance:', performance) // 这里作上报的事情 // SMA.performanceReport(performance) success && success.apply(this, [].slice.call(arguments)) } // 拦截请求失败方法 e[0].fail = function () { endTime = smaUtils.getTime() const performance = { type: constMap.performance, event: eventMap.wxRequest, url: e[0].url, status: arguments[0].statusCode, begin: beginTime, end: endTime, total: endTime - beginTime } smaUtils.logInfo('fail performance:', performance) // 这里作上报的事情 // SMA.performanceReport(performance) fail && fail.apply(this, [].slice.call(arguments)) } }, } // 替换微信相关属性 let oldWx = wx, newWx = {} for (var p in wx) { if (Request[p]) { let p2 = p.toString() newWx[p2] = function () { Request[p2](arguments) // 调用原来的wx.request 方法 oldWx[p2].apply(oldWx, [].slice.call(arguments)) } } else { newWx[p] = oldWx[p] } } // eslint-disable-next-line wx = newWx
疑惑:为何要使用替换整个wx对象的方法呢? 不直接用咱们的request 方法 替换 wx.request 方法
var oldRequest = wx.request wx.request = function(e) { // doSomething(); console.log('请求拦截操做...') oldRequest.call(this, e); // 调用老的request方法 } // 结果报错了: // TypeError: Cannot set property request of [object Object] which has only a getter
3.1 拦截App里注册的 onError事件
var oldAp = App App = function(options) { var oldOnError = options.onErrr options['onErrr'] = function(t) { // 作一些咱们本身想作的事情 console.log('统计错误....', t) // 调用原来的onLaunch 方法 oldOnError.call(this, t) } // 调用原来的App 方法 oldApp(options) }
3.2 拦截 conole.error
console.error = function() { var e = [].slice.call(arguments) if (!e.length) { return true } const currRoute = smaUtils.getPagePath() // 统计错误事件 // SMA.errorReport({event: eventMap.onError, route: currRoute, errrMsg: arguments[0]}) smaUtils.logInfo('捕捉到error 事件,', e) oldError.apply(console, e) }
至此,咱们已经有能力在小程序发起请求时,发生错误时,生命周期或者特殊函数回调时,咱们都能在里面作一些咱们想要的数据统计功能了。
说了这么多你们估计也看累了。鉴于篇幅,具体的代码就不在这里贴了。
最终实现的数据统计模块大体实现了如下功能:
整个统计代码的配置文件以下:
const wxaConfig = { project: 'myMiniProgram', // 项目名称 trackUrl: 'https://youhost.com/batch', // 后台数据统计接口 errorUrl: 'https://youhost.com/batch', // 后台错误上报接口 performanceUrl: 'https://youhost.com/batch', // 后台性能上报接口 version: '0.1', prefix: '_wxa_', priority: ['track', 'performance', 'error'], // 发送请求的优先级,发送时,会依次发送 useStorage: true, // 是否开启storage缓存 debug: false, // 是否开启调试(显示log) autoTrack: true, // 自动上报 onShow, onHide, 分享等 内置事件 errorReport: false, // 是否开启错误上报 performanceReport: false, // 接口性能上报 maxReportNum: 20, // 当次上报最大条数 intervalTime: 15, // 定时上报的时间间隔,单位 s, 仅当开启了定时上报有效。 networkList: ['wifi', '4g', '3g'], // 容许上报的网络环境 opportunity: 'pageHide' // pageHide、appHide、realTime(实时上报)、timing(定时上报) 上报的时机,四选一 } export default wxaConfig
具体上报时,上报的数据结构大体长这样:
项目已传到GitHub -> GitHub传送门-wxa
若是这篇文章帮到你了,以为不错的话来点个Star吧
大家是如何实现小程序数据统计的呢? 欢迎在评论里留言交流~~