搞了这么多年Java,也带过不少做毕设的学生,看到“基于SpringBoot的武夷智能公交系统”这类题目,我第一反应是:这题选得聪明。它既有SpringBoot这个当前最主流的后端框架撑门面,又有“线路规划”这种能正经讲算法的功能点,还有“实时车次管理”这种能展示推送和并发处理的实战场景。最难得的是,整套系统的业务边界非常清晰,本地化场景也好讲故事,本科生用半年时间从零啃下来,不算离谱,答辩时又有足够深度的技术点可以聊。
这里有个很多人都没想透的问题:同样是公交系统,为什么有的毕设做完就是个“数据库增删改查展示”,而有的能让人眼前一亮?差别就在“实时”两个字。用户打开App想知道的从来不是“哪条线路经过哪站”,而是“我等的这班车现在在哪儿”“还有几分钟到”“如果想去某个地方,怎么换乘最少、最省时间”。把这三个问题做扎实,项目就立住了。这篇文章把我从需求拆解、技术选型、表结构设计,到换乘算法、WebSocket实时推送、前后端联调的全过程捋了一遍,也把踩过的坑和答辩时的话术一起写出来,给准备开题或已经开工的同学一份能直接照着做的参考。
1. 项目定位与核心需求拆解
1.1 传统公交查询的三痛点和系统目标
武夷山这个场景我不比各位了解得更多,但国内公交出行的问题几乎都一样:站牌只写“首末班时间”,不告诉你车堵在哪条路、还有几站;想从A点到B点,不知道哪几趟车能到,更不知道换乘哪一站最划算;公交公司自己排班全凭经验,调整一条线路涉及站点、车辆、时刻表一堆纸质表格,改起来特别痛苦。这套系统要做的就是拆掉这三堵墙:
- 乘客打开页面,能看到线路上每一辆车的实时位置,以及到达当前站的预计时间;
- 输入起点和终点,系统给出多套换乘方案,标注全程时间和换乘次数;
- 运营人员在管理后台维护线路、站点和车次排班,车辆位置和在线状态一目了然。
再说得具体一点。传统查询场景里,乘客站在站台,最焦虑的是“车到底来不来、什么时候来”。这个焦虑的本质是信息不对称,而系统把车辆GPS位置实时暴露给乘客,就解决了这个问题。再比如说换乘规划,武夷山这种旅游城市,景点分散,游客对公交线路完全不熟,一个能给出“景区南门→度假区→高铁北站”多套方案的查询入口,价值远高于一张静态线路图。所以我在做需求分析时,把用户故事写成了三条主线:普通乘客查线路、游客规划出行、运营人员做线路和车次管理。三条主线对应三个角色,功能边界从一开始就是清楚的,后面开发和写论文都不容易跑偏。
1.2 功能模块怎么切分才算合理
模块划分直接决定开发工作量。很多同学喜欢把功能拆得很碎,今天加一个“意见反馈”,明天加一个“天气查询”,最后自己把自己累死。我的建议是:保住核心链路,其余都是加分项。核心链路就是“查询—规划—调度”这三件事。
| 端 | 子模块 | 关键功能 | 核心难点 |
|---|---|---|---|
| 乘客端 | 线路查询 | 按线路号、站点名搜索线路与途经站 | 站点与线路的多对多关系处理 |
| 乘客端 | 换乘规划 | 输入起终点,返回多套换乘方案 | 图算法与换乘次数剪枝 |
| 乘客端 | 实时位置 | 地图展示车辆位置、预计到站时间 | WebSocket推送与ETA计算 |
| 乘客端 | 公告信息 | 线路调整、特殊天气运营通知 | 无难点,CRUD即可 |
| 管理端 | 线路管理 | 维护线路、站点、站点顺序 | 顺序字段设计与地图选点 |
| 管理端 | 车次管理 | 排班、发车间隔、绑定车辆 | 时刻表自动生成逻辑 |
| 管理端 | 车辆监控 | 地图看车辆位置、在线/离线状态 | 位置数据刷新与离线判定 |
这个表做完,心里就有底了:乘客端三个核心功能都是“硬骨头”,管理端基本是常规CRUD加一点业务规则。如果时间紧张,可以先把车辆监控简化成“列表显示最后上报位置”,把地图展示放到第二迭代再做,但线路查询和换乘规划无论如何都要保住,这两个才是题目的灵魂。
2. 架构选型:为什么SpringBoot这套组合最合适
2.1 初中生都会问的问题:为什么不用SSH或SSM
我面试的时候常问这个问题,做毕设时也建议学生认真想明白。早些年做SSH(Struts+Spring+Hibernate)或SSM(Spring+SpringMVC+MyBatis),光XML配置文件就能写几屏,一个数据源配错就要折腾半天。SpringBoot最大的价值在于“约定大于配置”:内嵌Tomcat,打成一个jar直接跑;starter机制把常用依赖打包好,引入即用;自动配置根据classpath里的类库自动装配Bean。对你来说,这意味着省下大量配置时间,把精力集中在业务代码上。
当然,选SpringBoot还有一个很现实的答辩理由:它好讲。面试官或答辩老师问到“自动配置原理”,你可以从@SpringBootApplication说起,讲@EnableAutoConfiguration如何通过SpringFactoriesLoader加载AutoConfiguration类,再讲条件注解@ConditionalOnClass如何按需装配。这套逻辑理清楚了,甚至比项目本身还能加分。版本上我建议用SpringBoot 2.7.x配JDK 8,网上教程最多,踩坑资料最好找;如果你非要上3.x配JDK 17,那就要做好很多老代码和第三方包不兼容的心理准备,不是不行,是不值得在毕设里冒险。
2.2 技术栈清单与四层架构落地
整套系统我用的技术栈很克制,没有为了炫技堆东西,每个组件都有明确用途:
| 组件 | 版本建议 | 用途 |
|---|---|---|
| SpringBoot | 2.7.x | 后端核心框架 |
| MyBatis-Plus | 3.5.x | 数据库ORM,单表CRUD免手写SQL |
| MySQL | 8.0 | 业务数据存储 |
| Redis | 5.x | 车辆最新位置缓存、热点数据缓存 |
| WebSocket | Spring自带 | 车辆实时位置推送 |
| Spring Task | Spring自带 | 定时任务:模拟车辆位置上报、离线检测 |
| Vue + Element UI | Vue 2/3 | 管理端前端,我用的是Vue 2,稳定性优先 |
| 高德地图JS API | 最新版 | 地图展示与选点 |
分层还是经典的Controller-Service-Mapper三层,没引入复杂设计模式,但我在分包上做了文章。包结构是这样:
com.wuyi.smartbus ├── controller ├── service ├── mapper ├── entity(数据库实体) ├── dto(请求参数封装) ├── vo(响应数据封装) ├── config(配置类) ├── common(统一返回体、异常处理、常量) └── websocket这样的好处第一是职责清晰,答辩画架构图时一页就能讲明白;第二是避免entity直接被Controller暴露给前端,防止数据库字段泄露。很多初学者图省事,直接把Entity返回给前端,结果数据库里的deleteFlag、创建时间全被看到了,既不好看也不安全。
2.3 数据库建模:6张核心表的设计思路
数据库设计是地基,很多同学在这里翻车。我一共设计了6张核心表,外加公告等辅助表:
线路表 t_line:line_no线路编号、name线路名、start_station_name、end_station_name(冗余字段)、first_time、last_time、status运营状态。冗余首末站名称是为了查询列表不用join站点表,换来一点脏数据风险,在可接受范围内。
站点表 t_station:name、lng、lat、region区域。经纬度必须单独存,后面换乘算法和地图展示都要用。注意经纬度字段类型用decimal(10,6),不要用double,否则精度会出问题。
线路站点关联表 t_line_station:line_id、station_id、station_order、distance_to_next。station_order是灵魂字段,站点在一条线路上的先后顺序全看它。distance_to_next表示到下一站的距离,用于ETA计算。这里我建了联合唯一索引(line_id, station_order),防止重复数据。
车辆表 t_bus:plate_no车牌号、line_id所属线路、driver、status在线/离线/停运。
车辆位置表 t_bus_location:bus_id、lng、lat、speed、direction、run_status(行驶/进站/离站)、report_time。这个表是实时功能的数据源,但要特别注意写入频率,后面讲踩坑时细说。
车次排班表 t_schedule:line_id、bus_id、start_time发车时间、date_type工作日/周末。排班生成的时刻表就存在这里。
设计这6张表最容易犯的错是“一张大表搞定一切”,把所有信息塞进线路表里,用逗号分隔站点ID。这么做当时觉得省事,后面写换乘算法时简直想哭,因为把逗号串拆开再逐个查站点,SQL写得又臭又慢。按第三范式拆开,代价是多写几个join,但逻辑清晰太多,索引也能正常生效。
3. 核心功能实现:换乘算法与实时车次
3.1 换乘规划:用Dijkstra算出“少换乘又省时”的方案
换乘规划的本质是图的最短路径问题。把每个站点看作图的节点,任意两个相邻站点之间有一条边,边的权重可以是距离或行车时间。用户输入起点站和终点站后,系统在这个图上跑最短路径算法,得出乘车方案。
最小生成树用不上,这里就是单源最短路径。如果只要求“换乘次数最少”,用BFS就够了,实现简单,十几行代码。但作为毕设,我更推荐Dijkstra,理由有两个:一是它能算“总乘车时间最短”,比单纯的换乘次数少更有说服力;二是算法有足够的代码量和技术含量,答辩时可以重点讲“边的权重怎么设计”。
边的权重我分了三种情况:相邻站之间的行车时间,这是基础权重;换乘站额外加上8分钟的换乘缓冲时间,因为下车、等车、上车需要时间;同一条线路连续乘坐没有额外惩罚。这样算出来的路径,天然会避开频繁换乘的绕路方案。核心代码长这样:
public List<TransferPlan> plan(String fromStation, String toStation) { // 1. 查出所有线路站点关系,构建邻接表 adjMap<stationId, List<Edge>> // Edge: {toStationId, lineId, durationMin} // 2. Dijkstra优先队列,dist数组记录最少时间 // dist[i] = 从起点到站点i的最少分钟数 // preLine[i] = 到达i站时乘坐的线路,用于识别是否需要换乘 PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingInt(n -> n.time)); queue.offer(new Node(fromStation, 0, -1L)); while (!queue.isEmpty()) { Node cur = queue.poll(); if (visited.contains(cur.stationId)) continue; visited.add(cur.stationId); for (Edge edge : adjMap.get(cur.stationId)) { int extra = (edge.lineId == cur.lineId) ? 0 : 8; // 换乘缓冲 int newTime = cur.time + edge.durationMin + extra; if (newTime < dist[edge.toStationId]) { dist[edge.toStationId] = newTime; pre[edge.toStationId] = cur.stationId; preLine[edge.toStationId] = edge.lineId; queue.offer(new Node(edge.toStationId, newTime, edge.lineId)); } } } // 3. 回溯pre数组,把连续相同lineId的站点合并成一个乘车段 // 得到 List<Segment>,每段包含“起点站、终点站、乘坐线路号” }实际开发时有个容易出错的点:同一个物理位置的站台可能有多条线路停靠,但它们在数据库里不是同一条记录。比如“市立医院站”有1路和5路都经过,换乘时乘客其实不用走路,但如果这是两个站点记录,算法会误判为需要换乘并加8分钟惩罚。解决办法是给站点加一个group_id字段,物理位置相同的站台共享同一个group_id,算法在建图时先按group_id合并节点。这个细节很值钱,答辩时能讲出来,老师会觉得你真的做过调优。
3.2 实时位置模拟与WebSocket推送链路
毕业设计拿不到公交公司真实的GPS数据,这是客观条件限制,但完全可以用模拟器解决。我的方案是写一个定时任务,让每辆车沿着所属线路的站点顺序匀速移动:每5秒计算一次车辆当前经纬度,写入位置表,同时推送给前端。模拟器本身的代码不复杂:
@Scheduled(fixedRate = 5000) public void simulateBusMovement() { // 1. 查询所有在线车辆 List<Bus> buses = busService.listOnlineBuses(); for (Bus bus : buses) { // 2. 获取该车当前所在线路的站点序列 List<LineStation> stations = lineStationMapper.listByLineId(bus.getLineId()); // 3. 根据车辆当前进度,计算下一个目标站点 Station next = stations.get(bus.getCurrentStationIndex() + 1); // 4. 按固定速度向next移动一点点,更新经纬度 double newLng = bus.getLng() + (next.getLng() - bus.getLng()) * 0.1; double newLat = bus.getLat() + (next.getLat() - bus.getLat()) * 0.1; // 5. 写入缓存 + 推送WebSocket busLocationService.updateAndPush(bus.getId(), newLng, newLat); } }WebSocket推送的链路是:车辆位置更新后,服务端向同一线路的订阅者群发消息。前端打开线路详情页时,先通过HTTP拿到车辆静态信息,再建立WebSocket连接,之后只靠推送更新位置,不需要轮询。这样流量开销小,实时性也高,5秒刷新一次体感很流畅。
推送时要做好两件事。第一,Session要按线路分组管理,用一个ConcurrentHashMap<String, CopyOnWriteArraySet >保存,key是lineId;第二,车辆位置要先写Redis最新位置缓存,再通过WebSocket推送,不要直接写MySQL,因为5秒一次的频率对MySQL压力太大。前端收到消息后,更新地图上的车辆marker,同时调用ETA接口或直接由后端算好剩余分钟数一起推送,减少一次请求。
预计到站时间(ETA)我采用了分段估算法:拿车辆当前坐标去匹配线路上最近的站点,然后用“当前站到目标站的剩余距离除以平均车速”,再乘上1.2的交通系数作为缓冲。公式不复杂,但效果比最简单的直线距离除以速度靠谱很多,因为走的是站点间的实际道路距离。
3.3 管理端排班:自动生成全天车次时刻表
排班模块是管理端的核心。运营人员选择一条线路、一个日期类型、首班时间、末班时间,还要设置高峰和平峰两个时段的不同发车间隔,系统自动生成整天的车次时刻表。生成逻辑本质是一个循环:从首班车开始,依次累加当前时段对应的间隔分钟,直到超过末班时间。
这里我踩过一个坑:如果只用一个固定间隔,高峰期的拥挤问题没有针对性,所以我把一天切成三个时段——早高峰(7:00-9:00,间隔6分钟)、平峰(9:00-17:00,间隔12分钟)、晚高峰(17:00-19:30,间隔8分钟)。生成时判断当前时间落在哪个区间,再取对应间隔。代码大致是:
public List<LocalTime> generateSchedule(String lineId, String dateType) { LocalTime current = firstTime; LocalTime end = lastTime; List<LocalTime> schedule = new ArrayList<>(); while (!current.isAfter(end)) { schedule.add(current); int interval = getIntervalByTimeSlot(current); // 查时段表 current = current.plusMinutes(interval); } return schedule; }生成的每个时刻点就是一条车次记录。车次要不要绑定具体车辆,我建议绑定,因为后面车辆监控要展示“某辆车当前跑到哪了”,不绑定没法定位。如果车辆数量少于车次数,就在生成时给出提示“车辆不足”,让运营人员决定是否减少高峰班次。
4. 从零搭建到联调:关键实操与编码规范
4.1 初始化Maven工程与包结构
创建项目我用的是Spring Initializr,选Java 8、Spring Web、MyBatis-Plus、MySQL Driver、Redis、WebSocket依赖即可。有一点要注意:Spring Initializr现在已经默认生成SpringBoot 3.x,如果你选了它,JDK必须17以上,很多培训机构和网课用的还是JDK 8语法,老代码直接跑不了。我的建议是手动在pom.xml里把版本改回2.7.18,对应的Java版本设成1.8,稳字当头。
目录结构按前面说的包来建。controller层只做参数接收和结果返回,service层写业务逻辑,mapper层只写SQL。新手最容易犯的错是在controller里写一大串业务代码,看起来也能跑,但后面没法测、没法复用,答辩老师问两句就露馅。还有一个实用小建议:dto和vo不要混,接收前端参数的用dto,返回给前端的用vo,即使字段长得一样也分开,这是长见识的好习惯。
4.2 MyBatis-Plus配置与分页实战
MyBatis-Plus极大减少了单表CRUD样板代码。继承BaseMapper后,insert、updateById、selectById、selectPage这些方法直接用,不用自己写SQL。但同时要注意,多表联查和带复杂条件的统计SQL,MP并不擅长,这时候就老老实实写XML。比如“查询经过某站点的所有线路”,需要join线路表、站点表、关联表,我直接手写SQL放在mapper XML里,比MP的QueryWrapper更好维护。
分页是列表页的刚需,MP分页插件配置起来非常快:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置完就能用,最关键的一步是new Page<>(pageNum, pageSize)传给selectPage方法。如果查出来total值不对,多半是因为分页拦截器没注册成功,检查下MybatisPlusConfig有没有被Spring扫描到。另外,查询线路列表时,我建议默认按首班时间排序,并且只查status为1的运营中线路,不然把停运线路也展示出来,用户体验会很怪。
4.3 统一返回体与前后端联调约定
前后端分离的项目,接口规范不统一,联调就是灾难。我定义了一个Result类,所有接口统一返回这个结构:
@Data public class Result<T> { private Integer code; // 200成功 500业务失败 401未授权 private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> error(String msg) { Result<T> r = new Result<>(); r.code = 500; r.msg = msg; return r; } }前端axios响应拦截器统一判断code,不等于200就弹错误提示。这样一来,controller里只需关心业务结果,不用每个方法写重复的返回逻辑。另一个约定是接口字段名,后端Java用驼峰命名,前端JavaScript也要求用驼峰,禁止一个接口返回createTime,另一个返回created_at,这种不一致会逼疯人。
联调时我发现一个很常见的问题:后端返回null的字段,前端到处报undefined。为解决这个,我在全局配置里加了Jackson的空值处理策略,统一把null字符串输出成空字符串,集合输出成空数组,避免前端判空逻辑重复写。
5. 踩坑实录:5个高频问题与排查方法
5.1 车辆位置写库太频繁,数据库卡顿
模拟器每5秒更新一次所有在线车辆的位置,如果直接写MySQL,几十辆车跑起来后,数据库CPU直线上升。原因很简单:高频update产生大量行锁竞争和redo日志写入。解决办法是分层存储:最新位置写Redis,key为bus:loc:busId,value存JSON字符串,设置过期时间10分钟;历史轨迹按30秒一次批量插入MySQL,用于离线分析和演示。
实际操作时还要注意,读接口优先从Redis取,取不到再查MySQL。我在测试时发现Redis里出现大量过期键堆积,是因为没设置过期时间;后来统一加上TTL,内存占用立刻降下来了。
5.2 换乘算法算出绕路或死循环
第一次跑换乘算法时,我给的测试数据是“市政府站”到“高铁站”,结果方案显示绕了一个大圈。排查下来发现是两个问题:建图时没有处理站点分组,导致同站换乘被当成普通换乘;visited判断写在了循环里但只在出队时标记,导致环状线路上重复入队。修复后我用一个手工构造的小拓扑图做了单元测试:4条线路、10个站点,验证了所有两两组合,才算放心。
建议你也在开发阶段就建立这个测试习惯。不要等整个系统做完了再测算法,不然混着一堆前端问题,定位起来非常痛苦。手动构造小图的好处是答案可以手算,算法结果对不对一眼就能看出来。
5.3 地图上点位偏移,车辆跑到马路外面
用高德地图展示车辆位置时,我发现模拟的GPS经纬度和真实道路对不上,点位明显偏移。原因是坐标系不一致:GPS原始坐标是WGS-84,而高德地图用的是GCJ-02(国测局加密坐标),百度地图更是有自己的BD-09。不转换,点位肯定偏。
解决办法有两个方向:一是在模拟器生成位置时就按高德坐标生成,但这样数据不通用;二是写一个坐标转换工具类,统一把WGS-84转成GCJ-02再传给前端。毕设场景我推荐第二种,因为更贴近真实项目。转换公式网上一搜一大把,但要注意边界情况,我加上了一个“是否在国内”的简单判断,国外坐标不转换,防止地图上出现诡异跳点。
5.4 WebSocket连接频繁断开
实时功能开发完,我用Nginx做了反向代理,结果发现WebSocket每隔几十秒就自动断开重连。查了半天,原因是Nginx默认的HTTP代理不转发Upgrade头。解决方法是加三行配置:
location /ws/ { proxy_pass http://backend-server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }另外,浏览器端的WebSocket如果60秒没有消息,某些中间件会自动踢掉连接。所以前端要写一个心跳机制,每30秒发一个ping,后端收到后原样返回pong。把这两个问题解决后,连接稳定性立刻提升,连续跑两个小时也没有断线。
5.5 LocalDateTime序列化格式不对
前端向后端传时间,后端返回给前端的时间经常变成一串数字,或者格式变成“2024-05-01T10:00:00”这种带T的ISO格式。原因在于SpringBoot默认使用Jackson,对LocalDateTime的处理走的是JavaTimeModule,默认格式不符合业务习惯。统一解决方式是在application.yml里加配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8同时要给LocalDateTime字段加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),双保险。不配置的话,前端展示车次时刻表时会非常难看,还要自己做字符串截取,纯属白费劲。
| 症状 | 根因 | 解决 |
|---|---|---|
| 接口响应慢 | 高频写MySQL | Redis缓存最新位置,批量落库 |
| 换乘方案绕路 | 未合并同站台节点 | 引入group_id合并节点 |
| 地图点位偏移 | 坐标系不一致 | 统一转GCJ-02 |
| Websocket掉线 | Nginx未转发Upgrade头 | 补代理配置+心跳 |
| 时间格式带T | Jackson默认配置 | 全局配置时间格式 |
6. 答辩包装与演示防翻车经验
6.1 项目讲演的编排顺序
毕设答辩重点不是念PPT,而是用最短时间让老师听懂“你做了什么、怎么做的、有什么难点、怎么解决的”。我建议用“四段式”讲项目:
- 第一段讲痛点,30秒:用户不知道车在哪、不知道怎么换乘,你的系统解决了这个信息不对称。
- 第二段演示核心功能,2分钟:先查一条线路,看实时位置推送;再做一次换乘规划,展示两套方案及其时间、换乘次数。
- 第三段讲架构和难点,3分钟:亮出技术栈和表结构,重点讲换乘算法权重设计、WebSocket推送链路、Redis降低数据库压力这三个点。
- 第四段留一个未来展望,一句话即可:可以接入真实GPS设备、增加语音播报、结合客流做运力调度。
第四段不用展开,但一定要提,因为这是在暗示老师“你还有后续思考”,很多高分答辩就是靠这一句话定调。另外,“项目的创新点是什么”这种必问题,不要答“用了SpringBoot”这种废话,要答“把实时位置与换乘算法结合,用分组节点解决同站换乘误判”,这才是真正的差异化。
6.2 演示翻车防护清单
现场演示是翻车高发区,几乎每年都有学生因为地图API key过期、WebSocket连不上、数据库数据太少而当场尬住。我整理了一份自己用过的防护清单:
- 项目打包成jar,本地运行,不依赖云服务器,避免现场网络不通。
- 数据库导入完整演示数据:至少10条线路、200个站点、20辆车、一周的排班记录。数据太少的话,线路查询页面空空荡荡,非常尴尬。
- 给地图配置多个备用key,避免一个key超过免费配额导致白屏。
- WebSocket连不上时,准备一张实时位置的静态截图,直接切PPT展示。
- 演示前先冷启动一次项目,确认Redis、MySQL都正常启动,端口没被占用。
这些事看起来很琐碎,但全都踩过坑之后你会明白,答辩当天稳定大于惊艳。一次顺畅的演示给评委的印象,比任何华丽的架构图都管用。
6.3 代码与论文的一致性经验
最后说一个很多人忽略的点:论文里的流程图、数据库ER图必须和代码实际逻辑一致。我见过不少同学代码里明明用的是Redis缓存,论文里画的技术架构图却没有Redis;代码里字段名叫start_time,论文表格里写的“发车时间字段startTime”。这种不一致看着是小问题,但答辩老师只要仔细看一眼就能抓出来,然后怀疑整篇论文的真实性。
我在写论文前做了个小整理:把数据库每个表的字段清单照抄到论文附录,把核心接口的请求和返回示例运行一遍后截图贴进论文,把换乘算法的时间复杂度写在设计章节。这样一来,论文和代码就是同一个东西。磨刀不误砍柴工,花半天时间做这个对齐工作,省的是一答辩时被追问到哑火的险。
我个人做完这套系统的最大感受是:技术栈本身平平无奇,真正有价值的是把“实时”和“规划”这两个词落到了具体代码里。很多同学一上来就想用微服务、Elasticsearch,其实以这个业务体量来说,MySQL加Redis加WebSocket已经绰绰有余,把这些基础技术用扎实,你的完成度就已经超过90%的同类选题了。这套思路也不只适用于公交,凡是涉及“位置加线路加调度”的场景,比如景区接驳车、校园摆渡车、企业通勤班车,换个表皮就能复用。我后来做校园班车预约模块时,直接移植了这套实时位置推送和Dijkstra换乘逻辑,省了不少事。如果你正在做类似方向的毕设,先把基础CRUD做扎实,再往“实时”和“规划”两个点上深挖,六个月时间足够你交出一份能打的作品。