欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS前端監(jiān)控采集用戶行為的N種姿勢

 更新時間:2022年07月22日 10:33:38   作者:楊成功  
這篇文章主要為大家介紹了JS前端監(jiān)控采集用戶行為的N種姿勢,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

上一篇我們詳細介紹了前端如何采集異常數(shù)據(jù)。采集異常數(shù)據(jù)是為了隨時監(jiān)測線上項目的運行情況,發(fā)現(xiàn)問題及時修復(fù)。在很多場景下,除了異常監(jiān)控有用,收集用戶的行為數(shù)據(jù)同樣有意義。

怎么定義行為數(shù)據(jù)?顧名思義,就是用戶在使用產(chǎn)品過程中產(chǎn)生的行為軌跡。比如去過哪幾個頁面,點過哪幾個按鈕,甚至在某個頁面停留了多長時間,某個按鈕點擊了多少次,如果有需求都可以記錄下來。

但是記錄行為數(shù)據(jù)是一個和業(yè)務(wù)緊密關(guān)聯(lián)的事情,不可能把每個用戶每一步操作都極其詳細的記錄下來,這樣會產(chǎn)生極其龐大的數(shù)據(jù),很顯然不現(xiàn)實。

合理的做法是,根據(jù)產(chǎn)品的實際情況評估,哪個模塊哪個按鈕需要重點記錄,則可以采集的詳細一些;哪些模塊不需要重點關(guān)注,則簡單記錄一下基本信息。

根據(jù)這個邏輯,我們可以把行為數(shù)據(jù)分為兩類:

  • 通用數(shù)據(jù)
  • 特定數(shù)據(jù)

下面分別介紹這兩類數(shù)據(jù)該如何收集。

通用數(shù)據(jù)

在一個產(chǎn)品中,用戶最基本的行為就是切換頁面。用戶使用了哪些功能,也能從切換頁面中體現(xiàn)出來。因此通用數(shù)據(jù)一般是在頁面切換時產(chǎn)生,表示某個用戶訪問了某個頁面。

頁面切換對應(yīng)到前端就是路由切換,可以通過監(jiān)聽路由變化來拿到新頁面的數(shù)據(jù)。Vue 在全局路由守衛(wèi)中監(jiān)聽路由變化,任意路由切換都能執(zhí)行這里的回調(diào)函數(shù)。

// Vue3 路由寫法
const router = createRouter({ ... })
router.beforeEach(to => {
  // to 代表新頁面的路由對象
  recordBehaviors(to)
})

React 在組件的 useEffect 中實現(xiàn)相同的功能。不過要注意一點,監(jiān)聽所有路由變化,則需要所有路由都經(jīng)過這個組件,監(jiān)聽才有效果。具體的方法是配置路由時加 * 配置:

import HomePage from '@/pages/Home'
<Route path="*" component={HomePage} />,

然后在這個組件的的 useEffect 中監(jiān)聽路由變化:

// HomePage.jsx
const { pathname } = useLocation();
useEffect(() => {
  // 路由切換這個函數(shù)觸發(fā)
  recordBehaviors(pathname);
}, [pathname]);

上面代碼中,在路由切換時都調(diào)用了 recordBehaviors() 方法并傳入了參數(shù)。Vue 傳的是一個路由對象,React 傳的是路由地址,接下來就可以在這個函數(shù)內(nèi)收集數(shù)據(jù)了。

明確了在哪里收集數(shù)據(jù),我們還要知道收集哪些數(shù)據(jù)。收集行為數(shù)據(jù)最基本的字段如下:

app:應(yīng)用的名稱/標識

env:應(yīng)用環(huán)境,一般是開發(fā),測試,生產(chǎn)

version:應(yīng)用的版本號

user_id:當前用戶 ID

user_name:當前用戶名

page_route:頁面路由

page_title:頁面名稱

start_at:進入時間

end_at:離開時間

上面的字段中,應(yīng)用標識、環(huán)境、版本號統(tǒng)稱應(yīng)用字段,用于標志數(shù)據(jù)的來源。其他字段主要分為 用戶,頁面時間三類,通過這三類數(shù)據(jù)就可以簡單的判斷出一件事:誰到過哪個頁面,并停留了多長時間。

應(yīng)用字段的配置和獲取方式我們在上一節(jié) 搭建前端監(jiān)控,如何采集異常數(shù)據(jù)? 中講過,就不做多余介紹了,獲取字段的方式都是通用的。

下面介紹其他的幾類數(shù)據(jù)如何獲取。

獲取用戶信息

現(xiàn)代前端應(yīng)用存儲用戶信息的方式基本都是一樣的,localStorage 存一份,狀態(tài)管理里存一份。因此獲取用戶信息從這兩處的任意一處獲得即可。這里簡單介紹下如何從狀態(tài)管理中獲取。

最簡單的方法,在函數(shù) recordBehaviors() 所處的 js 文件中,直接導(dǎo)入用戶狀態(tài):

// 從狀態(tài)管理里中導(dǎo)出用戶數(shù)據(jù)
import { UserStore } from '@/stores';
let { user_id, user_name } = UserStore;

這里的 @/stores 指向我項目中的文件 src/stores/index.ts,表示狀態(tài)管理的入口文件,使用時替換成自己項目的實際位置。實際情況中還會有用戶數(shù)據(jù)為空的問題,這里需要單獨處理一下,方便我們在后續(xù)的數(shù)據(jù)查看中能看出分別:

import { UserStore } from '@/stores';
// 收集行為函數(shù)
const recordBehaviors = ()=> {
  let report_date = {
    ...
  }
  if(UserStore) {
    let { user_id, user_name} = UserStore
    report_date.user_id = user_id || 0
    report_date.user_name = user_name || '未命名'
  } else {
    report_date.user_id = user_id || -1
    report_date.user_name = user_name || '未獲取'
  }
}

上面代碼中,首先判斷了狀態(tài)管理中是否有用戶數(shù)據(jù),如果有則獲取,沒有則指定默認值。這里指定默認值的細節(jié)要注意,不是隨便指定的,比如 user_id 的默認值有如下意義:

  • user_id 為 0:表示有用戶數(shù)據(jù),但沒有 user_id 字段或該字段為空
  • user_id 為 -1:表示沒有用戶數(shù)據(jù),因而 user_id 字段獲取不到

用戶數(shù)據(jù)是經(jīng)常容易出錯的地方,因為涉及到登錄狀態(tài)和權(quán)限等復(fù)雜問題。指定了上述默認值后,就可以從收集到的行為數(shù)據(jù)中判斷出某個頁面用戶狀態(tài)是否正常。

獲取頁面信息

前面我們在監(jiān)聽路由變化的地方調(diào)用了 recordBehaviors 函數(shù)并傳入了參數(shù),頁面信息可以從參數(shù)中拿到,我們先看在 Vue 中怎么獲取:

// 路由配置
{
  path: '/test',
  meta: {
    title: '測試頁面'
  },
  component: () => import('@/views/test/Index.vue')
}
// 獲取配置
const recordBehaviors = (to)=> {
  let page_route = to.path
  let page_title = to.meta.title
}

Vue 中比較簡單,可以直接從參數(shù)中拿到頁面數(shù)據(jù)。相比之下,React 的參數(shù)只是一個路由地址,想拿到頁面名稱還需要做單獨處理。

一般在設(shè)計權(quán)限時,我們會在服務(wù)端會維護一套路由數(shù)據(jù),包含路由地址和名稱。路由數(shù)據(jù)在登錄后獲取,存在狀態(tài)管理中,那么有了 pathname 就可以從路由數(shù)據(jù)中找到對應(yīng)的路由名稱。

// React 中
import { RouteStore } from '@/stores';
const recordBehaviors = (pathname) => {
  let { routers } = RouteStore; // 取出路由數(shù)據(jù)
  let route = routers.find((row) => (row.path = pathname));
  if (route) {
    let page_route = route.path;
    let page_title = route.title;
  }
};

這樣,頁面信息的 page_route、page_title 兩個字段也拿到了。

設(shè)置時間

行為數(shù)據(jù)中用兩個字段 start_at、end_at 分別表示用戶進入頁面和離開頁面的時間。這兩個字段非常重要,我們在后續(xù)使用數(shù)據(jù)的時候可以判斷出很多信息,比如:

  • 某個用戶在某個頁面停留了多久?
  • 某個段時間內(nèi),某個用戶停留在哪幾個頁面?
  • 某個時間段內(nèi),哪個頁面的用戶停留時間最長?
  • 某個頁面,哪些用戶的使用率最高?

還有很多信息,都能根據(jù)這兩個時間字段判斷。開始時間很好辦,函數(shù)觸發(fā)時直接獲取當前時間:

var start_at = new Date();

結(jié)束時間這里需要考慮的情況比較多。首先要確定數(shù)據(jù)什么時候上報?用戶進入頁面后上報,還是離開頁面時上報?

如果進入頁面時上報,可以保證行為數(shù)據(jù)一定會被記錄,不會丟失,但此時 end_at 字段必然為空。這樣的話,就需要在離開頁面時再調(diào)接口,將這條記錄的 end_time 更新,這種方式的實現(xiàn)比較麻煩一些:

// 進入頁面時調(diào)用
const recordBehaviors = () => {
  let report_date = {...} // 此時 end_at 為空
  http.post('/behaviors/insert', report_date).then(res=> {
    let id = res.id // 數(shù)據(jù) id
    localStorage.setItem('CURRENT_BEHAVIOR_ID', id)
  })
}
// 離開頁面時調(diào)用:
const updateBehaviors = ()=> {
  let id = localStorage.getItem('CURRENT_BEHAVIOR_ID')
  let end_at = new Date()
  http.post('/behaviors/update/'+id, end_at) // 根據(jù) id 更新結(jié)束時間
  localStorage.removeItem('CURRENT_BEHAVIOR_ID')
}

上面代碼中,進入頁面先上報數(shù)據(jù),并保存下 id,離開頁面再根據(jù) id 更新這條數(shù)據(jù)的結(jié)束時間。

如果在離開頁面時上報,那么就要保證離開頁面前上報接口已經(jīng)觸發(fā),否則會導(dǎo)致數(shù)據(jù)丟失。在滿足這個前提條件下,上報邏輯會變成這樣:

// 進入頁面時調(diào)用
const recordBehaviors = () => {
  let report_date = {...} // 此時 end_at 為空
  localStorage.setItem('CURRENT_BEHAVIOR', JSON.stringify(report_date));
}
// 離開頁面時調(diào)用
const reportBehaviors = () => {
  let end_at = new Date()
  let report_str = localStorage.getItem('CURRENT_BEHAVIOR')
  if(report_str) {
    let report_date = JSON.parse(report_str)
    report_date.end_at = end_at
    http.post('/behaviors/insert', report_date)
  } else {
    console.log('無行為數(shù)據(jù)')
  }
}

對比一下這兩種方案,第一種的弊端是接口需要調(diào)兩次,這會使接口請求量倍增。第二種方案只調(diào)用一次,但是需要特別注意可靠性處理,總體來說第二種方案更好些。

特定數(shù)據(jù)

除了通用數(shù)據(jù),大部分情況我們還要在具體的頁面中收集某些特定的行為。比如某個關(guān)鍵的按鈕有沒有點擊,點了多少次;或者某個關(guān)鍵區(qū)域用戶有沒有看到,看到(曝光)了多少次等等。

收集數(shù)據(jù)還有一個更專業(yè)的叫法 ———— 埋點。直觀理解是,哪里需要上報數(shù)據(jù),就埋一個上報函數(shù)進去。

通用數(shù)據(jù)針對所有頁面自動收集,特定數(shù)據(jù)就需要根據(jù)每個頁面的實際需求手動添加。以一個按鈕為例:

<button onClick={onClick}>點擊</button>;
const onClick = (e) => {
  // console.log(e);
  repoerEvents(e);
};

上面代碼中,我們想記錄這個按鈕的點擊情況,所以做了一個簡單的埋點 ———— 在按鈕點擊事件中調(diào)用 repoerEvents() 方法,這個方法內(nèi)部會收集數(shù)據(jù)并上報。

這是最原始的埋點方式,直接將上報方法放到事件函數(shù)中。repoerEvents() 方法接收一個事件對象參數(shù),在參數(shù)中獲取需要上報的事件數(shù)據(jù)。

特定數(shù)據(jù)與通用數(shù)據(jù)的許多字段是一樣的,收集特定數(shù)據(jù)需要的基本字段如下:

app:應(yīng)用的名稱/標識

env:應(yīng)用環(huán)境,一般是開發(fā),測試,生產(chǎn)

version:應(yīng)用的版本號

user_id:當前用戶 ID

user_name:當前用戶名

page_route:頁面路由

page_title:頁面名稱

created_at:觸發(fā)時間

event_type:事件類型

action_tag:行為標識

action_label:行為描述

這些基本字段中,前 7 個字段與前面通用數(shù)據(jù)的獲取完全一樣,這里就不贅述了。實際上特定數(shù)據(jù)需要獲取的專有字段只有 3 個:

event_type:事件類型

action_tag:行為標識

action_label:行為描述

這三個字段也非常容易獲取。event_type 表示事件觸發(fā)的類型,比如點擊、滾動、拖動等,可以在事件對象中拿到。action_tag 和 action_label 是必須指定的屬性,表示本次埋點的標識和文字描述,用于在后續(xù)的數(shù)據(jù)處理時方便查閱和統(tǒng)計。

了解了采集特定數(shù)據(jù)是怎么回事,接下來我們用代碼實現(xiàn)。

手動埋點上報

假設(shè)要為登錄按鈕做埋點,按照上面的數(shù)據(jù)采集方式,我們書寫代碼如下:

<button data-tag="user_login" data-label="用戶登錄" onClick={onClick}>
  登錄
</button>;
const onClick = (e) => {
  // console.log(e);
  repoerEvents(e);
};

代碼中,我們通過元素的自定義屬性傳遞了 tag 和 label 兩個標識,用于在上報函數(shù)中獲取。

上報函數(shù) repoerEvents() 代碼邏輯如下:

// 埋點上報函數(shù)
const repoerEvents = (e)=> {
  let report_date = {...}
  let { tag, label } = e.target.dataset
  if(!tag || !label) {
    return new Error('上報元素屬性缺失')
  }
  report_date.event_type = e.type
  report_date.action_tag = tag
  report_date.action_label = label
  // 上報數(shù)據(jù)
  http.post('/events/insert', report_date)
}

這樣就實現(xiàn)了一個基本的特定數(shù)據(jù)埋點上報功能。

全局自動上報

現(xiàn)在我們回過頭來梳理一下這個上報流程,雖然基本功能實現(xiàn)了,但是還有些不合理之處,比如:

  • 必須為元素指定事件處理函數(shù)
  • 必須為元素添加自定義屬性
  • 在原有事件處理函數(shù)中手動添加埋點,侵入性高

首先我們的埋點方式是基于事件的,也就是說,不管元素本身是否需要事件處理,我們都要給他加上,并在函數(shù)內(nèi)部調(diào)用 repoerEvents() 方法。如果一個項目需要埋點的地方非常多,這種方式的接入成本就會非常高。

參考之前做異常監(jiān)控的邏輯,我們換一個思路:能否全局監(jiān)聽事件自動上報呢?

思考一下,如果要做全局監(jiān)聽事件,那么只能監(jiān)聽需要埋點的元素的事件。那么如何判斷哪些元素需要埋點呢?

上面我們?yōu)槁顸c的元素指定了 data-tag 和 data-label 兩個自定義屬性,那是不是根據(jù)這兩個自定義屬性判斷就可以?我們來試驗一下:

window.addEventListener('click', (event) => {
  let { tag, label, trigger } = event.target.dataset;
  if (tag && label && trigger == 'click') {
    // 說明該元素需要埋點
    repoerEvents(event);
  }
});

上面代碼還多判斷了一個自定義屬性 dataset.trigger,表示元素在哪種事件觸發(fā)時需要上報。全局監(jiān)聽事件需要這個標識,這樣可避免事件沖突。

添加全局監(jiān)聽后,收集某個元素的特定數(shù)據(jù)就簡單了,方法如下:

<button data-tag="form_save" data-label="表單保存" data-trigger="click">
  保存
</button>

試驗證明,上述全局處理的方式是可行的,這樣的話就不需要在每一個元素上添加或修改事件處理函數(shù)了,只需要在元素中添加三個自定義屬性 data-tag,data-label,data-trigger 就能自動實現(xiàn)數(shù)據(jù)埋點上報。

組件上報

上面全局監(jiān)聽事件上報的方式已經(jīng)比手動埋點高效了許多,現(xiàn)在我們再換一個場景。

一般情況下當埋點功能成熟之后,會封裝成一個 SDK 供其他項目使用。如果我們將采集數(shù)據(jù)按照 SDK 的思路實現(xiàn),讓開發(fā)者在全局監(jiān)聽事件,是不是一個好的方式呢?

顯然是不太友好的。如果是一個 SDK,那么最好的方式是將所有內(nèi)容聚合成一個組件,在組件內(nèi)實現(xiàn)上報的所有功能,而不是讓使用者在項目中添加監(jiān)聽事件。

封裝組件的話,那么組件的功能最好是將要添加埋點的元素包裹,這樣自定義元素也就不需要指定了,而轉(zhuǎn)為組件的屬性,然后在組件內(nèi)實現(xiàn)事件監(jiān)聽。

以 React 為例,我們看一下如何將上面的采集功能封裝為組件:

import { useEffect, useRef } from 'react';
const CusReport = (props) => {
  const dom = useRef(null);
  const handelEvent = () => {
    console.log(props); // {tag:xx, label:xx, trigger:xx}
    repoerEvents(props);
  };
  useEffect(() => {
    if (dom.current instanceof HTMLElement) {
      dom.current.addEventListener(props.trigger, handelEvent);
    }
  }, []);
  return (
    <span ref={dom} className="custom-report">
      {props.children}
    </span>
  );
};
export default CusReport;

組件使用方式如下:

<CusReport tag="test" label="功能測試" trigger="click">
  <button>測試</button>
</CusReport>

這樣就比較優(yōu)雅了,不需要修改目標元素,只要把組件包裹在目標元素之外即可。

總結(jié)

本文介紹了搭建前端監(jiān)控如何采集行為數(shù)據(jù),將數(shù)據(jù)分為 通用數(shù)據(jù) 和 特定數(shù)據(jù) 兩個大類分別處理。同時也介紹了多種上報數(shù)據(jù)的方式,不同的場景可以選擇不同的方式。

其中的數(shù)據(jù)部分只介紹了實現(xiàn)功能的基礎(chǔ)字段,實際情況中可以根據(jù)自己的業(yè)務(wù)需求添加。

許多小伙伴留言這套前端監(jiān)控能否開源,肯定是要開源的,不過內(nèi)容比較多我還在做,等到基本完善了我會發(fā)一個版本,感謝小伙伴們的關(guān)注。

本系列文章如下

以上就是JS前端監(jiān)控采集用戶行為的N種姿勢的詳細內(nèi)容,更多關(guān)于JS前端監(jiān)控采集用戶行為的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論