ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

城市公交查询系统JSP实战:数据库建模与换乘算法避坑指南

城市公交查询系统JSP实战:数据库建模与换乘算法避坑指南 简介城市公交查询系统毕业设计项目面向计算机相关专业学生及需要完成Web课程设计的开发者提供完整可运行的公交查询解决方案。系统分为线路查询、站点查询、公交换乘直达与一次换乘及后台管理四大模块涵盖公交数据维护、管理员登录、留言板等功能能帮助理解典型信息管理系统的分层设计与数据库交互。压缩包共119个文件约1.85MB以cs源码、aspx页面、ascx控件、SQL数据库文件及doc论文文档为主并附有开题报告、毕业论文、英文翻译等完整毕业设计材料便于对照设计与答辩准备。数据库文件可直接挂载项目附有连接配置说明公交换乘模块会依据输入站点自动判断直达或一次换乘方案源码结构清晰适合学习Web开发、毕业设计二次开发或参考论文撰写。已有575人学习下载。1. 城市公交查询系统毕业设计里最容易被低估的 JSP 实战项目要做一套城市公交查询系统技术栈是 JSP、源码、数据库再加一篇论文这个组合在毕业设计里不算新鲜但它的坑比想象中多。别把它当普通 CRUD 项目——线路表、站点表、线路站点关系表之间的关联加上换乘查询的算法选择才是这个题目的价值所在。适合想练 Java Web 全流程、又不想碰太重的框架的同学。我按实际做过的方案从数据库建模、源码工程怎么摆到换乘算法怎么写、论文怎么收尾完整讲一遍中间穿插几个典型的翻车现场。2. 公交查询系统的核心建模线路、站点与换乘怎么设计才不翻车2.1 三张核心表线路表、站点表、线路站点关系表公交查询系统的数据模型比普通增删改查复杂的地方在于线路和站点是多对多关系。一条线路经过多个站点一个站点有多条线路停靠。如果只建两张表查询“某站点有哪些线路”或者“某线路经过哪些站点”都会很别扭SQL 写起来拐弯抹角。常见的偷懒做法是只建一张大宽表字段直接拼成“线路名 站点1 站点2 ...”。小数据量时看起来没问题但一旦要查“某个站点经过哪些线路”就要把每个字段遍历一遍SQL 完全没法写更不用提换乘查询——它要把经过同一个站点的不同线路匹配出来宽表结构根本做不到。我一般的设计是下面这三张表-- 线路表 CREATE TABLE bus_line ( line_id INT NOT NULL AUTO_INCREMENT, line_name VARCHAR(50) NOT NULL COMMENT 线路名称如1路、K2路, start_station VARCHAR(50) NOT NULL COMMENT 起点站名, end_station VARCHAR(50) NOT NULL COMMENT 终点站名, first_bus_time TIME DEFAULT NULL COMMENT 首班车时间, last_bus_time TIME DEFAULT NULL COMMENT 末班车时间, price DECIMAL(3,1) DEFAULT 1.0 COMMENT 票价, PRIMARY KEY (line_id), UNIQUE KEY uk_line_name (line_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 站点表 CREATE TABLE bus_station ( station_id INT NOT NULL AUTO_INCREMENT, station_name VARCHAR(50) NOT NULL COMMENT 站点名称, longitude DECIMAL(10,6) DEFAULT NULL COMMENT 经度, latitude DECIMAL(10,6) DEFAULT NULL COMMENT 纬度, PRIMARY KEY (station_id), UNIQUE KEY uk_station_name (station_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 线路站点关系表 CREATE TABLE bus_line_station ( id INT NOT NULL AUTO_INCREMENT, line_id INT NOT NULL, station_id INT NOT NULL, station_order INT NOT NULL COMMENT 站点在线路上的顺序从1开始, PRIMARY KEY (id), UNIQUE KEY uk_line_station (line_id, station_id), KEY idx_station (station_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明线路表和站点表各保存基础属性关系表存“顺序”。这里station_order字段是整套系统的命脉——它决定了线路上站点先后顺序换乘查询里的“方向”判断全靠它。没有这个字段你只能知道某条线路经过哪些站点但不知道哪个先到哪个后到。公交车是有方向的顺序错了查询结果就是错的。参数说明DECIMAL(10,6)用于经纬度精度到小数点后 6 位约 0.1 米对公交站点定位完全够用。utf8mb4字符集是为了防止站名里出现生僻字或者特殊符号时乱码。两个UNIQUE KEY是业务上的硬约束线路名和站名在现实中就是唯一的。station_order从 1 开始不允许为 0——0 这个值在后续排序里会变成脏数据很多网上的源码包恰恰在这里翻车。2.2 换乘查询的两种算法一次换乘怎么算、二次换乘怎么兜底换乘是公交查询系统的核心卖点。用户输入起点站和终点站系统要告诉他在哪下车、换哪条线。先做一个前置判断如果起点和终点在同一条线路上那就是直达直接返回该线路的停靠站列表。这个判断很多人会漏结果直达线路也被当成换乘方案展示绕了一大圈答辩时老师一眼就能看出来。一次换乘的基础实现是起点站所在的线路集合和终点站所在的线路集合找它们的交集站点作为换乘点。这个用 SQL 反而比用 Java 写循环更直观SELECT DISTINCT ls1.line_id AS line_from, ls2.line_id AS line_to, s.station_name AS transfer_station FROM bus_line_station ls1 JOIN bus_line_station ls2 ON ls1.station_id ls2.station_id JOIN bus_station s ON ls1.station_id s.station_id WHERE ls1.line_id ? -- 起点所在线路 AND ls2.line_id ? -- 终点所在线路 AND ls1.line_id ls2.line_id逻辑说明这个自连接查询的核心思路是——把线路站点关系表自己和自己 join条件是“同站”。如果 A 线路和 B 线路都停靠某个站那这个站就是候选换乘点。line_from和line_to分别是两条线路的 idtransfer_station就是换乘站名。基础版本到这里就能跑但还有一个方向过滤的问题换乘站必须在起点的到达方向之后、在终点的到达方向之前否则会出现“往回坐”的换乘方案这个细节在第 4 章展开。参数说明两个问号分别传入起点线路和终点线路的 id。传参时要注意先查出线路 id 再传不要在 SQL 里直接拼字符串防 SQL 注入是一方面更现实的原因是站名可能重名但线路 id 不会。二次换乘换两次车用 SQL 做三表自连接也能写但可读性和性能都会崩我一般改用 Java 层 BFS 做后面单独讲。2.3 数据库选型与编码MySQL 5.7 还是 8.0字符集为什么必须 utf8mb4毕业设计环境里 MySQL 5.7 仍然是大多数学校的默认配置但如果你是从零开始我建议直接装 8.0。8.0 的窗口函数在写“每个站点的线路数量排名”这类统计时很方便而且默认字符集就是 utf8mb4能少踩一个编码坑。反过来如果你是拿学长的源码包数据库脚本可能是 5.7 的要注意排序规则差异——utf8mb4_unicode_ci和utf8mb4_general_ci对中文查询结果排序可能不一致这在做“站点线路列表”时会表现为顺序诡异。JDBC 连接串里有三个参数是血泪经验换来的jdbc:mysql://localhost:3306/bus_query?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8mb4保证 Java 程序到 MySQL 之间的编码统一serverTimezoneAsia/Shanghai是 MySQL 8.0 的硬性要求少了直接报时区错误useSSLfalse是本地开发省去证书配置。三个参数缺一个都会出问题这是接手过很多 JSP 毕业设计项目后的最小配置结论。再补一个实际遇到的坑很多网上流传的源码包里bus_line_station表没有station_order字段导致换乘逻辑根本写不出来。如果遇到这种情况用ALTER TABLE增量修改结构比删库重建安全得多ALTER TABLE bus_line_station ADD COLUMN station_order INT NOT NULL DEFAULT 0 AFTER station_id;这就是典型的mysql数据库修改结构场景。改完结构记得把已有的数据按顺序更新一遍否则默认值 0 会让所有站点排在同一位置上。3. 从源码到能跑JSP 工程的目录结构、数据库初始化与三个核心页面3.1 工程目录怎么摆经典的 Model 1 还是 Model 2 分层JSP 源码包拿到手第一件事是看目录结构。大多数毕设源码是 Eclipse 或 IDEA 的 Web 工程标准结构是这样的bus-query/ ├── src/ │ ├── com/bus/dao/ -- 数据访问层JDBC 封装 │ ├── com/bus/model/ -- 实体类Line, Station, TransferResult │ ├── com/bus/servlet/ -- ServletQueryServlet, TransferServlet │ └── com/bus/util/ -- DBUtil 连接工具类 ├── WebContent/ │ ├── index.jsp -- 首页线路查询入口 │ ├── line_detail.jsp -- 线路详情页 │ ├── station_query.jsp -- 站点查询页 │ ├── transfer.jsp -- 换乘结果页 │ ├── css/ │ └── WEB-INF/ │ ├── web.xml │ └── lib/ └── sql/ └── bus_query.sql -- 建库建表脚本这个结构是标准的 Model 2 分层JSP 做视图Servlet 做控制器DAO 做数据访问。为什么不用 Model 1直接在 JSP 里写 Java 代码因为答辩时老师一定会问“你的系统怎么分层的”。Model 2 的答案张口就来而且后期维护改一个页面不会碰坏业务逻辑。实体类放在model包里对应数据库表字段DAO 的增删改查方法返回这些实体前端 JSP 用 JSTL 遍历实体列表展示。3.2 数据库初始化脚本建库、建表、灌入演示数据拿到源码包后数据库初始化是最容易卡住的一步。很多人直接双击运行bus_query.sql结果一堆报错。正确的顺序是先建库再选库再建表最后灌数据。我的习惯是在脚本头部写清楚执行方式-- 城市公交查询系统 数据库初始化脚本 -- 执行方式mysql -u root -p bus_query.sql -- 或者在 Navicat 中新建查询粘贴本文件全部内容执行 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0; DROP DATABASE IF EXISTS bus_query; CREATE DATABASE bus_query DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bus_query; -- 建表语句见 2.1 节这里省略重复部分 -- 演示数据两条线路6 个站点 INSERT INTO bus_line (line_name, start_station, end_station) VALUES (1路, 火车站, 大学城), (2路, 汽车站, 火车站); INSERT INTO bus_station (station_name, longitude, latitude) VALUES (火车站, 120.123456, 30.123456), (大学城, 120.654321, 30.654321), (汽车站, 120.111111, 30.111111), (人民广场, 120.222222, 30.222222), (市政府, 120.333333, 30.333333), (第一医院, 120.444444, 30.444444); INSERT INTO bus_line_station (line_id, station_id, station_order) VALUES (1, 1, 1), (1, 4, 2), (1, 5, 3), (1, 2, 4), -- 1路火车站→人民广场→市政府→大学城 (2, 3, 1), (2, 4, 2), (2, 6, 3), (2, 1, 4); -- 2路汽车站→人民广场→第一医院→火车站逻辑说明演示数据的核心是让两条线路有交集——“人民广场”站被 1 路和 2 路同时经过。这样换乘查询就有真实数据可测从汽车站到大学城坐 2 路到人民广场换 1 路。没有交点你的换乘功能就是空转论文里的测试截图都没法拍。参数说明经纬度用了模拟值真实开发可以用地图开放平台的坐标拾取器抄。注意longitude在前、latitude在后和大多数地图 API 的参数顺序一致别写反了否则后面做图片坐标定位时站点会标到完全离谱的位置。脚本开头的SET FOREIGN_KEY_CHECKS0是防止外键约束导致插入顺序报错导完数据后记得改回SET FOREIGN_KEY_CHECKS1。3.3 核心查询 Servlet参数接收、JDBC 查询与页面转发查询 Servlet 是系统的入口。我习惯把所有查询请求收敛到一个QueryServlet里用action参数区分类型这样 web.xml 里只配一个映射就够了WebServlet(/query) public class QueryServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); if (line.equals(action)) { String lineName request.getParameter(lineName); // 线路查询按名称模糊匹配 ListLine lines lineDAO.findByName(lineName); request.setAttribute(lines, lines); request.getRequestDispatcher(/line_result.jsp).forward(request, response); } else if (station.equals(action)) { String stationName request.getParameter(stationName); // 站点查询查经过该站点的所有线路 ListLine lines lineDAO.findByStation(stationName); request.setAttribute(stationName, stationName); request.setAttribute(lines, lines); request.getRequestDispatcher(/station_result.jsp).forward(request, response); } else if (transfer.equals(action)) { String from request.getParameter(from); String to request.getParameter(to); // 换乘查询先查直达再查一次换乘最后 BFS 兜底 ListTransferPlan plans transferService.searchWithCache(from, to); request.setAttribute(plans, plans); request.getRequestDispatcher(/transfer_result.jsp).forward(request, response); } } }逻辑说明WebServlet(/query)注解是 Servlet 3.0 的写法不需要在 web.xml 里手动配置映射。三个分支对应三种查询全部走“接收参数 → 调 DAO → 存 request → forward 到 JSP”的流程。用forward而不是sendRedirect是为了让 request 里的数据能传到 JSP 页面同时浏览器 URL 不变用户刷新也不会重复提交表单。参数说明action参数的值是页面约定的字符串常量比如index.jsp里线路查询表单的提交地址是query?actionlinelineName...。这里有个新手爱犯的错在 Servlet 里直接new Connection()查库每来一个请求都新建连接Tomcat 跑半小时就卡死。正确做法是引入连接池或者至少在DBUtil里统一管理连接第 5 章会针对这个问题展开排查。3.4 JSP 前端页面表单提交、结果表格与图片坐标定位前端页面的核心是查询表单和结果展示。线路查询和站点查询用表格展示换乘查询用步骤化列表展示。注意表单的action地址必须带上项目上下文路径form action${pageContext.request.contextPath}/query methodget input typehidden nameaction valueline input typetext namelineName placeholder输入线路名如1路 button typesubmit查询/button /form逻辑说明${pageContext.request.contextPath}是 JSP EL 表达式动态解析出当前项目部署路径。比如部署在http://localhost:8080/bus-query/它解析为/bus-query表单提交地址就是/bus-query/query。不加这个前缀本地直接跑没问题但部署到服务器非根路径时表单请求全部 404。这是 JSP 新手最经典的翻车点之一。结果页展示可以用 JSTL 遍历% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle线路查询结果/title/head body h3线路${param.lineName}/h3 table border1 cellpadding6 tr th线路名称/thth起点/thth终点/thth首班/thth末班/thth票价/thth途经站数/th /tr c:forEach items${lines} varline tr td${line.lineName}/td td${line.startStation}/td td${line.endStation}/td td${line.firstBusTime}/td td${line.lastBusTime}/td td${line.price}/td tda hrefline_detail.jsp?lineId${line.lineId}详情/a/td /tr /c:forEach /table /body /html逻辑说明c:forEach是 JSTL 的核心标签遍历 Servlet 存入 request 的lines列表${line.lineName}调用的是Line实体类的getLineName()方法。line_detail.jsp?lineId${line.lineId}跳转详情页详情页会再查一次数据库拿到该线路所有站点的完整顺序并逐站展示。再回答一个搜索里经常出现的问题jsp 图片如何对坐标定位。如果你想在页面上放一张公交线路图并用area圈出可点击站点核心不是 JSP 而是坐标系换算。图片必须有明确的经纬度边界知道左上角和右下角的经纬度然后把站点经纬度映射成图片像素坐标。公式是像素X (站点经度 - 图片最小经度) / (图片最大经度 - 图片最小经度) * 图片显示宽度。这个换算放前端 JS 做放 JSP 里反而绕。5.5 节里会讲一个我实际踩过的坐标偏移坑。4. 换乘查询的实现细节从一次换乘 SQL 到 BFS 兜底4.1 一次换乘的 SQL 写法同站自连接与方向过滤第 2 章给了一次换乘查询的雏形。实际项目中还要加方向过滤。假设用户从起点 S 到终点 T候选换乘站是 P从 S 到 P 必须沿出发线路正方向站序递增从 P 到 T 必须沿目的线路正方向。否则会出现“先坐到终点再坐回来”的荒谬方案。完整 SQL 是这样SELECT a.line_id AS line_from, b.line_id AS line_to, s.station_name AS transfer_station, a.station_order AS order_from_transfer, b.station_order AS order_transfer_to FROM bus_line_station a JOIN bus_line_station b ON a.station_id b.station_id JOIN bus_station s ON a.station_id s.station_id WHERE a.line_id ? AND b.line_id ? AND a.line_id b.line_id AND a.station_order (SELECT station_order FROM bus_line_station WHERE line_id ? AND station_id ?) AND b.station_order (SELECT station_order FROM bus_line_station WHERE line_id ? AND station_id ?)逻辑说明两个子查询分别算出“起点站在起点线路里的顺序号”和“终点站在终点线路里的顺序号”。然后要求候选换乘站在起点站之后a.station_order 起点顺序、在终点站之前b.station_order 终点顺序。这一对条件把“往回坐”的非法方案过滤干净了。参数说明六个问号依次是起点线路 ID、终点线路 ID、起点线路 ID 起点站 ID、终点线路 ID 终点站 ID。这个 SQL 参数较长我建议把它封装在 DAO 方法里方法签名写成findOneTransfer(int fromLineId, int fromStationId, int toLineId, int toStationId)调用时传语义清晰的变量名不然答辩时自己也记不住第几个问号是干什么的。4.2 二次换乘的 BFS 兜底换乘次数是有限制的一次换乘覆盖不了所有出行需求。用户要从大学城去第一医院可能必须换两次车。SQL 三表自连接也能写但可读性差、性能也差。这个场景我在 Java 层用 BFS 做反而更清爽public ListTransferPlan bfsTransfer(String fromStation, String toStation) { // 1. 建图站点为节点线路直达关系为边 MapString, ListEdge graph buildGraph(); // 2. BFS 队列 QueueSearchNode queue new LinkedList(); SetString visited new HashSet(); queue.offer(new SearchNode(fromStation, null, 0, new ArrayList())); visited.add(fromStation); while (!queue.isEmpty()) { SearchNode node queue.poll(); if (node.station.equals(toStation)) { return buildPlan(node); // 找到终点回溯线路 } if (node.transferCount 2) continue; // 最多换乘2次 for (Edge edge : graph.getOrDefault(node.station, Collections.emptyList())) { if (!visited.contains(edge.target)) { visited.add(edge.target); queue.offer(new SearchNode(edge.target, edge.lineName, node.transferCount (edge.lineName.equals(node.currentLine) ? 0 : 1), node.path)); } } } return null; // 无可行方案 }逻辑说明BFS 天然按层扩展第一次到达终点站的路径一定是换乘次数最少的。transferCount记录当前路径换乘次数只有换到新线路时才加 1。这里的关键是Edge必须带lineName——因为站点图里有“同站换乘”的边只有跨线路时才增加换乘次数。visited集合防止环路上的死循环但要注意一个站点会出现在多条线路上直接标记站点为已访问可能把合法换乘方案也剪掉了。更稳妥的做法是记录“站点 当前线路”的组合作为访问状态。参数说明maxTransfer 2是业务阈值实际出行中换乘超过 2 次用户基本不会选算法前端裁剪掉能大幅减少搜索空间。buildGraph()方法把bus_line_station表加载到内存构建邻接表数据量在几百条线路、几千个站点时内存占用可以忽略。4.3 性能与体验结果排序、缓存策略与并发安全换乘查询的性能在毕业设计阶段不是瓶颈但有两个体验问题值得处理结果排序和重复查询。排序逻辑我一般这样定方案按换乘次数升序换乘次数相同按总站数升序总站数相同按步行距离升序。SQL 层只能满足部分排序条件BFS 方案可以在 Java 里用多级 Comparator 收尾。缓存是答辩时能拿出来讲的小亮点private static final MapString, ListTransferPlan CACHE new ConcurrentHashMap(); private static final int MAX_CACHE_SIZE 200; public ListTransferPlan searchWithCache(String from, String to) { String key from _ to; ListTransferPlan cached CACHE.get(key); if (cached ! null) return cached; ListTransferPlan result bfsTransfer(from, to); if (CACHE.size() MAX_CACHE_SIZE) { CACHE.clear(); // 简单策略满了就清空 } CACHE.put(key, result); return result; }逻辑说明ConcurrentHashMap保证多线程环境下 Servlet 并发请求不会互相踩踏。清空策略很粗暴——满了直接clear()毕设场景完全够用但要想清楚一个问题如果管理员在后台修改了线路数据缓存必须手动调用clear()否则用户会一直看到过期方案。这个“缓存失效”问题在论文里写一笔反而显得你考虑得周全。参数说明缓存 key 用from _ to注意站名里不要包含下划线否则 key 可能冲突。实际项目里我遇到过一次站名叫“城东_公交站”和另一个查询from城东、to公交站撞了同一个 key。换成分隔符#能规避这个问题。5. 避坑指南JSP 公交查询系统开发中最高频的 6 个翻车现场5.1 中文乱码JSP、Servlet、数据库三层编码不一致现象页面显示“火车站”变成“鐏溅绔”。原因JSP 请求参数、Servlet 响应、数据库存储三层编码不一致。最常见的情况是JSP 页面声明了 UTF-8但 Servlet 里没写request.setCharacterEncoding(UTF-8)Tomcat 默认用ISO-8859-1解码 GET 请求参数中文必然乱码。解决三层统一。JSP 页面顶部写pageEncodingUTF-8Servlet 的doGet和doPost开头各写request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8);同时 Tomcat 的server.xml里给 Connector 加参数URIEncodingUTF-8。三个地方改齐乱码基本消失。如果还乱检查数据库连接串是否带characterEncodingutf8mb4JDBC 层是第四层。5.2 数据库连接未关闭Tomcat 连接耗尽现象系统跑几分钟后所有查询卡住日志报Connection is not available, request timed out。原因DAO 里每个查询都new Connection用完不close()连接池被耗尽。这是 JSP 毕业设计最常见的问题没有之一。Tomcat 默认连接池上限也就几百个跑几次压力测试就满了。解决把连接获取和释放收口到DBUtil用try-with-resources或 finally 块确保关闭public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bus_query?useUnicodetruecharacterEncodingutf8mb4; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException ignored) {} if (ps ! null) try { ps.close(); } catch (SQLException ignored) {} if (conn ! null) try { conn.close(); } catch (SQLException ignored) {} } }逻辑说明close按 ResultSet → PreparedStatement → Connection 的顺序释放和创建顺序相反。JDBC 规范里外层连接关闭会自动释放内层资源但显式关一遍更保险也让查代码的人一眼能看出你没有漏关。5.3 数据库脚本导入失败字符集、版本、外键的三重坑现象执行bus_query.sql时报错或者导入后中文全部变成问号。原因一是脚本头部没有SET NAMES utf8mb4;命令行导入时客户端字符集不对二是脚本用了 MySQL 8.0 语法比如 CTE、窗口函数但本地是 5.7直接语法报错三是表之间有外键约束导数据顺序不对报Cannot add or update a child row。解决脚本开头加SET NAMES utf8mb4;和SET FOREIGN_KEY_CHECKS 0;导完数据后恢复外键检查。判断版本差异在命令行执行mysql --version看一眼如果是 5.7 就别硬跑 8.0 的脚本。很多毕设源码包里附带的数据库备份是学长从自己机器导出的带着外键依赖和自定义字符集到你机器上就按顺序崩。5.4 Servlet 路径 404注解映射与 web.xml 冲突现象页面能打开提交表单后 404URL 显示/query不存在。原因项目里 Servlet 同时用了WebServlet注解和 web.xml 中的servlet-mapping两边配置不一致或者注解路径写的是/query表单提交的路径/bus-query/query对不上还有一种情况是老项目用了 Servlet 2.4 的 web.xml 头不支持注解扫描注解形同虚设。解决统一用一种方式。如果是 Servlet 3.0Tomcat 7全部用注解把 web.xml 里的servlet和servlet-mapping删掉如果项目比较老全部用 web.xml把注解删掉。排查时先访问http://localhost:8080/项目名/query确认 Tomcat 实际部署路径再用浏览器的开发者工具看表单提交的完整 URL多数情况下是路径前缀的问题。5.5 图片坐标定位乱飞地图图片的经纬度映射偏了现象在线路图上用area标记的站点位置和实际线路图对不上点击热点区域完全错位。原因图片不是严格按经纬度比例绘制的或者图片显示尺寸被 CSS 缩放了而area的coords是写死的像素坐标。CSS 缩放后像素坐标全部失效。解决核心是两点。第一图片必须标注实际经纬度边界比如左上角对应(120.000000, 30.000000)右下角对应(120.500000, 30.500000)第二用 JS 在图片加载后按实际显示尺寸动态计算坐标不要写死function mapStationToImage(stationLng, stationLat) { var img document.getElementById(routeMap); var rect img.getBoundingClientRect(); var minLng 120.0, maxLng 120.5; var minLat 30.0, maxLat 30.5; var x (stationLng - minLng) / (maxLng - minLng) * rect.width; var y (stationLat - minLat) / (maxLat - minLat) * rect.height; return [x, y]; }这段代码计算出的像素坐标可以直接赋给area的coords也可以用来画标记覆盖层。注意图片不能被 CSS 缩放否则rect.width和图片原始像素不一致计算一样会偏。这个问题我当年调了一个下午后来发现是 CSS 里max-width:100%惹的祸。5.6 论文查重率过高代码和建表 SQL 不能整段贴现象论文查重率 30% 以上导师打回要求降重。原因很多人把源码、建表 SQL、系统截图整段贴进论文。这些内容在网上流传很广的毕设源码包里都能找到知网库里相似文本一抓一大把——你以为的“原创”实际上和别人的毕设论文高度重合。解决论文里只贴关键代码片段换乘算法、数据库连接、Servlet 分发逻辑每段不超过 15 行。数据库设计用 ER 图画出来不要贴建表 SQL 原文。功能描述用自己的话重新组织别从源码注释复制粘贴。如果导师非要完整代码放附录附录一般不计入查重范围。系统截图要截自己的真实运行画面别用源码包里的截图。6. 论文结构、答辩演示与一个值得加的亮点6.1 论文的五章框架公交查询系统的论文通常按五章走需求分析、总体设计、详细设计、系统实现、测试。重点放在需求分析里的用例图线路查询、站点查询、换乘查询三个核心用例以及详细设计里的 ER 图和换乘算法流程图。算法章节是老师最关心的部分建议把一次换乘 SQL 的方向过滤逻辑和二次换乘 BFS 的剪枝条件写透这是区别于普通 CRUD 系统的关键。6.2 答辩现场的演示顺序我会建议按“线路查询 → 站点查询 → 换乘查询”的顺序演示。线路查询最简单先过一遍建立信心站点查询展示关系和索引的用处换乘查询是技术高点放在最后压轴答辩老师大概率会追问“换乘是怎么实现的”这时候把 BFS 的思路和方向过滤讲出来就从被动回答问题变成了主动展示设计。6.3 一个加分亮点经纬度最近站点推荐如果系统还有余力可以加一个小而精的功能用户输入一个地址经纬度系统自动找到最近的公交站再走换乘逻辑。实现不复杂核心是 Haversine 距离公式public double haversine(double lat1, double lng1, double lat2, double lng2) { double r 6371.0; // 地球平均半径公里 double dLat Math.toRadians(lat2 - lat1); double dLng Math.toRadians(lng2 - lng1); double a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); return 2 * r * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); }逻辑说明Haversine 公式是球面上两点距离的标准算法返回值单位是公里。把用户坐标和所有站点坐标逐一计算距离取最近的站点作为起点复用第 4 章已有的换乘逻辑整个功能代码量不超过 50 行但在论文和答辩里的技术分量很足。我当时调试这个函数时没有把角度转弧度结果所有距离都算成天文数字折腾了一下午才发现是Math.toRadians漏了。做这个题目最大的教训是“不要一上来就写代码”。把数据库表设计好、把换乘算法的数据流画清楚后面实现就是按部就班反过来很多同学卡在换乘查询上就是因为关系表里缺了station_order数据结构不对算法怎么调都不对。希望这个思路和踩坑记录能帮到你至少让你的毕设少走一圈弯路。本文还有配套的精品资源点击获取
返回列表