ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL公交线路查询系统:从建表到部署排坑全解析

SpringBoot+Vue+MySQL公交线路查询系统:从建表到部署排坑全解析 期末这个时间点我收到最多的问题不是“怎么写项目”而是“老师发了一个能直接运行的公交线路查询系统源码为什么在我电脑上跑不起来”。这类项目一般长这样SpringBoot后端 Vue前端 MySQL数据库标题里写着“可直接运行”。听起来应该很简单但真正从下载到看到页面中间至少能卡出七八个让你想摔键盘的环节。这篇文章就把这套社区常见的公交线路查询系统掰开揉碎讲一遍。我会从技术选型为什么合理、数据库怎么建、后端接口怎么写、Vue页面怎么连一路讲到最后部署运行时的硬核排坑。不管你是为了课程设计交差还是真想用它做二次开发看完这篇心里都会更有底。1. 为什么SpringBootVueMySQL这套组合适合公交线路查询项目先别急着跑代码先把选型这件事想明白。很多人拿到“SpringBootVueMySQL”就照着敲却不知道为什么是这个组合。一旦理解了这个选择背后的逻辑出问题时定位速度会快很多。公交线路查询系统本质上是典型的信息管理系统一堆线路数据、站点数据需要录入、修改、删除、查询。这类系统有三件事特别重要数据之间的关系表达、接口开发效率、前端交互体验。先看数据模型。线路和站点之间是典型的多对多关系一条线路经过多个站点一个站点也有多条线路经过。MySQL这种关系型数据库天生就是干这个的两张表加一张关联表就能把关系表达得清清楚楚。如果换成文档型数据库这种多对多关系的查询反而要写不少聚合逻辑绕远路。再看后端。SpringBoot最核心的优势是“起步简单”。内置Tomcat不用单独配置服务器打成一个jar包就能跑。对应到这类管理系统前后端分离的开发方式也特别好分工后端只提供接口前端只做页面展示。相比以前老项目用JSP直接在页面里写Java代码这种模式的代码干净太多了。再说前端。Vue这种渐进式框架对中小型系统特别友好。你不需要把整个工程变成重量级应用只需要在管理后台这种页面里写组件数据绑定用起来非常顺手。一个公交线路查询页面要什么搜索框、线路列表、站点详情、增删改表单。Vue用v-model做表单、v-for渲染列表、vue-router做页面跳转几乎每一行代码都在往刀口上使。而且这三者都是国内教程最多、案例最泛滥的技术。SpringBoot的自动配置、MySQL的安装配置、Vue的环境搭建随便搜一下都能找到成片的资料。这份项目能“直接运行”恰恰是因为这套技术栈太成熟了。问题只在于你的本地环境到底跟作者预想的环境差了多少。我把这套组合和另外几个常见组合做过快速对比技术方案适合场景踩坑程度SpringBoot Vue MySQL中小型管理系统、快速开发、前后端分离中等主要是环境问题Servlet JSP MySQL老式课程设计不分离代码全糅一起入门简单后续难维护Node.js React MongoDB非关系型数据、实时交互前端门槛高数据结构不适合公交线路Django Vue SQLitePython系课程设计快速原型生态很好但跟Java项目没法直接比对所以结论很直接这套组合不是最炫酷的但胜在稳妥、适用面广、资料多。接下来进入正题第一个真正决定项目成败的地方——数据库表设计。2. 建表必须想明白线路、站点、关联关系怎么设计很多人一上来就建一张“线路表”里面用逗号分隔把所有站点塞进一个字段看着省事了后面写查询时全是灾难。公交线路查询系统的核心难点在于线路和站点之间的关联关系这部分建表没想明白后面接口写得再怎么漂亮也白搭。实际项目里最少需要三张表线路表、站点表、线路站点关联表。线路表存放线路本身的基础属性比如线路名称、始发站、终点站、首末班时间、票价。站点表存放站点信息至少要有站名最好加上经纬度方便以后做地图展示。关联表是重头戏它用来描述“某条线路经过哪些站点以及顺序是什么”。按我实际使用这个系统的习惯推荐直接把建表语句做成这样CREATE DATABASE bus_query_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE line ( id BIGINT PRIMARY KEY AUTO_INCREMENT, line_name VARCHAR(50) NOT NULL COMMENT 线路名称例如1路、K10路, start_station VARCHAR(50) NOT NULL COMMENT 始发站, end_station VARCHAR(50) NOT NULL COMMENT 终点站, first_bus_time TIME NOT NULL COMMENT 首班车时间, last_bus_time TIME NOT NULL COMMENT 末班车时间, price DECIMAL(4,2) NOT NULL DEFAULT 2.00 COMMENT 全程票价, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_line_name (line_name) ) ENGINEInnoDB COMMENT 线路表; CREATE TABLE station ( id BIGINT PRIMARY KEY AUTO_INCREMENT, station_name VARCHAR(100) NOT NULL COMMENT 站点名称, longitude DECIMAL(9,6) DEFAULT NULL COMMENT 经度, latitude DECIMAL(9,6) DEFAULT NULL COMMENT 纬度, UNIQUE KEY uk_station_name (station_name) ) ENGINEInnoDB COMMENT 站点表; CREATE TABLE line_station ( id BIGINT PRIMARY KEY AUTO_INCREMENT, line_id BIGINT NOT NULL COMMENT 线路ID, station_id BIGINT NOT NULL COMMENT 站点ID, station_order INT NOT NULL COMMENT 该站点在线路上的序号从1开始, distance_from_prev DECIMAL(10,2) DEFAULT 0 COMMENT 与上一站距离单位米第一站可填0, KEY idx_line_id (line_id), KEY idx_station_id (station_id), UNIQUE KEY uk_line_station_order (line_id, station_order) ) ENGINEInnoDB COMMENT 线路站点关联表;这里最关键的一点是关联表中有一个station_order字段。它不只是记录线路和站点的对应关系同时要表达“先到哪个站、再到哪个站”。很多人把关联表设计成只有线路ID和站点ID那能查出途经站点但顺序是乱的。公交线路又特别讲究顺序站序不同整条线路就完全变了。为什么不用JSON数组存站点因为你要做“根据站点查线路”这种反向查询也就是在站点表里点一个站查哪些线路经过这里。用JSON存的话就只能把所有线路拉出来在内存里解析匹配数据量一大人直接傻了。关系型数据库的关联表天生支持反向查询加一个索引就能高效跑到最后。另外索引也值得多说一句。line_station这个表建了idx_line_id和idx_station_id分别应对两个方向的查询按线路查站点、按站点查线路。unique key uk_line_station_order保证了“一条线路里不会出现两个相同序号的站点”防止重复数据。实际开发时很多人忽略的另一个问题就是表字符集。如果建表时用默认的latin1插入中文站名就会出现乱码。这里的utf8mb4不单是为了中文也是为以后存emoji之类的特殊字符做准备。我之前遇到过联调时数据全变成问号最后发现是初始化脚本里少了utf8mb4直接导致重做了整个数据库。3. 后端接口的“减负”思路查询链路与事务细节很多Java新手写后端接口是这样的controller里写的业务逻辑堆积如山SQL看起来也乱糟糟的。实际上这类源码项目已经替我们搭好了框架典型的分层结构是Controller负责接参返参Service负责业务逻辑Mapper负责和数据库打交道。为了不过度设计按这个方向走就够。我们要做的核心接口不外乎这么几个按关键字搜索线路、查看线路详情包含经过的站点、根据站点查线路、新增和修改线路、删除线路。其中查询接口是公交查询系统的灵魂写得好不好直接决定用户体验。先看统一返回结构。一个合理的管理系统接口习惯上会返回统一格式{ code: 0, message: success, data: {...} }。前端判断code是否为0不为0就弹错误提示。这里要注意很多项目的code值未必从0开始有的用200作为成功码前后端约定一致即可。我拿到一份源码第一件事就是看它的code约定否则联调时前端所有判断都会错乱。以一个基础接口GET /api/line/search?keywordxx为例接口要做的事是按线路名模糊匹配返回线路列表。看似简单实际写的时候有几个明显的坑要避。第一模糊搜索用LIKE要小心下划线。线路名称里经常出现类似“K10路”“机场巴士1线”“BRT-1路”这类中英混排的字符串如果你在SQL里写LIKE CONCAT(%, #{keyword}, %)用户输入下划线时会被当成通配符处理搜出来的结果完全不符合预期。解决方案是转义处理。虽然公交线路名里下划线很少见但做系统习惯性把这类问题提前堵上省得以后挨骂。第二查线路详情时不要一条线路查一次数据库循环再来一次。这是典型的N1问题。查10条线路如果在循环里每次再查一次站点最终会产生11次数据库交互。性能差还是其次关键是代码冗长。标准做法是查询时一次性把站点列表带出来用MyBatis的嵌套结果映射实现。关联查询一条SQL就搞定SELECT l.id, l.line_name, l.start_station, l.end_station, s.id AS station_id, s.station_name, s.longitude, s.latitude, ls.station_order, ls.distance_from_prev FROM line l LEFT JOIN line_station ls ON l.id ls.line_id LEFT JOIN station s ON ls.station_id s.id WHERE l.id #{lineId} ORDER BY ls.station_order;这样在Service层接收到的就是一张包含线路和站点信息的联表结果再手动组装成Line对象中嵌套List 的结构。虽然MyBatis的嵌套结果映射配置略绕但理解以后比循环查询高效太多。再讲写入操作这是很多人容易忽略的地方。新增一条线路时如果只在线路表里插一条记录却忘了往line_station表里插站点关联数据等用户点开线路详情时看到的站点列表就是空的。更糟的是如果中途插入到一半失败线路表有了数据关联表缺少数据系统就成了脏数据状态。解决方式是在Service层方法上加上Transactional事务注解让添加线路主体、批量插入关联站点这两件事在一个事务里完成要么全部成功要么全部失败回滚。看似不起眼但这种细节才是判断项目能不能真正用于“信息系统管理”的分水岭。只是做一个查询demo的话可以不加但既然项目定位是“信息管理系统”增删改的基本正确性必须保证。还有一个容易被忽略的隐患是数据库连接池配置。SpringBoot默认使用HikariCP连接池性能不错但配置不当也会出问题。一个经验值是这样的spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bus_query_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000useSSLfalse是很多连接失败的元凶。MySQL 8默认开启SSL如果客户端证书配置不对就会报Cannot connect to MySQL server之类的错误。本地开发环境直接用useSSLfalse绕开这个坑。serverTimezone如果不设置北京时间很容易差出8小时或者干脆报告时区错误。4. Vue前端从搭建到联调页面结构、路由传参和跨域处理后端接口写得再好前端联调不通也一样白搭。公交线路查询系统的前端一般做成单页应用页面数量不多靠vue-router管理几个路由完全够用。实际运行这套项目时最烦的不是写组件而是环境搭建和跨域。先看页面结构一个基础版本应该是这样首页搜索框输入线路号或站点名展示搜索结果列表线路详情页展示某条线路途径的站点列表按顺序排列站点详情页展示该站点有哪些线路经过管理页可选线路的新增、编辑、删除路由定义时最容易出错的是动态参数。比如线路详情页的路由通常写成/line/:id而页面里需要拿到这个id去请求接口。Vue3中通过useRoute()获取参数在Vue2里是this.$route.params.id。注意这里拿到的一定是字符串比如“12”而后端接口可能要求/api/line/12那没问题但如果后端接口限定参数必须为数字你就先Number(id)转换一下避免把12传过去导致类型不匹配。路由传参还有个常见误区很多人喜欢把整个线路对象放在params里传过去页面偷懒直接用。这种做法的缺陷是刷新页面后参数丢失因为params数据不会跟随URL持久化。更稳妥的做法是路由只传线路ID详情页进入后通过ID请求后端接口获取最新数据。这样刷新也不怕分享链接也能正常工作。再讲请求封装。项目前端通常封装一个request.js基于axios做拦截器。这一步的价值很直观import axios from axios; const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.response.use( response { const res response.data; if (res.code ! 0) { alert(res.message || 请求失败); return Promise.reject(new Error(res.message)); } return res.data; }, error { alert(网络异常请检查后端服务是否启动); return Promise.reject(error); } ); export default service;这样每个页面调用接口时就直接拿res.data不用到处判断成功失败UI部分也能统一提示错误。很多源码项目会把这段抽成公共模块自己写的时候最好也这样干后面维护起来省心得多。联调时有一个大头问题就是跨域。前端跑在http://localhost:5173后端跑在http://localhost:8080两个端口不一样浏览器默认是拦截这种跨域请求的。解决办法有两种后端加CrossOrigin注解或者前端配置代理。对于开发阶段我更推荐前端代理。在Vue项目根目录的vue.config.js里写module.exports { devServer: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这样的好处是前端请求地址直接写/api/line/search看起来就像同源请求浏览器不会拦截。部署到生产环境时再让Nginx做同样的事前端代码完全不用改动。如果两边都用CrossOrigin也能跑通但会产生大量与业务无关的跨域配置万一以后再接入别的客户端就会很头疼。开发联调阶段强烈建议安装Vue Devtools插件。排查Vue组件状态、路由参数、接口返回的数据结构非常有用一条数据不知道怎么来的打开Devtools看组件的data或props一眼就清楚了。很多网络热词列表里也有人问vue devtools插件下载其实在浏览器扩展商店直接搜安装就行Vue2和Vue3要选择对应版本。之前有人装错版本发现插件没反应就是这个原因。前端还有一个容易忽略的细节是加载状态和空状态。“正在查询中”“没有查到相关线路”这两种提示一定要做否则用户点击搜索后没有任何反馈会以为系统坏了。我做这类管理系统时习惯在列表区域放三个状态加载中显示一个简单的spinner空数据显示“暂无匹配线路”有数据才渲染列表。代码量增加不多体验提升却非常明显。5. 本地运行指南MySQL连接、端口冲突和环境变量检查标题里写着“可直接运行”恰恰是这个口号最容易翻车。我帮别人排查这类SpringBootVue项目90%的问题都集中在运行环境而不是代码本身。这篇就把整个运行链路上的坑按顺序排一遍。第一个坑MySQL没起来。很多人明明安装过MySQL但每次开机需要手动启动服务。Linux上报error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sockWindows上报“2003-Cant connect to MySQL server on localhost”多半就是服务没启动。先把服务启动再说其他都靠后。Mac上如果用Homebrew安装也要确认brew services list里mysql处于启动状态。第二个坑MySQL密码不对。源码项目一般会在application.yml里写死数据库账号密码最常见的就是root/123456。你的本地密码如果不一致直接改配置文件就行。spring: datasource: url: jdbc:mysql://localhost:3306/bus_query_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的实际密码改完要重新编译并重启后端。这个步骤看着简单实际操作中不少人改完配置直接刷新后端页面发现不起作用。SpringBoot改了配置文件当然要重新打包或者用spring-boot:run重启一些IDEA里还需要重新Build一下。第三个坑数据库本身还没初始化。拿到源码后需要先执行建库建表SQL把数据表建好最好再插入几条演示数据。最方便的做法是用Navicat或者MySQL Workbench直接运行SQL脚本。如果你本机还没有任何数据那就要把文章第二节里那些建表语句手工跑一遍。社区里的源码一般会带SQL脚本通常放在项目根目录的sql/文件夹里。实在找不到的话就打开后端jar包看一眼里面的文件结构很多项目会把SQL脚本打包进去。如果拿到的不是源码而是一个直接编译好的bus-query-system.jar又想知道它的配置写的是什么用记事本或解压工具打开jar包里的BOOT-INF/classes/application.yml就能看到数据库连接信息。网络上常说的“SpringBoot jar反编译成项目”其实很多时候并不需要反编译Java代码先看配置文件和结构就够排查问题了。如果确实需要把jar还原成源码工程IntelliJ IDEA自带的反编译器或者一些反编译工具也能用但还原出来的代码可读性差通常只是用来应急查逻辑。第四个坑前后端端口冲突。SpringBoot内嵌Tomcat默认端口是8080如果被其他服务占用了启动日志会直接报端口占用错误。改端口的方式是在application.yml里加server: port: 8080前端Vite默认端口通常也是5173同样可以在vue.config.js里改。我之前遇到过最尴尬的情况是后端把端口改成8081但前端代理还指向8080导致接口全部404。排查这类问题时先看后端控制台有没有正常启动再看前端Network面板里请求的实际URL是什么一步步缩小范围。第五个坑Maven/Node环境变量问题。后端要在命令行跑maven命令首先要安装JDK8或JDK17并配置好JAVA_HOME。用mvn -v检查如果提示找不到命令说明环境变量没配好。前端运行npm run dev同样需要Node环境用node -v确认版本。Vue3项目一般要求Node16以上Vue2则宽松一些太老的Node版本会导致构建失败。npm install如果太慢可以临时切换镜像源但注意不要乱想着找什么特殊途径解决正规的npm镜像加速就行。第六个坑时区和SSL配置。前面在数据库连接URL里写serverTimezoneAsia/Shanghai和useSSLfalse就是为规避这两个问题。MySQL 8如果连接串里不指定时区会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这种乱码错误。SSL问题表现更隐晦有时直接连不上有时连接成功但偶发断连。本地开发统一加这两个参数能省一夜的排查时间。把这些坑都填平后“直接运行”才真正成立后端启动看到SpringBoot的banner前端启动看到Local地址浏览器输入地址搜索一条线路站点列表按顺序展示这才是这套系统应该有的状态。6. 能用之后别急着交差换乘查询与地图展示的扩展思路很多同学拿到这个源码做完基础查询就算交差了。但如果只是按线路名搜索、查看途经站点说实话这只是一个“线路信息管理系统”离真正的“公交查询系统”还差一步。后续想让它更有竞争力有两个扩展方向我认为性价比最高。第一个是换乘查询。公交查询区别于线路查询的核心就是“从A站到B站怎么坐车”。这个功能需要把线路和站点抽象成图结构站点是节点同一线路上的相邻站点之间有一条边边的权重可以是站距也可以是预估耗时。换乘查询本质上就是图的最短路径搜索常见算法用BFS求最少换乘次数或者用Dijkstra求最短距离。整个数据量级对一个城市的公交网络来说是可控的用Java实现一个简单的图搜索完全可行。算法思路很简单先把line_station表的数据加载到内存构建一个MapLong, List相邻站点信息然后从起点站做广度优先遍历记录每一站所属的线路。当遍历到终点站时沿着记录回溯就能得到完整的换乘方案。注意换乘约束条件如果你已经坐上3路车相邻站点的优先级应该是继续留在3路上而不是鼓励你下车换乘1路。因为换乘成本高与单纯的距离权重不一样。这个细节是很多人实现换乘算法时容易忽略的。第二个是地图展示。建表时我特意留了longitude和latitude字段就是为了这一步打基础。把线路途经站点经纬度传到前端用地图引擎绘制一条折线再标注各个站点视觉上非常有说服力。前端只需在详情页引入地图组件把线路数据解析成坐标数组调用polyline方法画线即可。需要准备的数据就是把经纬度从数据库查出来按序排列这正好用到station_order字段。当然扩展成真正的城市级公交系统还需要考虑更多东西实时车辆位置、发车频率、末班车是否还有效、施工封路导致的临时绕行。这些都属于业务的深层逻辑对课程设计或者中小型项目来说先把换乘和地图做好已经足够亮眼了。最后再分享个人排查这套系统时的一点心得拿到任何SpringBootVue项目首先看数据库初始化脚本再看后端配置文件里的数据库连接和端口最后看前端代理指向的后端地址。三个点全部对齐这条链路基本就通了。我帮人排查这类“可直接运行”项目跑不起来时用的就是这个顺序成功率非常高。项目本身不算复杂但你真的动手把它跑通、改过、扩过一遍后对前后端分离、数据库设计、事务和接口联调的理解会扎实很多。
返回列表