# 数据埋点方案、监控方案
# 1、埋点分析
# 1、流程
数据采集 -> 上报 -> 数据分析 -> 监控
产品与数据分析:埋点名称、埋点信息、什么时候触发埋点
前端:确认埋点是否可行,是否可写
产品:GrowingIo、神策、诸葛IO、Heap
# 2、埋点数据
公用字段:
埋点的标识信息:eventId、eventType:click、pv、uv等
pv,page view 页面浏览次数,A用户访问页面两次,2pv
uv,user page view,按用户去重,A用户访问页面两次,1uv
业务自定义的信息,电商网站sku,eg:鞋子,颜色,size组合,红+42为一条sku
通用设备信息/用户信息:userId(登录),deviceId(未登录),useragent(Andriod/iOS/Huawei)、timestamp、location
formatTime:方便数据分析、errorStack、errorMsg(错误堆栈、信息)、level日志基本、traceId(错误标记,可将错误标记在区块中,便于错误分析)
# 3、埋点上报
- 实时上报:调用report之后立即发送请求
- 延时上报:sdk内部统一收集业务方要上报的信息,依拖于防抖或者在浏览器空闲时间或者在页面卸载前统一上送,上报失败做补偿。
- 埋点补偿
# 4、埋点方案
- 代码埋点:自定义属性和事件,对服务器的压力小
- 无埋点:无代码,框架自动采集全部事件及埋点数据,再由后端进行数据筛选和埋点分析,问题:性能不佳、无法个性化
- 实现:监听所有事件,上报所有点击事件以及对应的事件所在的元素,最后通过后台去分析数据
- 监听window事件,获取元素唯一标识id(getXPath)以及位置
- 可视化埋点:将业务代码与埋点代码分离,并通过搭建的可视化平台在输入的业务代码中添加埋点事件,最后输出的代码为业务代码与埋点代码耦合而成。
# 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
- 小程序MPlogger:小程序api
用户访问页面路径
- H5端:根组件watch route
- 小程序:onLoad Mixin
使用axio库全局监听请求,记录日志进行分析
# 3、低代码平台设计
背景:标准化工程解决前端工程问题;随着组件的标准化完善,使用区块和组件像积木一样快速搭建页面可极大提升研发效能。
架构设计:
组件拆分为:元组件(标准化组件实现)和布局组件,布局组件可嵌套元组件及复合组件
整体布局:物料区、渲染引擎、配置面板
- 物理区:元组件和布局组件
- 元组件和布局组件通过require.context自动导入
- 添加对应的渲染组件用于组件逻辑封装和解耦,后续调用render进行渲染,可使用jsx模版
- 渲染引擎:
- 输入:树形的jsonSchema结构,循环调用上述渲染组件进行渲染。
- 拖拽:使用h5的拖拽事件,内部可使用draggable事件包裹进行拖拽
- 物理区:元组件和布局组件
配置面板:不同类型的组件定义对应类型的配置面板
组件输出:vue原文件,通过拼接字符串实现,可借助服务端实现
- 区块依赖组件:可使用标签替代在vue主文件中,服务端配置源代码路径
- 前端发送对应的vue组件服务端解析后最终打包返回前端。
# 4、无限列表滚动方案
下拉到底,继续加载数据并拼接
数据太多,做虚拟列表展示:
- 首屏加载的时候,只加载可视区域内需要的列表项
- 滚动时,动态计算,获得可视区域内的列表项,并且将非可视区域内存在的列表项删除。
虚拟列表
计算当前可视区域开始数据索引startIndex
计算当前可视觉区域结束索引endIndex
计算当前可视区域的数据,并渲染在页面上
计算开始startIndex在总体列表中的位置偏移位置startOffset,并且设置到列表上
滚动
由于只是对可视区域内的列表进行渲染,为了保证列表容器的高度并可正常的触发滚动
容器: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>
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、监控系统
异常类型
- JS代码异常:运行错误
- Promise异常
- 静态资源加载异常
- api请求异常
- 跨域script异常
捕获方法
单点捕获:try/catch
全局捕获
js error:window.addEventListener('error')
resource error:window.addEventListener('error')
Promise异常:window.addEventListener('rejectionhandled|unhandlerejection')
重写XMLHttpRequset:添加捕获api请求异常
vue框架
- 打开所有日志和警告:Vue.config.silent = true
- errorHandler函数:指定组件的渲染和观察期间未捕获错误的处理函数。
react框架
- componentDidCatch:如果render()函数抛出错误,则会触发该函数,该函数包含错误堆栈的info。这个生命周期也会在后代组件抛出错误时被调用,但是不会捕获事件处理器和异步代码的异常。它会在【提交】阶段被调用,所以允许出现副作用
- getDerivedStateFromError:自带的捕获所有子组件中错误的方法,这个生命周期会在后代组件抛出错误时被调用。注意这个是在渲染阶段调用的,所以不允许出现副作用