# 数据埋点方案、监控方案

# 1、埋点分析

# 1、流程

数据采集 -> 上报 -> 数据分析 -> 监控

产品与数据分析:埋点名称、埋点信息、什么时候触发埋点

前端:确认埋点是否可行,是否可写

产品:GrowingIo、神策、诸葛IO、Heap

# 2、埋点数据

公用字段:

  1. 埋点的标识信息:eventId、eventType:click、pv、uv等

    pv,page view 页面浏览次数,A用户访问页面两次,2pv

    uv,user page view,按用户去重,A用户访问页面两次,1uv

  2. 业务自定义的信息,电商网站sku,eg:鞋子,颜色,size组合,红+42为一条sku

  3. 通用设备信息/用户信息:userId(登录),deviceId(未登录),useragent(Andriod/iOS/Huawei)、timestamp、location

formatTime:方便数据分析、errorStack、errorMsg(错误堆栈、信息)、level日志基本、traceId(错误标记,可将错误标记在区块中,便于错误分析)

# 3、埋点上报

  1. 实时上报:调用report之后立即发送请求
  2. 延时上报:sdk内部统一收集业务方要上报的信息,依拖于防抖或者在浏览器空闲时间或者在页面卸载前统一上送,上报失败做补偿。
  3. 埋点补偿

# 4、埋点方案

  1. 代码埋点:自定义属性和事件,对服务器的压力小
  2. 无埋点:无代码,框架自动采集全部事件及埋点数据,再由后端进行数据筛选和埋点分析,问题:性能不佳、无法个性化
    1. 实现:监听所有事件,上报所有点击事件以及对应的事件所在的元素,最后通过后台去分析数据
    2. 监听window事件,获取元素唯一标识id(getXPath)以及位置
  3. 可视化埋点:将业务代码与埋点代码分离,并通过搭建的可视化平台在输入的业务代码中添加埋点事件,最后输出的代码为业务代码与埋点代码耦合而成。

# 2、埋点系统

  • 原理:get请求1*1像素的git图片:体积最小、能完成完整的http请求、比xmlHttpRequest对象发送get请求性能好、跨域好

  • 埋点数据:埋点标识信息、设备信息、用户信息

  • 实时上报

  • 使用非侵入式埋点代码埋点:h5使用装饰器、小程序重写page和app对象,对生命周期及相关事件添加埋点hook。

  • 后端:pv服务器记录web日志,保存为非机构化数据

    • bdp通过ftp方式每日获取pv日志,进行结构化处理,然后入湖
    • pv服务器扫描pv日志通过kafka异步消息推送至bdsp并准实时入湖
  • 反哺:入湖数据分析,客户画像描述。

  • 扩展:

    • 异步上报:防抖、上报失败补偿机制

    • 无埋点:使用sdk封装好逻辑,业务侧使用,监听页面所有事件,上报点击事件、事件所在元素,发送后台分析

      • 监听所有事件:捕获机制,监听window元素
      • 获取元素唯一表示:xPath
      • 获取元素位置:offsetX、offsetY
    • 小程序和H5两端代码设计,采用设计模式基类复用,实现不同。

      • 数据采集baseLogger基类:

        • 小程序MPlogger:小程序api
          • getCurrentPages
          • wx.getSystemInfoSync
          • wx.onError
          • wx.onUnhandledRejection
        • H5端H5Logger:浏览器API
          • window.location.href
          • window.navigator.userAgent
          • Window.addEventListener
      • 用户访问页面路径

        • H5端:根组件watch route
        • 小程序:onLoad Mixin
      • 使用axio库全局监听请求,记录日志进行分析

# 3、低代码平台设计

背景:标准化工程解决前端工程问题;随着组件的标准化完善,使用区块和组件像积木一样快速搭建页面可极大提升研发效能。

架构设计:

  • 组件拆分为:元组件(标准化组件实现)和布局组件,布局组件可嵌套元组件及复合组件

  • 整体布局:物料区、渲染引擎、配置面板

    • 物理区:元组件和布局组件
      • 元组件和布局组件通过require.context自动导入
      • 添加对应的渲染组件用于组件逻辑封装和解耦,后续调用render进行渲染,可使用jsx模版
    • 渲染引擎:
      • 输入:树形的jsonSchema结构,循环调用上述渲染组件进行渲染。
      • 拖拽:使用h5的拖拽事件,内部可使用draggable事件包裹进行拖拽
  • 配置面板:不同类型的组件定义对应类型的配置面板

  • 组件输出:vue原文件,通过拼接字符串实现,可借助服务端实现

    • 区块依赖组件:可使用标签替代在vue主文件中,服务端配置源代码路径
    • 前端发送对应的vue组件服务端解析后最终打包返回前端。

# 4、无限列表滚动方案

  1. 下拉到底,继续加载数据并拼接

  2. 数据太多,做虚拟列表展示:

    1. 首屏加载的时候,只加载可视区域内需要的列表项
    2. 滚动时,动态计算,获得可视区域内的列表项,并且将非可视区域内存在的列表项删除。
  3. 虚拟列表

  • 计算当前可视区域开始数据索引startIndex

  • 计算当前可视觉区域结束索引endIndex

  • 计算当前可视区域的数据,并渲染在页面上

  • 计算开始startIndex在总体列表中的位置偏移位置startOffset,并且设置到列表上

  1. 滚动

    由于只是对可视区域内的列表进行渲染,为了保证列表容器的高度并可正常的触发滚动

    容器:infinite- list-container,相对定位

    需要一个元素来撑开高度保证滚动:infinite-list-phantom,绝对定位,z-index=0

    需要一个元素展示真正渲染的数据:infinite-list,绝对定位,z-index=-1

    • 监听滚动:监听infinite-list-container的滚动事件,获取scrollTop

      • 可视区域的高度:screenHeight
      • 列表项的高度:itemSize
      • 列表数据:listData
      • 当前滚动位置:scrollTop
    • 最终想要的数据

      • 列表总高度:listHeigh = listData.length * itemSize
      • 可显示的列表项:visibleCount = Math.ceil(screenHeight / itemSize)
      • 数据的起始索引:startIndex = Math.floor(scrollTop / itemSize)
      • 数据的结束索引:endIndex = startIndex + visibleCount
      • 列表真正显示数据:visibleData = listData.slice(startIndex, endIndex)
      • 偏移量:startOffset = scrollTop -(scrollTop%itemSize),当滚动后,由于渲染区域相对于可视区域已经发生了偏移,此时需要获取一个偏移量startOffset,通过样式控制将渲染区域偏移至可视区域中。
    • 无限滚动:当滚动触底, 就加载新一批数据, 拼接到原来的数据上

<template>
    <div class="infinite-list-container" ref="list" @scroll="scrollEvent">
        <!-- 撑起列表总高度 -->
        <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
        <!-- 实际渲染列表 -->
        <div class="infinite-list" :style="{ transform: getTransform }">
            <div class="infinite-list-item" v-for="item in visibleData" :key="item.id">
                <div class="left-section">
                    {{ item.title[0] }}
                </div>
                <div class="right-section">
                    <div class="title">{{ item.title }}</div>
                    <div class="desc">{{ item.content }}</div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
@Component
export default class VirtualList extends Vue {
    public listData: Data[] = [];
    // 可视区域高度
    public screenHeight: number = document.documentElement.clientHeight || document.body.clientHeight;
    // 可显示的列表项数
    public visibleCount: number = Math.ceil(this.screenHeight / this.itemSize);
    // 偏移量
    public startOffset: number = 0;
    // 起始索引
    public start: number = 0;
    // 结束索引
    public end: number = this.start + this.visibleCount;

    public $refs: {
        list: any;
    };

    // 列表总高度
    get listHeight() {
        return this.listData.length * this.itemSize;
    }

    // 偏移量对应的style
    get getTransform() {
        return `translate3d(0,${this.startOffset}px,0)`;
    }

    // 获取真实显示列表数据
    get visibleData() {
        return this.listData.slice(
            this.start,
            Math.min(this.end, this.listData.length)
        );
    }

    getTenListData() {
        if (this.listData.length >= 200) {
            return [];
        }
        return new Array(10).fill({}).map(item => ({ id: Faker.random.uuid(), title: Faker.name.title(), content: Faker.random.words() }))
    }

    created() {
        this.listData = this.getTenListData();
    }

    scrollToTop() {
        this.$refs.list.scrollTo({
            top: 0,
            left: 0,
            behavior: 'smooth'
        });
    }

    public scrollEvent(e: any) {
        // 当前滚动位置
        const scrollTop = this.$refs.list.scrollTop;
        // 此时的开始索引
        this.start = Math.floor(scrollTop / this.itemSize);
        // 此时的结束索引
        this.end = this.start + this.visibleCount;
        if (this.end > this.listData.length) {
            this.listData = this.listData.concat(this.getTenListData());
        }
        // 此时的偏移量
        this.startOffset = scrollTop - (scrollTop % this.itemSize);
    }
}
</script>
<style>
	.infinite-list-container {
    margin-top: 10px;
    height: 99%;
    overflow: scroll;
    position: relative;
    -webkit-overflow-scrolling: touch;
	}

  .infinite-list-phantom {
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      z-index: -1;
  }

  .infinite-list {
      left: 0;
      right: 0;
      top: 0;
      position: absolute;
      text-align: center;
  }

  .infinite-list-item {
      background: white;
      box-shadow: 0 0 10px rgba(144, 144, 144, 0.15);
      border-radius: 5px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-top: 10px;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# 5、监控系统

  1. 异常类型

    1. JS代码异常:运行错误
    2. Promise异常
    3. 静态资源加载异常
    4. api请求异常
    5. 跨域script异常
  2. 捕获方法

    1. 单点捕获:try/catch

    2. 全局捕获

      1. js error:window.addEventListener('error')

      2. resource error:window.addEventListener('error')

      3. Promise异常:window.addEventListener('rejectionhandled|unhandlerejection')

      4. 重写XMLHttpRequset:添加捕获api请求异常

      5. vue框架

        1. 打开所有日志和警告:Vue.config.silent = true
        2. errorHandler函数:指定组件的渲染和观察期间未捕获错误的处理函数。
      6. react框架

        1. componentDidCatch:如果render()函数抛出错误,则会触发该函数,该函数包含错误堆栈的info。这个生命周期也会在后代组件抛出错误时被调用,但是不会捕获事件处理器和异步代码的异常。它会在【提交】阶段被调用,所以允许出现副作用
        2. getDerivedStateFromError:自带的捕获所有子组件中错误的方法,这个生命周期会在后代组件抛出错误时被调用。注意这个是在渲染阶段调用的,所以不允许出现副作用
Last Updated: 11/11/2021, 11:04:53 AM