很久沒寫博客了,大半年時間花費在了許多瑣事上。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,達到修復未登陸用戶的帳號。