H5可回溯方案浅析 -- 以保险投保为例

在金融行业搬过砖的同学应该多少都有听说过【可回溯】这个词,它主要是为了记录用户所有的操作轨迹以及交互状态,大都用在与用户发生纠纷时的举证,并且也是金融监管部门的监管红线(必须要有)。

首先,大家先看一下可回溯最终实现的效果,下面的GIF是从rrweb官网的demo中录制的。

tutieshi_640x456_18s.gif

如上图,所谓的可回溯就是把我们在页面上的所有操作都“录制”下来,然后在其他地方进行“回放”,可以看到录制和回放我都加了引号,因为这并不是真正的视频录制和回放,而是收集用户操作,再通过特殊的解析器,模拟当时用户的界面以及操作。这是怎么实现的呢?

可回溯实现方式

本文的回溯功能是基于开源插件rrweb开发的,具体的代码实现可以直接前往rrweb的github项目进行查看。

可回溯的本质其实也是一种日志上报,只是它需要上报的范围比普通的日志上报要大得多,不仅是用户的所有交互,还有页面模板本身的变化,都需要记录起来,形成一个个HTML快照,然后再输出。

HTML快照

关于快照的完整官方文档

在rrweb中快照分为两种,全量快照和增量快照。在初始化的时候会将当前的DOM树全量收集一次,包括引入的CSS,这就是全量快照。

后续的变动都为增量快照,其核心是MutationObserver,通过这个API我们可以清晰的感知到页面DOM树的变化,并且会把变化的内容通过数组的形式返回。

快照序列化

关于序列化的完整官方文档

快照其实是一个个DOM节点,这并不是一种适合存储、使用的数据格式,所以需要将它们转换成描述性质的数据结构,这个过程有点像逆虚拟DOM。序列化后的快照就可以被rrweb提供的播放器解析,随后完成回放。

扫描二维码关注公众号,回复: 14490862 查看本文章

回溯数据存储方案

可回溯功能本身其实rrweb已经能完全cover住了,但是要在实际项目中使用,我们还需要设计后续的存储、拉取方案。

回溯维度

在讨论如何存储数据之前,我们需要先明确数据的维度,这直接影响我们存储的方式。

我们将会以一个保险投保的项目为例:

2022年8月8号,用户A在投保详情页填写表单,阅读了健康告知,进入收银台付款,最终完成了一份健康险的购买。

我们可以分析一下上述的流程,不难看出其中一共有两个核心维度用户时间和一个可选维度页面

后续对可回溯数据的调取必然也会按照这些维度来进行的,可能是调取某用户的所有操作记录,也可能是调取某用户某个时间段内的操作记录,甚至可以精细到某页面的记录(大多数情况下我们都是应用级别的回溯)。

所以我们的存储内容也需要与上述维度进行关联,不限于存储的库表名、文件夹、文件名或者是具体内容。

数据上报

前端产生数据之后就应该进行数据的上报,回溯数据的上报原理和笔者之前写的日志上报十分类似。

我们会按一定的时间频率数据条目对已产生的数据进行数据传输上报。

数据存储

这里其实是整个存储方案最核心的地方,我们收集来的数据到底应该怎么持久化。

可回溯数据是否需要落库?不落库还能怎么办?

在笔者的实践中是没有使用数据库做持久化的,采用的是腾讯云的对象存储COS进行回溯数据存储,不使用数据库的原因有以下两点:

① 成本更低

可回溯数据是极低频的数据,但是存储时长又很长(通常是3年以上),如果用数据库做持久化成本会变得很高,而COS则很适合做这种事情。

② 前端自闭环

使用COS做持久化可以不需要后端的人力介入,靠前端人力可以实现自闭环。

确定持久化方式之后,我们需要考虑的就是具体的实现方式了,下面将会介绍两种实现方式。

纯前端

十分不推荐这种方式

对象存储服务都会提供H5接入的SDK,也就是说我们可以直接C端将可回溯的数据传输到COS的存储桶里,但是这样会将访问存储桶的密钥将暴露在前端,十分的不安全,很容易被做成图床。

并且后续的回溯数据拉取也需要借助后端服务,我们终究是没法完全避开后端服务(云函数)。

后端服务(云函数)

推荐这种方式,如果没有条件部署Node服务可以用云函数来替代

对象存储服务一定会提供Node接入的SDK,接入的流程也比较简单,并且后续的回溯数据调取也可以借助该服务来进行。

const COS = require('cos-nodejs-sdk-v5')
const cos = new COS({ SecretId, SecretKey })
cos.putObject({
    Bucket,
    Region,
    StorageClass,
    Key: body.targetFile, // 2022/08/${userId}/${recordId}/${index}.json
    Body: body.content, // 前端产生的可回溯数据
})
复制代码

因为腾讯云的对象存储是支持在线新建文件的,所以我们可以不用在服务端将文件写好再上传,而是直接将回溯数据上传到桶里并生成JSON文件。

存储名称

确定在对象存储里持久化之后,我们就需要考虑怎么维护文件的存储路径,按照上面分析出的储存维度,以及后续调取数据的模式,我们可以梳理出这样的储存路径。

年/月/用户标识/录制标识/切片

每个/都代表一层文件夹,下面介绍一下每一层的用途:

第一层是年份,因为在保险业务内可回溯的数据是按年来管理的,进行数据删除也是以年为维度删除的,这一层可以方便后续的维护。

第二层是月份,一个用户保险投保行为基本都能在一个月内完成,所以我们加了一层月份维度,让调取更加方便。

然后是用户标识,这里一般指用户的唯一标识,它可能是userId也可能是openId,具体视业务而定,核心就是能够定位到唯一的用户,因为大多数调取可回溯数据的时候,提供给我们的就只有时间和用户信息。

再接着就是录制标识,这也是比较核心的一环,具体的实现将会在下面的Record-id中阐述,这个标识主要的作用就是将前端分段上报的数据串联起来,组成一个完整的回溯数据。

最后一个就是最终的文件名,前端在上传的时候会维护一个index,用于标识这次上传的是录制标识中的第几个切片,主要用于保证回溯数据的顺序准确。

接入方式

rrweb官方提供了十分简约的接入方式,这种方式适用于大部分的情况。

let events = [];

rrweb.record({
  emit(event) {
    // 将 event 存入 events 数组中
    events.push(event);
  },
});

// save 函数用于将 events 发送至后端存入,并重置 events 数组
function save() {
  const body = JSON.stringify({ events });
  events = [];
  fetch('http://YOUR_BACKEND_API', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body,
  });
}

// 每 10 秒调用一次 save 方法,避免请求过多
setInterval(save, 10 * 1000);
复制代码

初始化时机

录制初始化的时机主要取决于录制的维度,是页面维度还是应用维度,两者的初始化时机完全不同,我们需要具体情况具体分析。

不过初始化的实现是完全一致的,我们可以封装好初始化的方法,然后就可以灵活使用了。

如果是应用级别的可回溯收集,我们就可以在SPA初始化的生命周期中完成这个初始化;如果是页面级的话也是在相应的页面生命周期中开始初始化。

record-id

按官方推荐的接入方式,接入后会发现每次save的数据之间其实是没有关联关系的,我们需要维护一个用于关联的标识,也就是标题中的record-id字段。

在初始化网页应用的时候,我们会随机生成一串字符串,并以record-id为key值存入sessionStorage中,后续每次的save操作都会带上该字符串,用于标识本次数据的归属。

除此之外,在初始化的时候,还需要初始化一个index属性,初始值为0或1都可以,每次save的时候也需要将其带上作为切片值,并且是自增的。

回放方案

const events = YOUR_EVENTS;

const replayer = new rrweb.Replayer(events);
replayer.play();
复制代码

和录制一样,rrweb提供了配套的回放解决方案,使用方式也十分简单。这里我们需要解决的是如何准确获取到需要回放的数据。

一般在提取可回溯数据时我们会拿到两个数据用户信息操作时间,然后则需要我们通过这两个数据定位到一个范围内的数据。

在本案例中,我们上传回溯数据时会将当时的record-id做一次日志上报,所以我们可以通过用户信息+操作时间在日志系统中查找到对应的record-id,然后可以拼出COS中完整的文件夹路径:

年/月/用户标识/录制标识

但是我们并不知道这个record-id下有多少个切片文件,接下来可以用COS提供的查询文件夹内容的APIgetBucket,查询到对应record-id下的切片文件有多少个,然后将所有切片文件批量拉取回来并在服务端完成拼接,最后输出给前端。

小结

整个H5的可回溯方案至此已经介绍完毕,用一张图来总结大概是这样的:

未命名文件 (1).png

整体来说,我们需要开发的地方并不多,因为大头已经有成熟的第三方插件帮我们处理好了,我们要做的是沉下心来考虑中间串联的部分。

毕竟一个完整的可回溯方案,数据收集、数据传输、数据持久化以及数据使用,是缺一不可的。

猜你喜欢

转载自juejin.im/post/7132729131568988196