zoukankan      html  css  js  c++  java
  • app嵌入的H5页面的数据埋点总结

    好久没写博客了,大半年时间花费在了许多杂事上。

    最近1个月专门为H5页面的app开发了一些埋点功能,主要是考虑到以后的可复制性和通用型,由于不是前端开发出身,相对来说还是比较简陋的。

    正题开始:H5页面的埋点主要涉及到的元素有a标签,button按钮,以及form表单的提交。

    目前实现的功能基本还都是代码埋点的方式,但是相对来说比较简洁了,我这里主要是针对a标签和button的事件埋点。

    第一个js脚本 bigdataIndex.js

    var _qjmap = _qjmap || [];
    var _bigdataDomain = "http://localhost:8082/"
    _qjmap.push(['tenantCode', 'xxxxx000001']);
    
    (function () {
    
        //监控a标签的单击事件
        var a = document.getElementsByTagName("a");
        for(var i =0; i<a.length; i++){
            a[i].onclick = (function(i){
                return function(){
                    var data = this.getAttribute('data-bigdata');
                    var href = this.getAttribute('href');
    
                    if(data){
    
                        var prevEvent = sessionStorage.getItem("event") || '';
                        sessionStorage.setItem("prevEvent",prevEvent);
    
                        if(data.indexOf(':') != -1){
                            var event = data.split(':')[0] || '';
                            var eventData = data.split(':')[1]|| '';
                            sessionStorage.setItem("event", event);
                            sessionStorage.setItem("eventData", eventData);
                        }else {
                            sessionStorage.setItem("event", data);
                        }
                    }
                    if(href){
                        sessionStorage.setItem('href', href);
                    }
                    send();
                }
            })(i);
        }
    
        function send(){
            var ma = document.createElement('script');
            ma.type = 'text/javascript';
            ma.async = true;
            ma.src = _bigdataDomain + "js/qjdata.js?v=1";
            var s = document.getElementsByTagName('script')[0];
            s.parentNode.insertBefore(ma, s);
        }
    
    
        //在按钮事件中调用该方法
        function btnEventSend(event,data){
            sessionStorage.setItem("prevEvent",sessionStorage.getItem("prevEvent"));
            sessionStorage.setItem("event", event);
            sessionStorage.setItem("eventData", data);
            send();
        }
    
    })();
    
    
    //为button时候,手工触发
    function bigdataBtnEventSend(event, data){
        sessionStorage.setItem("prevEvent",sessionStorage.getItem("prevEvent"));
        sessionStorage.setItem("event", event);
        sessionStorage.setItem("eventData", data);
        var ma = document.createElement('script');
        ma.type = 'text/javascript';
        ma.async = true;
        ma.src = _bigdataDomain + "js/qjdata.js?v=1";
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ma, s);
    }

    说明:

    _qjmap为全局变量:添加的tenantCode为租户的id,如果统计多个应用,可以通过这个字段来区分。
    _bigdataDomain为第一个js脚本下载第二个js脚本的域名地址。

    接下来在一个闭包函数中监听了a标签的单击事件, 如果触发,则获取a标签data-bigdata属性、href属性,
    并且从sessionStorage中获取对应的事件,保存到sessionStorage中作为上个事件,以便下个事件获取。

    如果该事件带有具体的数据,则必须使用冒号放到事件类型后面,方便后面的分拆保存。
    例如:用户点击商品列表中的某个商品,跳转到商品详情页面中。
    则设置a标签的属性为 <a href="item/123456.htm" data-bigdata="viewGoods:123456">苹果</a>
    经过如上设置,在该页面中嵌入上面的bigdataIndex.js,则event为viewGoods, eventData为123456(这里123456假设为商品的id)。

    接下来最重要的就是send()方法:
        function send(){
            var ma = document.createElement('script');
            ma.type = 'text/javascript';
            ma.async = true;
            ma.src = _bigdataDomain + "js/qjdata.js?v=1";
            var s = document.getElementsByTagName('script')[0];
            s.parentNode.insertBefore(ma, s);
        }

    该方法中动态创建一个脚本,并且从远程服务器上下载qjdata.js文件加载到页面中,此处使用的是异步加载的方式。

    qjdata.js

    (function(){
    
        function getOsInfo() { // 获取当前操作系统
            var os;
            if (navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1) {
                os = 'Android';
            } else if (navigator.userAgent.indexOf('iPhone') > -1) {
                os = 'IOS';
            } else if (navigator.userAgent.indexOf('Windows Phone') > -1) {
                os = 'WP';
            } else {
                os = 'none';  //未知
            }
            return os;
        }
    
        function getOSVersion() { // 获取操作系统版本
            var OSVision = '1.0';
            var u = navigator.userAgent;
            var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //Android
            var isIOS = !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
            if (isAndroid) {
                OSVision = navigator.userAgent.split(';')[1].match(/d+.d+/g)[0];
            }
            if (isIOS) {
                OSVision = navigator.userAgent.split(';')[1].match(/(d+)_(d+)_?(d+)?/)[0];
            }
            return OSVision;
        }
    
        function getDeviceType() { // 获取设备类型
            var deviceType;
            var sUserAgent = navigator.userAgent.toLowerCase();
            var bIsIpad = sUserAgent.match(/(ipad)/i) == "ipad";
            var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
            var bIsMidp = sUserAgent.match(/midp/i) == "midp";
            var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
            var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
            var bIsAndroid = sUserAgent.match(/android/i) == "android";
            var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
            var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";
    
            if (!(bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM)) {
                deviceType = 'PC'; //pc
            } else if (bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
                deviceType = 'phone'; //phone
            } else if (bIsIpad) {
                deviceType = 'ipad'; //ipad
            } else {
                deviceType = 'none';  //未知
            }
            return deviceType;
        }
    
        function getOrientationStatus() { // 获取横竖屏状态
            var orientationStatus;
            if (window.screen.orientation.angle == 180 || window.screen.orientation.angle == 0) { // 竖屏
                orientationStatus = '竖屏';
            }
            if (window.screen.orientation.angle == 90 || window.screen.orientation.angle == -90) { // 横屏
                orientationStatus = '横屏';
            }
            return orientationStatus;
        }
    
        function getNetWork() { // 获取网络状态
            var netWork;
            switch (navigator.connection.effectiveType) {
                case 'wifi':
                    netWork = 'wifi'; // wifi
                    break;
                case '5g':
                    netWork = '5G'; // 5g
                    break;
                case '4g':
                    netWork = '4G'; // 4g
                    break;
                case '2g':
                    netWork = '2G'; // 2g
                    break;
                case  '3g':
                    netWork = '3G'; // 3g
                    break;
                case  'ethernet':
                    netWork = 'ethernet'; // 有线
                    break;
                case  'default':
                    netWork = 'none'; // 未知
                    break;
            }
            return netWork;
        }
    
        //生成唯一Id
        function generateUUID() {
            var d = new Date().getTime();
            if (window.performance && typeof window.performance.now === "function") {
                d += performance.now(); //use high-precision timer if available
            }
            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = (d + Math.random() * 16) % 16 | 0;
                d = Math.floor(d / 16);
                return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
            });
            return uuid;
        }
    
    
        //判断用户是否存在,不存在则生成唯一号
        function getUUID() {
            var uuid = localStorage.getItem("bigdata_uuid");
            if(uuid == '' || uuid == null){
                uuid = generateUUID();
                localStorage.setItem("bigdata_uuid", uuid);
            }
            return uuid;
        }
    
        //获取cookie
        function getCookie(sName)
        {
            var aCookie = document.cookie.split("; ");
            var returnValue = "";
            for (var i=0; i < aCookie.length; i++)
            {
                var key = sName + '=';
                if(aCookie[i].indexOf(key) != -1){
                    returnValue = unescape(aCookie[i].substr(sName.length + 1));
                }
            }
            return returnValue;
        }
    
        function setCookie(name,value)
        {
            var Days = 30;
            var exp = new Date();
            exp.setTime(exp.getTime() + Days*24*60*60*1000);
            document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
        }
    
        function clearCookie(name){
            setCookie(name,'');
        }
    
        //页面离开时触发
        // window.onbeforeunload = function(){
        //     params.intime = localStorage.getItem("bigdata_intime");
        //     params.duration = getDuration();
        //     params.outtime =  Date.now();
        // }
    
        //记录的参数值
        var params = {};
        params.osInfo = getOsInfo();
        params.osVersion = getOSVersion();
        params.deviceType = getDeviceType();
        params.webType = getNetWork();
        params.orientationStatus = getOrientationStatus();
        params.deviceId = getUUID();
        params.actionTime = Date.now();
        var intime = sessionStorage.getItem("bigdata_intime");
        params.previousUrl_intime = intime || '' ;
        params.duration = intime == null ? 0 : Date.now() - intime;
        sessionStorage.setItem("bigdata_intime", Date.now().toString());
    
    
        params.event = sessionStorage.getItem("event");
        params.preEvent = sessionStorage.getItem("prevEvent");
        params.eventData = sessionStorage.getItem("eventData");
    
        sessionStorage.removeItem("eventData");
        sessionStorage.removeItem('href');
        params.loginName = getCookie("_mall_newMobile_username");
        params.userCode = getCookie("userId");
        params.targetUrl = sessionStorage.getItem('href');
        // params.phoneType = getPhoneTypeAndVersion().split("#")[0];
        // params.phoneVersion = getPhoneTypeAndVersion().split("#")[1];
    
    
        //document对象元素
        if(document){
            params.currUrl = document.URL || '';             //当前URL地址
            params.prevUrl = document.referrer || '';     //上一路径
    
            params.loginIp = document.domain || '';         //获取域名
            params.title = document.title || '';        //标题
        }
    
        //window对象元素
        if(window && window.screen){
            params.height = window.screen.height || 0;  //获取显示屏信息
            params.width = window.screen.width || 0;
            params.colorDepth = window.screen.colorDepth || 0;
        }
    
        //navigator对象数据
        if(navigator){
            params.lang = navigator.language || '';  //获取语言的种类
            if(navigator.geolocation) {
                params.longitude = sessionStorage.getItem('longitude');
                params.latitude = sessionStorage.getItem('latitude');
            }
        }
    
        //解析_qjmap配置
        if(_qjmap){
            for(var i in _qjmap){
    
                console.log(_qjmap[i]);
    
                switch (_qjmap[i][0]){
                    case 'tenantCode':
                        params.tenantCode = _qjmap[i][1];
                        break;
                    default:
                        break;
                }
            }
        }
        
        
    
        //拼接字符串
        var args = '';
        for(var i in params){
            if(args != ''){
                args += 'x01';
            }
            var p = params[i];
            if(p != null ){
                p = p.toString().replace(new RegExp("=",'g'),"%3D");
            }
            args += i + '=' + p;  //将所有获取到的信息进行拼接
        }
    
        //通过伪装成Image对象,请求后端脚本
        var img = new Image(1, 1);
        var src = 'http://localhost:8082/bigdata/qjdata.gif?args=' + encodeURIComponent(args);
        // alert("请求到的后端脚本为" + src);
        img.src = src;
    
    })();

     qjdata.js说明:

    在该js中主要是拼接数据,并通过构造虚拟的image的方式,发送到后台。

    params对象包含了很多用户访问的设备、网络、以及自定义的信息。这里不一一介绍了。

    部分数据需要提前存放到sessionStorage中来获取,比如经纬度等。

    最后将params对象转化为字符串,通过image的参数方式传递到后台。

    后台java代码实现:

    package com.king;
    
    
    import org.apache.commons.lang3.StringUtils;
    import org.jboss.logging.Logger;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.OutputStream;
    
    @Controller
    @RequestMapping("/bigdata")
    public class BigdataController {
    
        private static final Logger log = Logger.getLogger(BigdataController.class);
    
        @RequestMapping(value = "qjdata.gif")
        public void dataCollection(String args, HttpServletResponse response){
            if(StringUtils.isNotBlank(args)){
                String[] arr = args.split("01");
                for(String kv :arr){
                    String[] kvmap = kv.split("=");
                    if(kvmap.length > 1 && !kv.split("=")[1].equals("null")){
                        String key = kv.split("=")[0];
                        String value = kv.split("=")[1];
    
                        //针对登录账号解密
                        if(key.equals("loginName")){
                            try {
                                value = value.replaceAll("%3D","=");
                                value = value.substring(1,value.length()-1);
                                value = ThreeDES.decryptThreeDESECB(value, ThreeDES.LoginDesKey);
                            }catch (Exception e){
                                log.error(e);
                            }
                        }
                        System.out.println(key + "==>" + value);
                    }else{
                        System.out.println(kv.split("=")[0] + "==>" + "");
                    }
                }
            }
            System.out.println("=========================================");
        }
    }

    说明: 由于用户的账号保存在sessionStorage中时候进行了加密,所以只能在这里进行解密操作。没有加密的可以不需要这段。

    最后输出的信息,即为用户的访问行为信息,下面为最终的输出信息供参考:

    用户登录:

    从登录页(login)到商品分类页面(pageClass):

    从商品分类页(pageClass)到首页(pageHome)

    浏览商品详情页(viewGoods),这里的201807271813151即为商品的id号。

     ⚠️最后注意点:

        关于用户的唯一id问题,由于设备的Id号现在很多被屏蔽了,不好获取。

        在用户未登录的时候,通过js自动生成了一串UUID,然后保存到localStorage中,这个除非手工清除了缓存,否则会一直保存在本地。

        当用户登录后,可以查看到用户登录的账号,所以在后续数据清洗时,可以根据有账号的uuid去匹配无账号的uuid,达到修复未登录用户的账号。



  • 相关阅读:
    基于Visual C++2013拆解世界五百强面试题--题13-找最大公共子字符串
    基于Visual C++2013拆解世界五百强面试题--题12-进制转换
    Color颜色对照表
    根据选择打开相应的程序
    复制一个5G文件只需要两秒,全网最牛方法!
    判断一个男人穷还是富,只看这几点!
    判断一个男人穷还是富,只看这几点!
    判断一个男人穷还是富,只看这几点!
    16年国庆假期期间兼职所悟
    16年国庆假期期间兼职所悟
  • 原文地址:https://www.cnblogs.com/30go/p/10445516.html
Copyright © 2011-2022 走看看