環境
js
javaphp
1、埋點分析,是網站分析的一種經常使用的數據採集方法。數據埋點是一種良好的私有化部署數據採集方式。java
2、頁面數據收集事件的分析和設計
一、針對不一樣分析模塊,須要不一樣的數據,來設計頁面事件:
(1)用戶基本信息就是用戶的瀏覽行爲信息分析,只須要pageview事件就能夠了;
(2)瀏覽器信息分析以及地域信息分析其實就是在用戶基本信息分析的基礎上添加瀏覽器和地域這個維度信息,其中瀏覽器信息咱們能夠經過瀏覽器的window.navigator.userAgent來進行分析,地域信息能夠經過nginx服務器來收集用戶的ip地址來進行分析,也就是說pageview事件也能夠知足這兩個模塊的分析。
(3)外鏈數據分析以及用戶瀏覽深度分析咱們能夠在pageview事件中添加訪問頁面的當前url和前一個頁面的url來進行處理分析,也就是說pageview事件也能夠知足這兩個模塊的分析。
(4)訂單信息分析要求客戶端發送一個訂單產生的事件,那麼對應這個模塊的分析,咱們須要一個新的事件chargeRequest。
(5)對於事件分析咱們也須要一個客戶端發送一個新的事件數據,咱們能夠定義爲event。
(6)除此以外,咱們還須要設置一個launch事件來記錄新用戶的訪問。
客戶端的各類不一樣事件發送的數據url格式以下,其中url中後面的參數就是咱們收集到的數據:http://wjy.com/log.gif?requestdata
通過上面的分析咱們須要設計四類頁面事件以下:node
最終分析模塊nginx |
客戶端js sdk事件web |
用戶基本信息分析瀏覽器 |
pageview事件服務器 |
瀏覽器信息分析cookie |
|
地域信息分析session |
|
外鏈數據分析app |
|
用戶瀏覽深度分析 |
|
訂單信息分析(訂單成功、訂單失敗) |
chargeRequest事件 |
事件分析 |
event事件 |
|
launch事件 |
二、頁面處理流程以下:
數據參數:
參數名稱 |
類型 |
描述 |
en |
string |
事件名稱, eg: e_pv |
ver |
string |
版本號, eg: 0.0.1 |
pl |
string |
平臺, eg: website |
sdk |
string |
Sdk類型, eg: js |
b_rst |
string |
瀏覽器分辨率,eg: 1800*678 |
b_iev |
string |
瀏覽器信息useragent |
u_ud |
string |
用戶/訪客惟一標識符 |
l |
string |
客戶端語言 |
u_mid |
string |
會員id,和業務系統一致 |
u_sd |
string |
會話id |
c_time |
string |
客戶端時間 |
p_url |
string |
當前頁面的url |
p_ref |
string |
上一個頁面的url |
tt |
string |
當前頁面的標題 |
ca |
string |
Event事件的Category名稱 |
ac |
string |
Event事件的action名稱 |
kv_* |
string |
Event事件的自定義屬性 |
du |
string |
Event事件的持續時間 |
oid |
string |
訂單id |
on |
string |
訂單名稱 |
cua |
string |
支付金額 |
cut |
string |
支付貨幣類型 |
pt |
string |
支付方式 |
三、js sdk:
(function() { var CookieUtil = { // get the cookie of the key is name get : function(name) { var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null; if (cookieStart > -1) { var cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1) { cookieEnd = document.cookie.length; } cookieValue = decodeURIComponent(document.cookie.substring( cookieStart + cookieName.length, cookieEnd)); } return cookieValue; }, // set the name/value pair to browser cookie set : function(name, value, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value); if (expires) { // set the expires time var expiresTime = new Date(); expiresTime.setTime(expires); cookieText += ";expires=" + expiresTime.toGMTString(); } if (path) { cookieText += ";path=" + path; } if (domain) { cookieText += ";domain=" + domain; } if (secure) { cookieText += ";secure"; } document.cookie = cookieText; }, setExt : function(name, value) { this.set(name, value, new Date().getTime() + 315360000000, "/"); } }; // 主體,其實就是tracker js var tracker = { // config clientConfig : { serverUrl : "http://node2/log.gif", sessionTimeout : 360, // 360s -> 6min maxWaitTime : 3600, // 3600s -> 60min -> 1h ver : "1" }, cookieExpiresTime : 315360000000, // cookie過時時間,10年 columns : { // 發送到服務器的列名稱 eventName : "en", version : "ver", platform : "pl", sdk : "sdk", uuid : "u_ud", memberId : "u_mid", sessionId : "u_sd", clientTime : "c_time", language : "l", userAgent : "b_iev", resolution : "b_rst", currentUrl : "p_url", referrerUrl : "p_ref", title : "tt", orderId : "oid", orderName : "on", currencyAmount : "cua", currencyType : "cut", paymentType : "pt", category : "ca", action : "ac", kv : "kv_", duration : "du" }, keys : { pageView : "e_pv", chargeRequestEvent : "e_crt", launch : "e_l", eventDurationEvent : "e_e", sid : "bftrack_sid",//會話ID uuid : "bftrack_uuid",//用戶ID mid : "bftrack_mid",//會員ID preVisitTime : "bftrack_previsit", }, /** * 獲取會話id */ getSid : function() { return CookieUtil.get(this.keys.sid); }, /** * 保存會話id到cookie */ setSid : function(sid) { if (sid) { CookieUtil.setExt(this.keys.sid, sid); } }, /** * 獲取uuid,從cookie中 */ getUuid : function() { return CookieUtil.get(this.keys.uuid); }, /** * 保存uuid到cookie */ setUuid : function(uuid) { if (uuid) { CookieUtil.setExt(this.keys.uuid, uuid); } }, /** * 獲取memberID */ getMemberId : function() { return CookieUtil.get(this.keys.mid); }, /** * 設置mid */ setMemberId : function(mid) { if (mid) { CookieUtil.setExt(this.keys.mid, mid); } }, startSession : function() { // 加載js就觸發的方法 if (this.getSid()) { // 會話id存在,表示uuid也存在 if (this.isSessionTimeout()) { // 會話過時,產生新的會話 this.createNewSession(); } else { // 會話沒有過時,更新最近訪問時間 this.updatePreVisitTime(new Date().getTime()); } } else { // 會話id不存在,表示uuid也不存在 this.createNewSession(); } this.onPageView(); }, onLaunch : function() { // 觸發launch事件 var launch = {}; launch[this.columns.eventName] = this.keys.launch; // 設置事件名稱 this.setCommonColumns(launch); // 設置公用columns this.sendDataToServer(this.parseParam(launch)); // 最終發送編碼後的數據 }, onPageView : function() { // 觸發page view事件 if (this.preCallApi()) { var time = new Date().getTime(); var pageviewEvent = {}; pageviewEvent[this.columns.eventName] = this.keys.pageView; pageviewEvent[this.columns.currentUrl] = window.location.href; // 設置當前url pageviewEvent[this.columns.referrerUrl] = document.referrer; // 設置前一個頁面的url pageviewEvent[this.columns.title] = document.title; // 設置title this.setCommonColumns(pageviewEvent); // 設置公用columns this.sendDataToServer(this.parseParam(pageviewEvent)); // 最終發送編碼後的數據 this.updatePreVisitTime(time); } }, onChargeRequest : function(orderId, name, currencyAmount, currencyType, paymentType) { // 觸發訂單產生事件 if (this.preCallApi()) { if (!orderId || !currencyType || !paymentType) { this.log("訂單id、貨幣類型以及支付方式不能爲空"); return; } if (typeof (currencyAmount) == "number") { // 金額必須是數字 var time = new Date().getTime(); var chargeRequestEvent = {}; chargeRequestEvent[this.columns.eventName] = this.keys.chargeRequestEvent; chargeRequestEvent[this.columns.orderId] = orderId; chargeRequestEvent[this.columns.orderName] = name; chargeRequestEvent[this.columns.currencyAmount] = currencyAmount; chargeRequestEvent[this.columns.currencyType] = currencyType; chargeRequestEvent[this.columns.paymentType] = paymentType; this.setCommonColumns(chargeRequestEvent); // 設置公用columns this.sendDataToServer(this.parseParam(chargeRequestEvent)); // 最終發送編碼後的數據 this.updatePreVisitTime(time); } else { this.log("訂單金額必須是數字"); return; } } }, onEventDuration : function(category, action, map, duration) { // 觸發event事件 if (this.preCallApi()) { if (category && action) { var time = new Date().getTime(); var event = {}; event[this.columns.eventName] = this.keys.eventDurationEvent; event[this.columns.category] = category; event[this.columns.action] = action; if (map) { for ( var k in map) { if (k && map[k]) { event[this.columns.kv + k] = map[k]; } } } if (duration) { event[this.columns.duration] = duration; } this.setCommonColumns(event); // 設置公用columns this.sendDataToServer(this.parseParam(event)); // 最終發送編碼後的數據 this.updatePreVisitTime(time); } else { this.log("category和action不能爲空"); } } }, /** * 執行對外方法前必須執行的方法 */ preCallApi : function() { if (this.isSessionTimeout()) { // 若是爲true,表示須要新建 this.startSession(); } else { this.updatePreVisitTime(new Date().getTime()); } return true; }, sendDataToServer : function(data) { alert(data); // 發送數據data到服務器,其中data是一個字符串 var i2 = new Image(1, 1);// <img src="url"></img> i2.onerror = function() { // 這裏能夠進行重試操做 }; i2.src = this.clientConfig.serverUrl + "?" + data; }, /** * 往data中添加發送到日誌收集服務器的公用部分 */ setCommonColumns : function(data) { data[this.columns.version] = this.clientConfig.ver; data[this.columns.platform] = "website"; data[this.columns.sdk] = "js"; data[this.columns.uuid] = this.getUuid(); // 設置用戶id data[this.columns.memberId] = this.getMemberId(); // 設置會員id data[this.columns.sessionId] = this.getSid(); // 設置sid data[this.columns.clientTime] = new Date().getTime(); // 設置客戶端時間 data[this.columns.language] = window.navigator.language; // 設置瀏覽器語言 data[this.columns.userAgent] = window.navigator.userAgent; // 設置瀏覽器類型 data[this.columns.resolution] = screen.width + "*" + screen.height; // 設置瀏覽器分辨率 }, /** * 建立新的會員,並判斷是不是第一次訪問頁面,若是是,進行launch事件的發送。 */ createNewSession : function() { var time = new Date().getTime(); // 獲取當前操做時間 // 1. 進行會話更新操做 var sid = this.generateId(); // 產生一個session id this.setSid(sid); this.updatePreVisitTime(time); // 更新最近訪問時間 // 2. 進行uuid查看操做 if (!this.getUuid()) { // uuid不存在,先建立uuid,而後保存到cookie,最後觸發launch事件 var uuid = this.generateId(); // 產品uuid this.setUuid(uuid); this.onLaunch(); } }, /** * 參數編碼返回字符串 */ parseParam : function(data) { var params = ""; for ( var e in data) { if (e && data[e]) { params += encodeURIComponent(e) + "=" + encodeURIComponent(data[e]) + "&"; } } if (params) { return params.substring(0, params.length - 1); } else { return params; } }, /** * 產生uuid */ generateId : function() { var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; var tmpid = []; var r; tmpid[8] = tmpid[13] = tmpid[18] = tmpid[23] = '-'; tmpid[14] = '4'; for (i = 0; i < 36; i++) { if (!tmpid[i]) { r = 0 | Math.random() * 16; tmpid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; } } return tmpid.join(''); }, /** * 判斷這個會話是否過時,查看當前時間和最近訪問時間間隔時間是否小於this.clientConfig.sessionTimeout<br/> * 若是是小於,返回false;不然返回true。 */ isSessionTimeout : function() { var time = new Date().getTime(); var preTime = CookieUtil.get(this.keys.preVisitTime); if (preTime) { // 最近訪問時間存在,那麼進行區間判斷 return time - preTime > this.clientConfig.sessionTimeout * 1000; } return true; }, /** * 更新最近訪問時間 */ updatePreVisitTime : function(time) { CookieUtil.setExt(this.keys.preVisitTime, time); }, /** * 打印日誌 */ log : function(msg) { console.log(msg); }, }; // 對外暴露的方法名稱 這樣在頁面中或者其餘js域能夠直接調用 window.__AE__ = { startSession : function() { tracker.startSession(); }, onPageView : function() { tracker.onPageView(); }, onChargeRequest : function(orderId, name, currencyAmount, currencyType, paymentType) { tracker.onChargeRequest(orderId, name, currencyAmount, currencyType, paymentType); }, onEventDuration : function(category, action, map, duration) { tracker.onEventDuration(category, action, map, duration); }, setMemberId : function(mid) { tracker.setMemberId(mid); } }; // 自動加載方法 var autoLoad = function() { // 進行參數設置 var _aelog_ = _aelog_ || window._aelog_ || []; var memberId = null; for (i = 0; i < _aelog_.length; i++) { _aelog_[i][0] === "memberId" && (memberId = _aelog_[i][1]); } // 根據是給定memberid,設置memberid的值 memberId && __AE__.setMemberId(memberId); // 啓動session __AE__.startSession(); }; autoLoad(); })();
3、程序後臺事件分析
一、程序後臺事件設計
好比訂單成功和訂單退款事件只能在後臺程序中觸發,頁面是沒法觸發的:
http://wjy.com/log.gif?requestdata
最終分析模塊 |
PC端js sdk事件 |
訂單信息分析 |
chargeSuccess事件 chargeRefund事件 |
數據參數:
參數名稱 |
類型 |
描述 |
en |
string |
事件名稱, eg: e_cs |
ver |
string |
版本號, eg: 0.0.1 |
pl |
string |
平臺, eg: website,javaweb,php |
sdk |
string |
Sdk類型, eg: java |
u_mid |
string |
會員id,和業務系統一致 |
c_time |
string |
客戶端時間 |
oid |
string |
訂單id |
二、後臺程序處理流程:
三、程序示例
事件定義代碼:
package com.sxt.client; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * 分析引擎sdk java服務器端數據收集 * * @author root * @version 1.0 * */ public class AnalyticsEngineSDK { // 日誌打印對象 private static final Logger log = Logger.getGlobal(); // 請求url的主體部分 public static final String accessUrl = "http://node2/log.gif"; private static final String platformName = "java_server"; private static final String sdkName = "jdk"; private static final String version = "1"; /** * 觸發訂單支付成功事件,發送事件數據到服務器 * * @param orderId * 訂單支付id * @param memberId * 訂單支付會員id * @return 若是發送數據成功(加入到發送隊列中),那麼返回true;不然返回false(參數異常&添加到發送隊列失敗). */ public static boolean onChargeSuccess(String orderId, String memberId) { try { if (isEmpty(orderId) || isEmpty(memberId)) { // 訂單id或者memberid爲空 log.log(Level.WARNING, "訂單id和會員id不能爲空"); return false; } // 代碼執行到這兒,表示訂單id和會員id都不爲空。 Map<String, String> data = new HashMap<String, String>(); data.put("u_mid", memberId); data.put("oid", orderId); data.put("c_time", String.valueOf(System.currentTimeMillis())); data.put("ver", version); data.put("en", "e_cs"); data.put("pl", platformName); data.put("sdk", sdkName); // 建立url String url = buildUrl(data); // 發送url&將url加入到隊列 SendDataMonitor.addSendUrl(url); return true; } catch (Throwable e) { log.log(Level.WARNING, "發送數據異常", e); } return false; } /** * 觸發訂單退款事件,發送退款數據到服務器 * * @param orderId * 退款訂單id * @param memberId * 退款會員id * @return 若是發送數據成功,返回true。不然返回false。 */ public static boolean onChargeRefund(String orderId, String memberId) { try { if (isEmpty(orderId) || isEmpty(memberId)) { // 訂單id或者memberid爲空 log.log(Level.WARNING, "訂單id和會員id不能爲空"); return false; } // 代碼執行到這兒,表示訂單id和會員id都不爲空。 Map<String, String> data = new HashMap<String, String>(); data.put("u_mid", memberId); data.put("oid", orderId); data.put("c_time", String.valueOf(System.currentTimeMillis())); data.put("ver", version); data.put("en", "e_cr"); data.put("pl", platformName); data.put("sdk", sdkName); // 構建url String url = buildUrl(data); // 發送url&將url添加到隊列中 SendDataMonitor.addSendUrl(url); return true; } catch (Throwable e) { log.log(Level.WARNING, "發送數據異常", e); } return false; } /** * 根據傳入的參數構建url * * @param data * @return * @throws UnsupportedEncodingException */ private static String buildUrl(Map<String, String> data) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); sb.append(accessUrl).append("?"); for (Map.Entry<String, String> entry : data.entrySet()) { if (isNotEmpty(entry.getKey()) && isNotEmpty(entry.getValue())) { sb.append(entry.getKey().trim()) .append("=") .append(URLEncoder.encode(entry.getValue().trim(), "utf-8")) .append("&"); } } return sb.substring(0, sb.length() - 1);// 去掉最後& } /** * 判斷字符串是否爲空,若是爲空,返回true。不然返回false。 * * @param value * @return */ private static boolean isEmpty(String value) { return value == null || value.trim().isEmpty(); } /** * 判斷字符串是否非空,若是不是空,返回true。若是是空,返回false。 * * @param value * @return */ private static boolean isNotEmpty(String value) { return !isEmpty(value); } }
事件發送代碼:
package com.sxt.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; /** * 發送url數據的監控者,用於啓動一個單獨的線程來發送數據 * * @author root * */ public class SendDataMonitor { // 日誌記錄對象 private static final Logger log = Logger.getGlobal(); // 隊列,用戶存儲發送url private BlockingQueue<String> queue = new LinkedBlockingQueue<String>(); // 用於單列的一個類對象 private static SendDataMonitor monitor = null; private SendDataMonitor() { // 私有構造方法,進行單列模式的建立 } /** * 獲取單列的monitor對象實例 * * @return */ public static SendDataMonitor getSendDataMonitor() { if (monitor == null) { synchronized (SendDataMonitor.class) { if (monitor == null) { monitor = new SendDataMonitor(); Thread thread = new Thread(new Runnable() { @Override public void run() { // 線程中調用具體的處理方法 SendDataMonitor.monitor.run(); } }); // 測試的時候,不設置爲守護模式 // thread.setDaemon(true); thread.start(); } } } return monitor; } /** * 添加一個url到隊列中去 * * @param url * @throws InterruptedException */ public static void addSendUrl(String url) throws InterruptedException { getSendDataMonitor().queue.put(url); } /** * 具體執行發送url的方法 * */ private void run() { while (true) { try { String url = this.queue.take(); // 正式的發送url HttpRequestUtil.sendData(url); } catch (Throwable e) { log.log(Level.WARNING, "發送url異常", e); } } } /** * 內部類,用戶發送數據的http工具類 * * @author root * */ public static class HttpRequestUtil { /** * 具體發送url的方法 * * @param url * @throws IOException */ public static void sendData(String url) throws IOException { HttpURLConnection con = null; BufferedReader in = null; try { URL obj = new URL(url); // 建立url對象 con = (HttpURLConnection) obj.openConnection(); // 打開url鏈接 // 設置鏈接參數 con.setConnectTimeout(5000); // 鏈接過時時間 con.setReadTimeout(5000); // 讀取數據過時時間 con.setRequestMethod("GET"); // 設置請求類型爲get System.out.println("發送url:" + url); // 發送鏈接請求 in = new BufferedReader(new InputStreamReader(con.getInputStream())); // TODO: 這裏考慮是否能夠 } finally { try { if (in != null) { in.close(); } } catch (Throwable e) { // nothing } try { con.disconnect(); } catch (Throwable e) { // nothing } } } } }