app嵌入的H5頁面的數據埋點總結

很久沒寫博客了,大半年時間花費在了許多瑣事上。javascript

最近1個月專門爲H5頁面的app開發了一些埋點功能,主要是考慮到之後的可複製性和通用型,因爲不是前端開發出身,相對來講仍是比較簡陋的。前端

正題開始:H5頁面的埋點主要涉及到的元素有a標籤,button按鈕,以及form表單的提交。java

目前實現的功能基本還都是代碼埋點的方式,可是相對來講比較簡潔了,我這裏主要是針對a標籤和button的事件埋點。android

第一個js腳本 bigdataIndex.jsios

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);
}

說明:git

_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文件加載到頁面中,此處使用的是異步加載的方式。web

qjdata.jsspring

(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說明:apache

在該js中主要是拼接數據,並經過構造虛擬的image的方式,發送到後臺。windows

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("\001");
            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,達到修復未登陸用戶的帳號。

相關文章
相關標籤/搜索