背景介绍
基于PM2.5的健康路径规划项目,需要轨迹记录和轨迹展示功能。帮助用户记录出行轨迹,并提供分析PM2.5的吸入剂量。
主要涉及功能如下
- 实时采集用户轨迹并可视化在主页面地图中,点击开始按钮,采集开始,点击结束按钮采集结束,采集过程中支持暂停/继续
- 轨迹展示功能,点击轨迹在地图中生成轨迹图
亮点
- 定期推送功能
- 轨迹展示的灵活统计功能
设计思路
轨迹采集
两种策略:一种是暂存起来,定量推动到服务器。另外基于websocket实时推送
第一种策略实时性差,但是如果只是推送到服务器,不需要共享到其他用户,实时性不重要。前端可视化也不依赖于服务器的数据。第二种策略的实时性强,但是要保持持久连接较耗时。因此选择第一种。但是本文会给出两者的实现方法。
定量推送
主逻辑
定量推送数据:当
localStorage
中的数据量达到设定的阈值就推送到服务器。因为网络异常推送失败或其他原因,
localStorage
数据不断增加,导致容量接近上限时,上限是容量的2/3,将数据转移到IndexedDB
,继续保持数据采集。顺序问题:当恢复网络时,必须优先推送
IndexedDB
中的数据,随后再推送localStorage
中的数据,确保数据按时间顺序上传。检测网络状态:
navigator.onLine
用于判断当前网络状态是否在线。online
和offline
事件:分别在网络恢复和网络断开时触发。online
事件触发后立即尝试发送缓存数据,确保及时上传未发送的数据。为保证上传顺序使用任务队列和互斥锁,避免上传任务的冲突。
采用互斥锁
在上传过程中引入一种互斥锁的机制,确保在上传过程中不会重复访问同一份数据。这样在上传过程中,即使有新的数据加入到 localStorage,也不会与当前正在上传的数据发生冲突。避免了多个上传任务可能同时进行,导致并发冲突,比如数据重复上传、数据上传顺序错乱等问题
如何实现:在 JavaScript 的单线程环境中,虽然没有直接的互斥锁机制,但我们可以通过标志位(flag)的方式实现互斥锁的效果。
在我们的实现中,isUploading 就相当于一个互斥锁,它用来标记当前是否有上传任务正在进行:
1 | let isUploading = false; // 标志是否正在上传数据 |
1 | isUploading = true; // 加锁,表示系统正在执行上传任务 |
1 |
|
1 | if (isUploading) return; // 如果已加锁(正在上传),直接返回,不执行新的上传任务 |
实现
- 数据采集与存储
- 使用
navigator.geolocation
进行轨迹点的实时采集。每次采集到一个点后,立即将其存入localStorage
中。 - 在
localStorage
达到容量的2/3
时,将数据转移到IndexedDB
中以防止localStorage
的存储容量不足。 - 轨迹点数据的采集频率不依赖于网络状态,数据始终保持持续采集。
- 任务队列与上传机制
- 使用任务队列来管理数据上传过程,确保即使有多个任务并发执行时,上传操作不会发生冲突。
- 每次采集到的数据会在
localStorage
中暂存。当数据量达到一定的阈值(如500
个点),或者网络恢复时,系统会尝试将数据上传到服务器。 - 如果当前已有正在执行的上传任务,则新的上传任务会被添加到任务队列中,等待前一个任务完成后依次执行。这样可以避免同时多个上传任务导致的冲突。
- 网络状态检测与数据传输
- 系统实时监控网络状态,当网络断开时,数据会继续存储到
localStorage
或IndexedDB
中,而不会尝试上传。 - 当网络恢复时,会优先处理任务队列中的上传任务,将缓存中的数据(优先从
IndexedDB
,然后是localStorage
)上传至服务器。
- 上传过程与冲突管理
- 通过任务队列机制来防止并发上传冲突:当一个上传任务正在进行时,新的上传任务不会立即执行,而是加入队列等待当前任务完成。
- 在上传成功后,系统会清理已上传的数据(从 localStorage 或
IndexedDB
中删除),并继续处理下一个上传任务,直到所有缓存的数据都上传完成。
- 数据传输与顺序维护
- 当网络恢复时,系统首先检查
IndexedDB
中的数据。由于IndexedDB
保存的是localStorage
中超过容量的数据,因此IndexedDB
中的数据应优先上传。 - 在
IndexedDB
的数据上传完成后,系统会继续处理 localStorage 中的数据,保证传输顺序的正确性。
实时推送
主逻辑
获取到一个点数据就备份到
localStorage
中,然后推送到服务端,同送成功将备份删除。遇到
websocket
断开的的情况,将数据保存如localStorage
,链接回复,将localStorage
的数据推送到服务端,推送成功就删除localStorage
中的数据。
推送顺序问题
点的顺序对轨迹来说是重要的,因此需要确保数据库中存储的点的数据是正确的,每一个点数据有时间戳,可以在后端进行排序。也可以在前端控制推送顺序,使用双队列。从复杂度上说前端复杂度较小,因此选择前端处理的方式。
实现
- 数据采集与存储
- 使用
navigator.geolocation
进行轨迹点的实时采集,每次采集到一个点后,立即将其存入实时队列,并将该数据缓存到localStorage
中。 - 实时队列用于存储采集到的新数据,实时发送给服务端;
localStorage
则作为本地缓存,防止断网或其他异常情况下数据丢失。
WebSocket
连接与重连机制
WebSocket
建立后,若连接成功,会开始发送实时数据,并检查是否有缓存的轨迹数据(localStorage
中)。- 若
WebSocket
连接断开,会进入重连模式,在重连成功后立即尝试将缓存中的数据发送到服务器。
- 双队列管理
实时队列:存储正在采集的数据,WebSocket
连接正常时,直接发送数据给服务端。
- 缓存队列(
localStorage
):当WebSocket
连接断开时,实时队列中的数据存储到localStorage
中;当连接恢复时,发送缓存队列的数据。
- 双队列管理
- 数据传输与顺序维护
- 当网络恢复时,先将
localStorage
中的数据传输到服务端,随后再发送实时队列中的新数据,确保传输顺序不乱。
localStorage
清空与缓存清理
- 在
WebSocket
连接恢复后,成功发送缓存队列的数据后,立即从localStorage
中删除已成功推送的数据。 - 避免重复发送数据,确保只发送最新数据。
历史轨迹展示
可以根据年、月、周、日来统计和显示轨迹,对后端传来的数据进行处理统计,统计原则根据用户的设置决定,生成用于渲染的数据。点击每一条数据在地图上生成轨迹。