当前位置: 首页 > Web前端 > JavaScript

手把手教大家实现一套小程序全埋点SDK方案

时间:2023-03-26 21:00:51 JavaScript

本文相关概念本文重点介绍如何实现一个简单的微信小程序无痕埋点方案,所以概念埋点知识就不展开过多解读了,大家可以自行google。这里我们只需要了解两个关键问题。什么是追踪?跟踪是数据收集领域的一个术语。简单的说,就是用来跟踪用户的一些特定行为和事件,并进行记录和报告。常见的埋点主要分为人工埋点(代码埋点)、自动埋点(无痕埋点)、目视埋点、角色埋点三大类。我觉得站在用户和自己产品的角度会更清晰:对于用户:可以对收集到的数据进行分析,以便为用户提供精准的信息推送、个性化推荐等。当然这些用户行为信息也可以提升产品的操作体验。针对产品:通过分析用户在各页面的停留时间和交互节点等诸多信息,分析产品存在的问题,并提供后续优化思路。参考链接:https://xw.qq.com/partner/vivoscreen/20210520A08ATP00其实从概念上我们也可以知道,埋点时信息的追踪和信息的上报其实是完全独立的。所以我们在设计的时候也可以把这两部分独立的设计出来。ok,接下来就是肉搏时间了!实现过程的总体思路首先我们要知道一个微信小程序其实就是一个App,然后这个App是由多个Pages(页面)和Components(自定义组件)组成的。有意思的是,无论是App的注册,还是Page和Component的注册,都是通过他们对应的方法传入相应的参数(options)来完成的(这种形式也可以从上图看出)我们拿来拿以Page的注册为例,将图中的注册方法写成不同的方式来贴合我们的描述。constoptions={data:{msg:'HelloWorld',},//用户自定义事件bindViewTap(){wx.navigateTo({url:'../logs/logs',});},//页面自包含生命周期onLoad(){if(wx.getUserProfile){this.setData({canIUseGetUserProfile:true,});}},};//注册当前页面,并从微信提供的Page方法中传入相应参数完成Page(options);App和Component的注册与此类似,有兴趣的可以看看。所以一般!我们可以通过重写App、Page、Component这三个方法来监控它的options的一些方法和生命周期(嗯,大概就是这个意思,我踩了一下,你们都懂了)ok,明白了大概思路,先总结一下,然后开始做:设计一个tracker,重写App、Page、Component方法设计一个reporter,记录监听到的事件数据,发送到服务器Tracker开始敲代码在此之前,先确认一下如何使用我们实施的埋点。只有在确认了正确的使用方法之后,我们才能在这个开口的基础上,补充我们相应的功能。通常情况下,如果我们要修改微信小程序的native方法,需要在入口文件app.js处引入我们重写的方法来达到这个目的//app.jsimportinitfrom'./track/index';init({ak:'minapp-001',url:'http://baidu.com',autoTrack:{appLaunch:false,appHide:false,appShow:false,pageShow:false,pageHide:false,pageUnload:false,onShare:false,},//其他});config是暂定的,后面可以根据具体需要添加各种配置参数。这里我们为了简单只设置三个属性ak:保证这个二次追踪程序的唯一值url:从追踪点获取的信息需要上传的服务器地址autoTrack:全程追踪点的开启方案,并且相应的字段设置为true来启用自动抓取跟踪点信息确定了使用方法后,由此我们可以确定我们的代码应该如何展开。1.init//暂存原来的三个方法,后面会用到constcollector={oldApp=App,oldPage=Page,}constinit=(config)=>{//生成cidif(!storage.get('cid')){storage.set('cid',getUUID());}//初始化用户自定义配置,store是全局数据仓库(不用管)if(config!==undefined)store.set('config',config);//重写App&Page方法App=(options)=>collector.oldApp(proxyAppOptions(options));Page=(options)=>collector.oldPage(proxyPageOptions(options));};这里我们可以看到在init方法中,我们实现了最关键的一步,就是重写App和Page方法。这里需要注意的一点是,我们需要明确一点,我们重写方法的目的是获取传入的options参数的一些属性和方法。所以这里我们就把他的options改写一下,然后把改写后的结果再传给原生的App和Page方法去执行。我们上面定义的收集器就是把这些原始的方法保存起来,供我们在这里调用。(当然收集器中的属性不止这些,后面还会收集一些其他的信息)2.proxyAppOptions接下来我们看看proxyAppOptions方法做了什么/***重写App中的options参数*@param{*}options原options参数*@returns新options参数*/const_proxyAppOptions=(options)=>{//App中手动注入埋点的方法options.$ta={//方便用户通过getApp()方法直接调用track方法track:$ta.track.bind(reporter),//访问者的访问uid默认为0,用户登陆后需要手动更新其用户idlogin:(uid)=>this.login(uid),};//onLaunch事件监听器options.onLaunch=useAppLaunch(options.onLaunch);//onShow事件监听器options.onShow=useAppShow(options.onShow);//onHide事件监听器options.onHide=useAppHide(options.onHide);返回选项;};这段代码可以看到在options中重写了生命周期,然后加入了一些自己的处理逻辑,比如useAppLaunch、useAppShow等钩子函数。接下来我们看一下这些钩子的实现。其实这些hook很简单就是加点逻辑/**======================AppEventProxy=========================*/exportconstuseAppLaunch=(oldOnLunch)=>_proxyHooks(oldOnLunch,function(){constdata={event:'appLaunch',path,title,timemap,};$ta.track('devices',data);});exportconstuseAppShow=(oldOnShow)=>_proxyHooks(oldOnShow,function(){constdata={event:'appShow',};$ta.track复制代码('devices',data);});exportconstuseAppHide=(oldOnHide)=>{_proxyHooks(oldOnHide,function(){constdata={event:'appHide',};$ta.track('devices',data);});};/***代理原方法执行回调函数*@param{*}fn需要代理的方法*@param{*}cb需要执行的回调*/function_proxyHooks(fn=function(){},cb){returnfunction(){//如果回调存在if(cb){cb.apply(this,arguments);}//执行原函数fn.apply(this);};}其实这里没什么好说的,逻辑也比较清楚。可以看看proxyHooks这个方法,其实是执行了原来的方法,然后执行传入的回调,而我们在回调中其实是加了一些我需要埋点的一些数据信息。$ta这个方法是Reporter的一部分。这里我们只需要了解它是用来发送埋点数据的。下面我会详细说说它的实现。3.proxyPageOptions其实页面的部分和上面的app是一样的,都是处理生命周期的,但是页面除了生命周期还有很多自定义事件,所以这里是为了我们可以取一起看看这个。这里的自定义事件一般都是一些点击事件。//页面原始生命周期设置constPAGE_LIFE_METHOD=['onLoad','onShow','onReady','onHide','onUnload','onPullDownRefresh','onReachBottom','onShareAppMessage','onShareTimeline','onAddToFavorites','onPageScroll','onResize','onTabItemTap','onSaveExitState',];/***重写Page中的options参数*@param{*}options原options参数*@returns新options参数*/constproxyPageOptions=(options)=>{//...//自定义事件监听器for(letpropinoptions){//需要保证是一个函数,而不是原生生命周期函数if(typeofoptions[prop]=='function'&&!PAGE_LIFE_METHOD.includes(prop)){//在选项上覆盖自定义方法。选项[prop]=usePageClickEvent(选项[prop]);}}返回选项;};这部分的处理其实也比较简单,就是遍历options的属性,判断一些自定义的事件去处理。我们来看看pageClickEventhook是干什么的/***监听页面的点击事件*@param{*}oldEvent原生页面自定义事件*/exportconstpageClickEvent=(oldEvent)=>_proxyHooks(oldEvent,function(e){if(e&&e.type==='tap'){$ta.track('event',{event:'pageClick',//...});}});Reporter这部分其实就是封装一个请求方法,将跟踪到的信息发送给服务器但是这里我们有几点需要考虑:埋网请求不能抢占原事件的请求,即业务请求先发送。发送埋藏信息时,要保证顺序更有利于分析,比如小程序show的信息应该在hide之后发送。当网络出现波动时,如果埋点信息传输失败,我们应该缓存数据等待下一次传输,以保证信息的完整性。从'../store'导入商店;从'../../utils'导入{存储};从'../platform'导入平台;从'qs'导入qs;classReporter{constructor(){//要发送的跟踪信息队列this.queue=[];这个.timerId;}/***Track埋点数据*@param{*}data需要上报的数据*/track(type,data={}){//添加一些公共信息字段data.t=type;this.queue.push(qs.stringify(data));if(!this.timerId){//为了不影响正常的业务请求,这里延迟发送我们的埋点信息this.timerId=setTimeout(()=>{this._flush();},store.get('配置').延迟);}}/***执行队列中的任务(向后台发送跟踪信息)*/_flush(){constconfig=store.get('config');//当队列中有数据时请求if(this.queue.length>0){constdata=this.queue.shift();platform.request({//请求地址url:config.url,//timeouttimeout:config.request_timeout,方法:'POST',标头:{'content-type':'application/x-www-form-urlencoded'},数据:{ak:config.ak,cid:storage.get('cid'),ns:store.get('networkType'),uid:storage.get('uid')||0,data:Date.now(),data,},//TODO发送失败时保存此信息successinstorage:()=>{},fail:({errMsg})=>{console.error(errMsg);},complete:()=>{//执行完成后发送下一条消息this._flush();},});}else{this.timerId=null;}}}exportdefaultnewReporter();总结本文只是小程序埋点方案的简单实现,从整体框架入手进行描述,很多细节没有涉及。有什么问题可以一起讨论。本文来源于我的公众号。可以扫描二维码关注,平时也会发一些稀奇古怪的东西hh