1. 接到这个项目时,我首先想清楚的三件事
去年年初,朋友介绍了一个公交公司的项目——做一套城市公交调度系统。初次沟通时,调度员给我演示了他们的日常:一本厚厚的发车记录本,一部对讲机,一块写满线路和车次的白板。车辆走到哪、下一班什么时候发、某个站点客流是否积压,全靠调度员的经验判断。这套模式在小县城、单一线路还能勉强运转,一旦线路超过十条、车辆超过五十台,人工调度的压力就非常明显。
客户的需求其实很朴素:要把车辆位置实时看到,要把发车计划自动排出来,要能查每天的营运数据。但"朴素的需求"落到系统设计上并不简单,因为公交调度牵扯的不仅是技术问题,还有业务流程、人员习惯、数据口径等一连串实际问题。基于SpringBoot来做这套系统,技术层面没有太大悬念,真正的难点在于把调度员的脑内经验转译成可计算、可落地的逻辑。
项目刚开始,我先做了一件事:和调度员一起跟车跑了三天。这三天对我的帮助远大于任何需求文档。公交调度的真实流程是这样的——早上发车前,车辆在首末站停车场等待,调度员根据当天的排班表安排车辆上线;车辆运营中,司机会在某些重要站点通过对讲机汇报路况和客流,比如"XX站人很多,需要加车""前方堵车,晚点十分钟";高峰结束,调度员再把多余的车辆从线路上抽回来。这个"上线-运营-抽车"的循环,是所有调度功能的核心骨架。
如果一开始就扎进代码里,很容易把系统做成"一堆列表的增删改查",而忽略了调度员真正需要的东西。我在项目启动会上立了三条设计原则,这几条原则贯穿了整个开发周期:
- 系统要能回答"车在哪、几时到、跑了几趟"这三个问题;
- 排班模块必须支持手动干预,算法只是辅助,不能替代调度员的决策权;
- 所有的操作都要留痕,因为公交公司有营运台账和审计要求。
这三条原则直接决定了后面的模块划分、数据库设计和接口方案。比如"操作留痕"这一条,导致我在所有核心表上都加了操作日志字段,并且单独建了一张操作流水表。当时看起来是多写了几个接口,上线三个月后就体现出价值了——某次早晚高峰发车记录对不上,就是靠流水表还原了当时的操作顺序,找出了原因。
1.1 调度系统里的角色与人机边界
公交调度系统里其实有四种角色,很多人容易搞混:系统管理员、调度员、司机、乘客(或对外查询端)。我见过不少同类项目,一上来就给司机和调度员分同样的权限,结果调度台能看到不该看的维修数据,司机端又被塞了一堆排班管理功能,整个系统臃肿难用。
合理的划分是这样的:系统管理员管基础数据,包括线路、站点、车辆档案、司机档案;调度员看的是当班线路的实时状态,核心操作是发车确认、临时加车、抽车、调整发车间隔;司机通过车载终端或手机端接收调度指令,上报异常情况,查询自己的班次计划;乘客端(如果有的话)只需要一个线路查询和实时到站预估,不需要登录就能用。
这个系统我最终实现了管理端和调度端两个Web入口,司机用的是一个轻量化的H5页面。没有做App,因为公交公司的车载终端系统比较老旧,短期内统一部署App不现实,H5可以直接嵌到现有的车载平板里用,成本最低。
1.2 先梳理业务流程,再设计软件功能
公交调度的业务流程可以用一条时间线来理解。前一天晚上,调度员根据客流历史数据、节假日安排、天气预测,制定第二天的排班计划;当天发车前,司机签到领车,调度员确认车辆上线;运营过程中,GPS设备每10秒上报一次位置,系统根据位置计算车辆是否准点,是否出现连续两车间隔过大的情况;高峰时段,调度员根据实时客流临时调整发车密度;收车后,系统自动汇总当天的运营数据,生成日报表。
这个流程梳理清楚之后,功能模块基本上就自己浮现出来了:基础数据管理、排班管理、实时监控、调度指令、统计分析。而我花时间最多的,是排班管理里的"如何定义一辆车准点"——听起来很简单,但实际上牵涉到首站发车时间、中间站到站时间偏差、末站收车时间三套口径。低估这个粒度,后面做准点率统计时会返工到怀疑人生。
提示:公交调度系统最忌讳一上来就写代码。先把"线路-车辆-班次-站点"这几个实体的关系画清楚,再谈技术方案。业务关系没理清,后面每个模块都会互相打架。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:SpringBoot生态里我为什么最终选这几样
选型这件事,我一向主张"够用就好,但要给未来留余量"。这套系统并发量不会像电商那样动辄每秒上万,但有一个特殊性:实时位置数据的写入是持续且高频的,如果五十台车每10秒上报一个坐标,一天下来的数据量就是几十万条,而且这些数据要保留至少三个月供轨迹回放。这个特性决定了技术方案不能全凭个人喜好来定。
2.1 SpringBoot版本与自动装配机制的取舍
SpringBoot版本我选了2.7.x。为什么不直接上3.x?原因有三个:第一,3.x基于Jakarta EE,很多老的依赖需要跟着升级,而公交公司这边有存量系统需要对接,比如某厂商的GPS设备SDK,翻了下它的依赖还是javax开头,直接升3.x就得改SDK或者做一层适配,风险不划算;第二,团队里几个人对SpringBoot 2.x的自动装配机制已经很熟,排错效率高;第三,2.7.x还在社区维护期内,安全更新跟得上。
这里顺带说一下SpringBoot自动装配的原理,因为后面排错时真的用到了。spring-boot-autoconfigure包里维护了一堆XXXAutoConfiguration类,SpringBoot启动时通过@EnableAutoConfiguration注解配合spring.factories(2.7.x)或AutoConfiguration.imports文件,把候选配置类加载进来,再配合@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBea
