1、实时监控

2、轨迹查询

3、告警管理

4、围栏设置

5、看板与报表

万级车辆实时地图 + 双模拟器:车联网前端与测试工程实践(第 3 篇 · 前端与测试)
前两篇讲了业务架构和后端链路。这篇讲数据到达前端之后的故事------也是整个系列里"工程密度"最高的一篇。
车联网的前端不是"一个后台 + 一个小程序"就完事:没有终端,联调无从谈起。所以我们的前端体系是"四位一体":Web 管理端 + 小程序/H5 + PC 模拟器 + Android 模拟器。前两位是产品,后两位是测试资产,四位一体才构成一个能交付、能验收、能回归的完整工程。
一、功能矩阵:两端展示,两台模拟器

图3-1 业务架构图
Web 管理端功能最全,按三个域组织:
- 监控域:监控大屏、实时监控地图、轨迹回放、宫格分组多车同屏
- 管理域:车辆档案、电子围栏、报警处理、运营报表
- 视频与指令域:视频墙轮巡 + 云台控制 + 对讲、历史录像回放、终端指令下发与台账
小程序/H5 聚焦移动看车:实时定位、设备树、报警处理、轨迹查询、实时视频。一套 uni-app 代码双端产出------微信小程序用原生 map 组件,H5 用 MapLibre + 天地图。它的 WS 协议实现与 Web 端逐字平移------不是"差不多",是同一份逻辑同一份常量的完整复制,两端行为严格一致。
两台模拟器是测试体系的根基:
- PC 模拟器(Java 21 + JavaFX + AtlantaFX):808/1078/809 三协议全模拟,四个 808 协议版本,多车批量压测,JSON 剧本驱动,HTTP 控制面(8899 端口)供自动化测试远程驱动
- Android 模拟器(Kotlin + Compose):808 + 1078,六种定位模式(CSV 回放 / GPX 回放 / 真实 GPS / 手动驾驶等),全量下行指令自动应答表,能真拍照、真推流
为什么模拟器敢拿去验收?因为它们的协议编解码和后端用的是同一个 codec jar ------模拟器行为 = 真终端行为。这不是口号,是第 1 篇说的 codec 红线在前端测试域的延伸。
二、核心难题:上万辆车实时动,怎么不卡
先把问题定义清楚。监控大屏打开,订阅上万辆车,每台车 5~30 秒上报一次定位,高峰期意味着每秒数百个位置更新穿过网络、穿过状态管理、最终变成地图上的像素移动。裸写的话:每秒数百次 Vue 响应式更新 + 数百次 DOM/图层操作,浏览器主线程直接卡死,风扇起飞。
答案是四级节流------不是一招,是一条管线,每一级把"更新次数"往下压一个量级:

图3-2 流程架构图
第 1 级:网络层检丢补全。 自研 WS 协议里每帧带序号(seq),解帧时检查连续性。发现缺口怎么办?注意位置是覆盖式状态 ------旧位置没有任何价值,不需要逐条补。所以策略是:缺口一旦检出,直接走 REST 拉一次全量快照,干净利落。另有两道保险:心跳看门狗 ------2.5 个心跳周期收不到任何帧就判定"假死"(TCP 半开连接的典型症状),主动断开重连;页签可见性监听------浏览器页签切到后台时渲染被冻结、定时器被节流,切回前台时主动做一次快照校验补查。
第 2 级:订阅层引用计数。 订阅来源是多方同时存在的:设备树勾选了一批车、地图视野圈了一批车、报警列表又在跟踪几台车。三方的订阅集合有重叠。做法是给每台车维护引用计数:任一来源订阅就 +1,全部退订才 -1 归 0 真正退订------谁退订都不影响其他订阅方。上行订阅帧分批发送,每批不超过 2000 台,避免单帧过大。
第 3 级:状态层批刷。 WS 帧到达后不直接写响应式状态 ,先进一个 100ms 的批刷队列,到点一次性合并提交------一帧合批了 200 台车的更新,也只触发一次 Vue 更新周期。车辆的尾迹(车头后面拖着的小尾巴)走非响应式环形缓冲,固定 60 个点,push 即覆盖,完全不进 Vue 的依赖追踪系统。
第 4 级:渲染层 rAF 合批。 地图图层操作按 50ms 节奏在 requestAnimationFrame 里合批执行。最重要的一条军规:万级点位禁止逐台 new Marker------每台车一个 DOM 元素,上万台就是上万个 DOM 节点,必死。正确姿势是全量车辆走 MapLibre 的矢量图层(GPU 渲染,一次绘制),只有用户选中的那台车单独挂一个真 Marker 承载点击弹窗。
四级叠起来的效果:网络层保证"不重不漏",订阅层保证"只收该收的",状态层把"每秒数百次更新"压成"每秒 10 次提交",渲染层再压成"每秒 20 次绘制"。每一级都在做同一件事:合并同类项,砍掉无意义的中间态。
三、自研 WS 协议:为什么不直接用裸 JSON
WebSocket 本身只是管道,上面跑什么格式得自己定。我们的协议极简:
- 上行(浏览器 → 服务端):订阅帧(按车订阅、批量订阅)+ ping 心跳
- 下行 (服务端 → 浏览器):三种帧------单台位置帧、合批位置帧(一帧打包 N 台车的最新位置,整帧 ≤64KB)、上下线通知帧
三个设计取舍:
- 合批帧是性能关键。后端推送侧本身就按窗口聚合,一帧推 200 台车只花一次帧头、一次解析、一次 JS 事件。如果一台一帧,光帧解析的开销就能把主线程吃垮
- 二进制友好。字段定长定序,比 JSON 省流量也省解析时间。别迷信 JSON 的可读性------推送链路是机器读的,可读性应该体现在文档和抓包工具上
- Web 端与小程序端逐字平移 。协议的 TS 实现在两端是同一份逻辑的复刻,帧常量、解析函数、心跳参数完全一致。一端改了协议另一端必须同步改,这是纪律不是建议------两端行为不一致的 bug,是联调阶段最贵的那类 bug
四、四端四套技术栈,四条契约绑成一个系统

图3-3 技术架构图
Web(Vue3 + Vite + MapLibre)、小程序/H5(uni-app + wot-design-uni)、PC 模拟器(JavaFX)、Android 模拟器(Compose)------四套技术栈各玩各的,但四条共享契约谁也不能违反:
|-------------------------------------------|---------------|
| 契约 | 它消灭的问题 |
| codec 纯 jar:编解码唯一实现,四个进程共用 | "模拟器能过,真车不能过" |
| WS 协议契约:Web 与小程序逐字平移 | 两端行为不一致 |
| 坐标纪律:存储计算 WGS-84,渲染边界才转 GCJ-02 | 同车不同位置、轨迹漂移 |
| ID 纪律:carId(档案 ID)≠ tid(设备号),字段命名强制区分 | 张冠李戴,指令发错车 |
坐标纪律值得展开说,因为它是车联网前端最高发的 bug 来源。国内地图坐标体系有三套:GPS 原始的 WGS-84、国测局加密的 GCJ-02(高德/腾讯/微信原生 map 用)、百度再加密的 BD-09。坐标一旦在不同环节"有的转了有的没转",同一辆车在不同页面上能差出几百米。
我们的纪律是单一口径 + 边界转换 :服务端存储、计算、推送只用 WGS-84 ;天地图(CGCS2000,显示上与 WGS-84 可直接对齐)免转换直接上图;微信小程序原生 map 组件要 GCJ-02,在数据进入 map 组件的最后一跳统一转换。任何中间环节禁止私自转换------转换函数在整个前端只有两个允许出现的位置。
坐标错乱和 ID 混用这两类 bug 有个共同点:不是能力问题,是制度问题------从制度上让它们不可能发生,比事后修便宜一百倍。
五、数据模型:三条线汇入一个状态源

图3-4 数据架构图
前端数据面三条线,职责互不替代:
- WS 帧管实时:订阅上行、推送下行,全双工,只承载"变化"
- REST 管快照与历史:设备树懒加载、地图全量快照(配合缺口补全)、历史轨迹抽稀、报警处理、视频信令------一切"一次性、可重来"的请求
- 坐标流管渲染边界:WGS-84 单一口径贯穿,按端决定在不在最后一跳转 GCJ-02
三条线都汇入同一个 wsStore------全端唯一的车辆状态源,再经批刷管线流向地图和列表。数据流单向、可追溯:界面上任何一辆车的状态,都能反推出它来自哪一帧、哪次快照、哪次合并。
历史轨迹回放单独说一句:原始轨迹点动辄几万个,全画到地图上又卡又乱。服务端按地图缩放级别做抽稀------缩得越小点越少,放得越大点越全,配合回放时的速度曲线和报警点位标注,取证场景既流畅又完整。
六、视频链路:浏览器播 H.265 的坎
车载摄像头为了省流量大量采用 H.265 编码,但浏览器原生不支持 H.265 硬解------这是车联网 Web 端绕不过去的坎。我们的方案:
- 容器与传输:后端 1078 网关把 RTP 流转封装成 FLV;浏览器走 WS-FLV(6899 端口)用 mpegts.js 解封装;小程序走 HTTP-FLV(6810 端口)
- H.265 解码 :mpegts.js 解出的 H.265 裸流交给 h265web(WASM 软解码器)逐帧解码上屏。软解吃 CPU,所以视频墙做了路数控制------同屏路数超过阈值时,非焦点窗口自动降码流/降帧率,焦点窗口保清晰
- 对讲:独立端口(6805)承载双向音频,与视频流互不干扰
视频墙还有个和后端呼应的设计:用户切走页面或关闭窗口,前端主动通知后端关流------配合后端"30 秒无消费者自动回收",双保险,绝不让忘关的流烧流量。
七、PC 模拟器:多车压测与剧本回归
PC 模拟器(Java 21 + JavaFX + AtlantaFX)是测试体系的重型武器,四个能力:
- 三协议全模拟:808 的定位/报警/指令应答、1078 的 RTP 推流(读取本地视频文件打成 RTP 包)、809 的下级平台行为,一个进程全搞定
- 四协议版本:808 的 2011/2013/2019 等版本可逐车切换,后端的多版本兼容性全靠它回归
- JSON 剧本:把"终端该干什么"写成剧本------几点几分上线、沿某条路线行驶、行驶中触发超速报警、收到指令后延迟 N 秒应答。剧本可版本化管理,测试用例就是代码
- HTTP 控制面(8899):创建/销毁车辆、启停剧本、注入报警,全部可通过 HTTP 远程驱动------CI 里的自动化集成测试就是脚本调控制面 + 断言后端台账
压测场景是这么跑的:控制面批量创建几千台虚拟车 → 全部上线鉴权 → 按真实频率并发上报定位 → 观察全链路水位(网关 CPU、MQ 堆积、PG 写入、WS 推送延迟)→ 找到拐点,确认余量。上线前的每一次容量结论,都是模拟器压出来的,不是拍脑袋拍的。
八、Android 模拟器:真机行为最后一块拼图
PC 模拟器解决了"量",Android 模拟器(Kotlin + Compose)解决"真":
- 六种定位模式:CSV 轨迹回放、GPX 回放、真实 GPS、手动驾驶(摇杆控制方向速度)等------既能复现固定路线做回归,也能开着真车路测
- 全量下行自动应答表:每一类 0x8xxx 指令的应答行为都可在表里配置------立即应答/延迟应答/应答失败,覆盖各种刁钻的终端行为
- 真拍照、真推流:用真机摄像头应答拍照指令、给 1078 推真实摄像头画面------这两件事 PC 模拟器只能用图片/文件模拟,真机才能端到端验证
交付验收的最后一环就是 Android 模拟器:拿着手机在真实园区里跑一圈,平台上看到的轨迹、视频、报警,和真终端跑出来的逐条比对。它过不了的验收,真终端一定也过不了。
九、系列总结
三篇文章,一条主线:
- 第 1 篇:五层架构 + 一条定位帧的业务闭环------骨架
- 第 2 篇:协议面/业务面分离 + MQ 信封 + Redis 路由 + 月分区------后端
- 第 3 篇(本篇):四级节流渲染管线 + 四端四契约 + 双模拟器测试闭环------前端与测试
回头看,这套平台没有黑科技,值钱的全是纪律:协议与业务分离、codec 零依赖红线、坐标单一口径、看车不查库、订阅不互踢、更新必节流。 这些纪律没有一条写在教科书上,条条都是事故的学费。守住它们,万级车辆实时监控用常规技术栈也能做得稳、做得久。
如果这个系列对你有帮助,欢迎点赞收藏评论交流。协议细节、渲染管线、模拟器设计,哪块想深入聊的,评论区见。
文中部署地址、密钥、域名等敏感信息均已脱敏;架构图为作者基于实际项目整理绘制。