前端监控之用户行为监控实践1(数据收集)

前文对前端监控进行了简单介绍,起因是因为当前做的一个需求,老板要看当前项目的uv、pv信息。其实这是非常简单的统计。 但在最开始接到这个需求,却难倒我了。 现在进行简单的复盘,记录一下实现方法。 

一、数据记录

用户行为从大类上主要分为两个方面:

① 页面浏览行为 ② 操作行为

统一埋点函数

基于此共识,首先封装一个统一的埋点函数,代码如下:

export const captureMessage = async (type: 'browse' | 'action', content: string, title?: string) => {
  // 省略了判断开发环境时,在本地打印的代码
  const data = { type, content, title };
  // 这里调用的是统一封装的请求函数
  await post('userlog', data, { 'Content-Type': 'application/json; charset=utf-8' });
};

然后,采用的埋点方式,是在上文中提到的(前端监控简介_艾米栗写代码的博客-CSDN博客)手动埋点。 

页面浏览埋点

我们最初的埋点比较简单,主要是对 url 的埋点。而在 vue 中,url 的跳转主要是通过路由实现的,因此,只需要在全局检测路由的变化。代码如下:

    watch(() => route.fullPath, () => {
      const title = (route.meta?.title ?? '') as string
      const url: string = (route as any).fullPath || route.path
      captureMessage('browse', url, title)
    })

操作行为埋点  

而操作行为的埋点同理,只需在需要监控的操作事件响应函数中,插入captureMessage 函数即可。

二、打补丁

问题分析

以上是我们最开始的记录方式,原则上,这样记录是能够满足后续的分析需求的。

注意,这也只是理论上可行。 但后续,产品提出要分析某个页面下不同子页面的pv、uv 情况。但项目当前的情况是,这个页面下的URL极其混乱,无法进行统一分析。为了满足分析需要,需要对这个页面下的 URL 进行统一化。

具体说来,页面的URL 为 /report。

进来之后的初始页面默认为子页面 1,这里我们设 子页面1名称为 table1.

在切换报表时,其 URL 会响应为子页面名称 ,即 /report/tableName。

但除此之外,该页面下还存在另外三种URL类型。

类型1:ID 类的 URL 是报表最开始的迭代方式,其ID 为 数字,即 /report/numberID

类型2:模板类的URL 记录的是,某一子页面下的用户选择项。其模板ID通过MD5生成,URL 为: /report/templateID。

类型3:具体信息类,从其他页面跳转到该子页面,携带的是具体请求信息。其URL 为:

/report?query=***(query 为对象,进行 encode 编码)

为了满足报表统计的需要,需要对不同URL进行统一,而统一的方式为,在 当前URL 的后面,加上 /table/tableName 。

即:

/report --> /report/table1

/report/tableName --> /report/tableName (保持不变)

/report/numberID --> /report/numberID/table/tableName 

/report/templateID --> /report/templateID/table/tableName

/report?query=*** --> /report?query=***/table/tableName

numberID和 templateID 对应的tableName在对应的数据库中可以查询到。

描述完了要做的事情,上代码咯!

问题解决(补丁)

代码

export const getEventTableUrl = async (content:string) => {
  
  let table = ''

  const eventUrl = content.replace('/report', '')

  // 情况1: content 为 report/table/tableName
  if(eventUrl.startsWith('/table')){
    table = ''
  }
  else if(eventUrl === '') {
    // 情况2: content 仅为: /report    
    table = DEFAULT_QUERY_TABLE
  }
  else if(eventUrl.includes('?')) {
    // 情况3: content 为:/report?query=*** 参数中有query,解析query 参数获得queryKey
    // 匹配query参数
    const matchContent = eventUrl.match(/[?&]query=([^&/]*)&?/)
    table = getTableFromQuery(matchContent ? matchContent[1] : '')
  }
  else {
    // 情况4:content为:/report/***(id) 从模板id 或 eventid 中获得querykey
    table = await getTableFromTemplate(eventUrl.split('/')[1])
  }
  
  if(!table) return content;

  return `${content}/table/${table}`
}

统一代码过程中,有两个小细节花费了较多时间。 

细节1: 拿到 URL 中的 query 参数内容

如何拿到get 请求中的参数信息,这是一个老生长谈的话题了。解决方法也是多种多样,在网上一搜,有人暴力破解和匹配的,有人调用函数的,有人用正则的。

比如: JavaScript 正则表达式获取url后的内容(第一个问号后的内容)_白菜new的博客-CSDN博客

而我,最开始的想法是,寻找 node 中是否提供了通用的解决方案。

哎嘿,还真的有:

MDN官方文档:

URL API - Web APIs | MDN

来自鑫旭大佬的详细介绍:

JS URL()和URLSearchParams() API接口详细介绍 « 张鑫旭-鑫空间-鑫生活

实例代码如下: 

//截取问号之后的字符串
const search = eventUrl.match(/\?(\S*)/) 
// 获取query参数
const queryParam = new URLSearchParams(search ? search[0] : '').get('query')

 但是,我们组长在review 这段代码之后,他觉得,这样不够简洁,应该直接用正则进行匹配。。。

于是,迫于”强权“之下代码变成了这样

eventUrl.match(/[?&]query=([^&/]*)&?/)

虽然正则看起来很简洁,可是,大家的正则水平真的能一眼看懂这行代码是在干嘛么???

我经验少,咱也不知道是不是自己太菜,我只能加上注释为后来的人默默助力。。。

 但是呢,一味抗拒对于自己的成长是不利的,在此之下,我还是对正则进行了一番研究。

我发现,正则可以在 match、replace 等都可以使用。

可参考文章: 正则表达式和字符串的方法

细节2: 解析 query 参数

在解析上面提到的带有 query 参数的 URL 时,发生了两个意外情况。

1、无法解码

前端基础-encodeURIComponent原理 | 华仔的博客

2、无法将string转化为对象

这是因为,用户可以对url进行编辑,这就有可能导致,记录的query 可能信息不全。甚至query中有一些会解析出错的消息,比如说,有一些无法解码的 + 号。

因此,在解码和转化为对象的时候,需要进行错误捕获。

/** 解析string为json */
export const parseString = (str:string): Record<string,any>| null => {
  try {
    return JSON.parse(str)
  } catch {
    return null
  }
}

/** 解析 URI */
export const decodeUri = (uri: string) => {
  try {
    return decodeURIComponent(uri)
  } catch {
    return ''
  }
}

 通过以上的方式,我们就可以统计到需要进行数据分析的元信息了。

接下来就是进行数据分析了。

三、统计信息

这一部分我将用另外的篇幅去总结。

猜你喜欢

转载自blog.csdn.net/qq_34539486/article/details/129181037