跳到正文

首页 · 实习经历

氢能重卡国标数据上报

按 GB/T 32960 向对端上报车辆运行数据。平台原有实现是一次性脚本,缺少配置、试跑与断点续传能力;重建为常驻服务后,已在生产环境支撑 50 台车队实时上报。

自动驾驶数据平台 2026.07 – 至今 后端 · 独立负责
50 台
生产车队规模
500 万
日上报帧配额
17
REST 接口
100
单元测试
  • Java
  • Spring Boot
  • MySQL
  • Redis
  • TCP 二进制协议

业务背景

要求

按 GB/T 32960 向监管侧对端上报车辆运行数据。对端只接受符合国标的 TCP 二进制帧,任何字段偏差都会导致整包解析错位。

原有实现

一次性脚本。参数写死在代码里,不能试跑,连接一断就得从头重来,也没有任何进度与审计记录。

要补的能力

可配置、可试跑、可续跑、可观测的常驻服务 —— 让运营能自己发起上报并对结果负责,而不是每次找研发跑脚本。

一次上报的完整时序

含连接中断与断点续传分支 —— 这是生产环境里最常走到的路径。

13 / 13
管理前端平台服务任务引擎对端平台创建上报任务校验并映射 VIN 白名单提交任务,返回任务号申请并发信号量车辆登入登入应答 · 成功实时信息上报 × N分片进度落库连接中断重连并从断点续传车辆登出任务终态:成功 / 部分成功进度与分片明细

点播放看完整流程,或直接点任意一条消息,查看报文结构与当时的设计考虑。

任务状态机

「部分成功」是独立终态而非失败:有分片没跑通,但整体已尽力完成,运营需要据此决定补跑范围。

获得额度 全部分片成功 部分分片失败 不可恢复 / 取消 续跑 续跑 待运行 运行中 成功 部分成功 失败
待运行
任务已创建,尚未申请到并发额度
运行中
持有信号量,正在逐分片投递
成功
全部分片投递成功
部分成功
存在失败分片,但任务已跑完;运营据此决定是否补跑
失败
不可恢复错误,或取消后不再续跑

关键设计决策

并发控制用可过期信号量,而非互斥锁

问题

需要限制同时上报的车辆数,避免压垮对端;同时进程被杀或容器重启时,占用的额度必须能自动回收,否则额度会被永久泄漏。

备选方案

  • 进程内信号量:多实例部署下完全失效
  • 数据库悲观锁:每次申请都打数据库,且崩溃后锁残留
  • Redis 可过期信号量:带 TTL,持有者消失后自动释放

选择

Redis 可过期信号量。额度以带 TTL 的计数实现,任务运行期间定期续期;进程异常退出后 TTL 到期自动回收。

代价

续期失败会导致额度被提前回收、出现短暂超发。可接受,因为对端的硬限制留有余量,且超发窗口只有一个续期周期。

断点以分片为边界,而非以帧为边界

问题

连接中断是常态。续跑要能接上,但逐帧记录进度会让写库开销超过上报本身。

备选方案

  • 逐帧记录:精确但写放大严重
  • 只记任务级进度:写得少,但一断就得整体重来
  • 分片级记录:每片成功后写一次

选择

分片级记录。分片是重入的最小单位,续跑时从最后一个成功分片之后继续。

代价

中断发生在分片中途时,该片会被完整重发。对端按时间戳幂等处理,重复帧不会造成数据错误。

VIN 由服务端映射,不接受前端传入

问题

VIN 是车辆唯一标识,直接开放会让前端有能力向对端上报任意车辆的数据。

备选方案

  • 前端传 VIN,后端校验白名单
  • 前端只传业务分组,后端映射为 VIN

选择

前端只传车辆分组,服务端查白名单映射为 VIN。接口层面根本不存在传入 VIN 的入口,越权在设计上被消除而非靠校验兜住。

代价

分组与车辆的对应关系变更需要走配置,前端无法临时指定单车。运营场景不需要这个灵活度。

排障实录

自定义数据块缺少长度字段,导致对端解析错位

现象
前面的信息体对端都能正确解析,一旦报文里带上自定义数据块,对端从该块之后的所有字段全部错位,且不报错、只是数值荒谬。
排查
本端自解析完全正常,说明组帧逻辑「对自己」是自洽的。逐字节比对国标对自定义数据块的定义,发现块内约定了 2 字节长度字段,而我方实现直接跟了数据体。
成因
自定义块的长度字段是给对端用的定界依据。本端解析时因为已知自己写了多少字节,不读这个字段也能正确切分,所以本地测试完全发现不了。
修复
补上 2 字节长度字段,并为组帧结果增加「按国标规则反向切分」的单元测试,而不是用自己的解析器验证自己的编码器。

自描述字段是写给对端的契约,自己解析用不上也不能省。用自己的解码器验证自己的编码器,等于两边犯同一个错还互相背书。

并发限流被静默降级为全局互斥锁,任务集体假死

现象
并发数配置为 50,实际观察到任何时刻都只有一个任务在跑,其余全部卡在等待,且没有任何报错。
排查
限流实现里,信号量的键在某个分支下退化成了固定值,导致所有任务竞争同一个槽位 —— 相当于把信号量用成了全局互斥锁。加上持有者异常退出时锁不释放,队列越堆越长。
成因
限流键的构造逻辑存在一条未覆盖的分支,测试用例只验证了「并发被限制住」,没有验证「并发上限确实是 N」。
修复
修正键的构造,改用可过期信号量保证异常退出后自动释放,并补充断言实际并发度等于配置值的测试。

限流类缺陷不会报错,只会变慢,因此必须测「上限是多少」而不只是测「有没有被限制」。静默降级比直接失败更难发现。

在线验证

依据公开国标独立实现的解码器,在你的浏览器本地运行,不上传任何数据。改一个字节就能看到 BCC 校验的反应。

共 62 字节 · 悬停右侧字段可高亮对应字节

232302FE4C583044454D4F303030303030303041310100251A080E0F1E0001010301023000030D40027627104801140BB82300050007296B7801CDF1D0A7
起始符与 BCC 校验均通过

报文头

起始符'##'固定为 ASCII '##'
命令标识实时信息上报
应答标志命令包0xFE 表示这是一条上行命令包,而非对下行的应答
唯一识别码LX0DEMO00000000A1VIN 码,17 位 ASCII
数据单元加密方式不加密
数据单元长度37 字节与实际长度一致

采集时间

采集时间2026-08-14 15:30:00年份为实际年份减 2000,各字段单字节 BCD 语义外的纯数值

整车数据

信息类型标志0x01 整车数据
车辆状态启动
充电状态未充电
运行模式纯电
车速56.0 km/h分辨率 0.1 km/h
累计里程20000.0 km分辨率 0.1 km
总电压63.0 V分辨率 0.1 V
总电流0.0 A分辨率 0.1 A,偏移量 −1000 A
SOC72 %
DC-DC 状态工作
挡位挡位 4|驱动力 有|制动力 无bit0–3 挡位,bit4 驱动力,bit5 制动力
绝缘电阻3000 kΩ
加速踏板行程35 %
制动踏板状态0 %

车辆位置数据

信息类型标志0x05 车辆位置数据
定位状态定位有效|北纬|东经bit0 有效性,bit1 南北纬,bit2 东西经
经度120.155000°单位 10⁻⁶ 度
纬度30.274000°单位 10⁻⁶ 度

校验码

BCC 校验码校验通过从「命令标识」到「数据单元」末尾逐字节异或