首页 · 实习经历
氢能重卡国标数据上报
按 GB/T 32960 向对端上报车辆运行数据。平台原有实现是一次性脚本,缺少配置、试跑与断点续传能力;重建为常驻服务后,已在生产环境支撑 50 台车队实时上报。
- 50 台
- 生产车队规模
- 500 万
- 日上报帧配额
- 17
- REST 接口
- 100
- 单元测试
- Java
- Spring Boot
- MySQL
- Redis
- TCP 二进制协议
业务背景
要求
按 GB/T 32960 向监管侧对端上报车辆运行数据。对端只接受符合国标的 TCP 二进制帧,任何字段偏差都会导致整包解析错位。
原有实现
一次性脚本。参数写死在代码里,不能试跑,连接一断就得从头重来,也没有任何进度与审计记录。
要补的能力
可配置、可试跑、可续跑、可观测的常驻服务 —— 让运营能自己发起上报并对结果负责,而不是每次找研发跑脚本。
数据链路
组件名称均为通用称谓。
点击任一节点,查看它在链路里的职责与我负责的部分。虚线为依赖调用,实线为主数据流。
一次上报的完整时序
含连接中断与断点续传分支 —— 这是生产环境里最常走到的路径。
点播放看完整流程,或直接点任意一条消息,查看报文结构与当时的设计考虑。
任务状态机
「部分成功」是独立终态而非失败:有分片没跑通,但整体已尽力完成,运营需要据此决定补跑范围。
- 待运行
- 任务已创建,尚未申请到并发额度
- 运行中
- 持有信号量,正在逐分片投递
- 成功
- 全部分片投递成功
- 部分成功
- 存在失败分片,但任务已跑完;运营据此决定是否补跑
- 失败
- 不可恢复错误,或取消后不再续跑
关键设计决策
并发控制用可过期信号量,而非互斥锁
问题
需要限制同时上报的车辆数,避免压垮对端;同时进程被杀或容器重启时,占用的额度必须能自动回收,否则额度会被永久泄漏。
备选方案
- 进程内信号量:多实例部署下完全失效
- 数据库悲观锁:每次申请都打数据库,且崩溃后锁残留
- Redis 可过期信号量:带 TTL,持有者消失后自动释放
选择
Redis 可过期信号量。额度以带 TTL 的计数实现,任务运行期间定期续期;进程异常退出后 TTL 到期自动回收。
代价
续期失败会导致额度被提前回收、出现短暂超发。可接受,因为对端的硬限制留有余量,且超发窗口只有一个续期周期。
断点以分片为边界,而非以帧为边界
问题
连接中断是常态。续跑要能接上,但逐帧记录进度会让写库开销超过上报本身。
备选方案
- 逐帧记录:精确但写放大严重
- 只记任务级进度:写得少,但一断就得整体重来
- 分片级记录:每片成功后写一次
选择
分片级记录。分片是重入的最小单位,续跑时从最后一个成功分片之后继续。
代价
中断发生在分片中途时,该片会被完整重发。对端按时间戳幂等处理,重复帧不会造成数据错误。
VIN 由服务端映射,不接受前端传入
问题
VIN 是车辆唯一标识,直接开放会让前端有能力向对端上报任意车辆的数据。
备选方案
- 前端传 VIN,后端校验白名单
- 前端只传业务分组,后端映射为 VIN
选择
前端只传车辆分组,服务端查白名单映射为 VIN。接口层面根本不存在传入 VIN 的入口,越权在设计上被消除而非靠校验兜住。
代价
分组与车辆的对应关系变更需要走配置,前端无法临时指定单车。运营场景不需要这个灵活度。
排障实录
自定义数据块缺少长度字段,导致对端解析错位
- 现象
- 前面的信息体对端都能正确解析,一旦报文里带上自定义数据块,对端从该块之后的所有字段全部错位,且不报错、只是数值荒谬。
- 排查
- 本端自解析完全正常,说明组帧逻辑「对自己」是自洽的。逐字节比对国标对自定义数据块的定义,发现块内约定了 2 字节长度字段,而我方实现直接跟了数据体。
- 成因
- 自定义块的长度字段是给对端用的定界依据。本端解析时因为已知自己写了多少字节,不读这个字段也能正确切分,所以本地测试完全发现不了。
- 修复
- 补上 2 字节长度字段,并为组帧结果增加「按国标规则反向切分」的单元测试,而不是用自己的解析器验证自己的编码器。
自描述字段是写给对端的契约,自己解析用不上也不能省。用自己的解码器验证自己的编码器,等于两边犯同一个错还互相背书。
并发限流被静默降级为全局互斥锁,任务集体假死
- 现象
- 并发数配置为 50,实际观察到任何时刻都只有一个任务在跑,其余全部卡在等待,且没有任何报错。
- 排查
- 限流实现里,信号量的键在某个分支下退化成了固定值,导致所有任务竞争同一个槽位 —— 相当于把信号量用成了全局互斥锁。加上持有者异常退出时锁不释放,队列越堆越长。
- 成因
- 限流键的构造逻辑存在一条未覆盖的分支,测试用例只验证了「并发被限制住」,没有验证「并发上限确实是 N」。
- 修复
- 修正键的构造,改用可过期信号量保证异常退出后自动释放,并补充断言实际并发度等于配置值的测试。
限流类缺陷不会报错,只会变慢,因此必须测「上限是多少」而不只是测「有没有被限制」。静默降级比直接失败更难发现。
在线验证
依据公开国标独立实现的解码器,在你的浏览器本地运行,不上传任何数据。改一个字节就能看到 BCC 校验的反应。
共 62 字节 · 悬停右侧字段可高亮对应字节
报文头
| 起始符 | '##'固定为 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 |
| SOC | 72 % |
| DC-DC 状态 | 工作 |
| 挡位 | 挡位 4|驱动力 有|制动力 无bit0–3 挡位,bit4 驱动力,bit5 制动力 |
| 绝缘电阻 | 3000 kΩ |
| 加速踏板行程 | 35 % |
| 制动踏板状态 | 0 % |
车辆位置数据
| 信息类型标志 | 0x05 车辆位置数据 |
| 定位状态 | 定位有效|北纬|东经bit0 有效性,bit1 南北纬,bit2 东西经 |
| 经度 | 120.155000°单位 10⁻⁶ 度 |
| 纬度 | 30.274000°单位 10⁻⁶ 度 |
校验码
| BCC 校验码 | 校验通过从「命令标识」到「数据单元」末尾逐字节异或 |