ARTICLE DETAIL

资讯详情

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

JSP机票预订系统源码解析:从MVC分层到Tomcat部署的完整Java Web实战

JSP机票预订系统源码解析:从MVC分层到Tomcat部署的完整Java Web实战 简介JSP机票预订系统源码包面向Java Web初学者及课程设计开发者模拟在线机票查询、预订、订单管理等核心流程直观呈现JSP、Servlet、JavaBean、DAO及MVC分层架构在真实项目中的落地方式。压缩包共1644个文件大小约38.59MB除JSP页面、Java类与配置文件外还包含大量gif/png/jpg图片、js/css前端脚本与样式、jar依赖库及SQL数据库脚本覆盖前端展示、交互逻辑与后端数据操作所需的全部组成部分。目前已有156人学习或下载适合用于课程设计参考、源码研读或项目二次开发。通过阅读源码可掌握用户认证、数据校验、会话管理、数据库增删改查等关键知识点同时学习WEB-INF目录的组织方法、表现层与业务逻辑的分离思路以及如何借助CSS、JavaScript完善界面交互体验。1. JSP 机票预订系统源代码适合课程设计也适合补 Java Web 短板这份 JSP 机票预订系统源代码zip 包解开之后是一整套基于 JSP Servlet JavaBean DAO 的经典 Java Web 项目。很多同学下载源码第一件事是点开 jsp 页面看界面长什么样我建议反过来——先看 web.xml 和 classes 目录因为这套项目的控制权根本不在页面而在 Servlet 和 Filter 手里。它模拟的是在线机票预订的核心流程注册登录、搜索航班、填写乘客信息、确认订单整体走 MVC 分层。适合两类人一是刚学完 Java Web 基础、想找一份能完整跑通请求到数据库全链路代码的初学者二是课程设计需要快速交付、又想在答辩时讲清楚架构思路的学生。这份源码的价值不在页面多精致而在于你顺着一次预订请求能看懂业务逻辑是怎么从浏览器走到数据库、再原路返回的。2. 先拆目录再碰代码WEB-INF、classes 与 MVC 的真实落点2.1 从 zip 到项目根先认清五类目录的职责拿到压缩包先别急着解压进 IDE先在文件管理器里看一眼顶层结构。这类 JSP 项目通常不是 Maven 工程没有 pom.xml它是传统的 Web 工程目录直接靠 Tomcat 识别。解开后你会发现几类东西jsp 文件夹、WEB-INF 文件夹、css/js/images 资源文件夹偶尔还有 SQL 脚本直接放在根目录。先说 jsp 文件夹这里面放的是页面文件包括首页、登录页、注册页、机票搜索页、订单确认页。它们在架构里扮演 View 的角色但你如果只盯页面会发现里面混着不少 Java 代码片段也就是常说的 scriptlet。这是早期 JSP 项目的典型写法不是最佳实践但胜在直观——每一行逻辑都能在页面上找到对应输出。再说 WEB-INF这是整个项目的核心里面至少有 web.xml、classes 和 lib 三个子目录。web.xml 是部署描述符配置 Servlet 映射、Filter 过滤器和欢迎页classes 目录放编译后的 .class 文件和包路径一一对应lib 目录放第三方 jar 包比如 MySQL 驱动。一个常见误区是以为 jsp 页面才是项目主体实际上 Servlet 和 Filter 全压在 WEB-INF 里页面只是前台。css/js/images 不用多说纯前端资源。css 控制布局和样式js 做表单校验和交互images 放 logo 和按钮图。这里我想提醒一句如果页面样式和你预期差很远优先去 css 里找而不是去 Java 代码里找——前端问题很少出在 Servlet 里。2.2 五个核心类各管哪一段Controller、Model 与 Filter 的分工压缩包里列出的类文件很有代表性它们合起来就是一个微缩版 MVC。我按自己的理解把它们分成三组你先有个全局印象后面走流程时再逐个展开。类名角色职责UserLoginServletController接收登录/注册请求调用 DAO 校验用户管理 SessionMainServletController主页面入口加载菜单和初始数据控制页面跳转CoreServletController/调度处理核心业务请求分发常见做法是统一入口再按参数路由CommonDaoModelDAO封装 JDBC 操作执行 SQL 增删改查屏蔽数据库细节User / FlightNumber / TicketInfoModelJavaBean用户、航班、票务的数据载体字段对应数据库表MenuServiceModelService菜单和业务逻辑的组织层Dao 之上的服务封装SetCharacterEncodingFilterFilter统一请求和响应的字符编码解决中文乱码注意 SetCharacterEncodingFilter 不是 Servlet它实现了 Filter 接口。它的作用是在请求进入 Servlet 之前强制把编码设为 UTF-8避免表单提交的中文变成乱码。很多项目里它被配置在 web.xml 的最前面因为 Filter 有执行顺序放错了位置就白写。CoreServlet.class 和 MainServlet.class 同时存在说明这个项目不是单 Servlet 架构而是按功能拆了多个入口。常见的做法是 MainServlet 处理主页面相关请求CoreServlet 处理机票查询、预订这类核心业务UserLoginServlet 单独管用户认证。这种拆分的好处是职责清晰坏处是 web.xml 里的 Servlet-mapping 会比较多排查问题时得先确认请求到底命中了哪个 Servlet。2.3 把 MVC 映射到这份源码JSP 不是全部Servlet 才是入口刚接触 JSP 的人容易有一个错觉项目里 .jsp 文件最多所以 JSP 是主体。实际上在这份源码里JSP 只是视图层真正的入口是 Servlet。一个典型请求的路径是浏览器提交表单 → web.xml 里的 servlet-mapping 把 URL 映射到对应的 Servlet → Servlet 调用 DAO 操作数据库 → 把结果放进 request 或 session → forward 或 redirect 到 JSP 页面 → JSP 用 EL 表达式或 Java 片段渲染数据。看一个登录请求就够了。登录表单的 action 指向一个以 .do 结尾的 URL这个 URL 在 web.xml 里被映射到 UserLoginServlet。UserLoginServlet 收到请求后先从 request 里取出用户名和密码再调 CommonDao 的查询方法核对数据库查到了就把 User 对象塞进 session然后跳转到主页没查到就返回登录页并带一个错误提示。整个过程 Controller 只做调度不写 SQLJSP 只负责显示不碰数据库连接。这就是 MVC 在这份源码里的真实落点。理解了这条链路你再打开任何一个 jsp 文件看代码就能分清楚哪些片段在做数据展示、哪些片段在做流程控制、哪些片段其实应该挪到 Servlet 里去。3. 把预订流程走通登录、查票、下单的数据流转3.1 UserLoginServlet登录逻辑与会话处理用户认证是整套系统的第一道关卡也是最容易看出代码水平的模块。入门项目的登录逻辑通常长这样从 request 拿参数拼 SQL查库比对密码跳转。但合格的写法会多考虑几件事密码字段是否为空、用户是否存在、登录成功后 Session 里放什么、Session 超时时间设多少。在 UserLoginServlet 里核心流程我一般这样组织// 典型 JSP 登录 Servlet 的核心逻辑示意写法 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); if (username null || password null || username.trim().isEmpty() || password.isEmpty()) { response.sendRedirect(login.jsp?errorempty); return; } User user commonDao.findUserByNameAndPwd(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); // 30 分钟无操作自动失效 response.sendRedirect(main.jsp); } else { response.sendRedirect(login.jsp?errorbadCredentials); } }这段代码有几个参数值得注意。request.setCharacterEncoding(UTF-8) 是保险动作虽然项目里配了 SetCharacterEncodingFilter但 Servlet 里再设一次不亏尤其在 Filter 配置有误时它能兜底。session.setMaxInactiveInterval 设的是会话超时秒数30 分钟是合理值太短用户填个表单就被踢下线太长有安全隐患。而用重定向而不是 forward 跳转是为了避免用户刷新页面时重复提交表单。这里有个新手容易踩的细节密码比对是在 Java 代码里做的还是在 SQL 里做的这份项目是典型教学风格密码直接明文存在数据库里SQL 查询时一并比对。生产环境必须用加盐哈希比如 BCrypt但作为课程设计源码这个写法能让你一眼看懂认证流程的每一步反而不算坏事。3.2 FlightNumber 与 TicketInfo航班和票务的数据模型FlightNumber 和 TicketInfo 是两个 JavaBean我习惯叫它们数据盒子。JavaBean 的规矩是私有字段 公有 getter/setter 无参构造。FlightNumber 通常对应航班表字段包括航班号、出发城市、到达城市、出发时间、到达时间、票价、余票量TicketInfo 对应订单表字段包括订单号、用户 ID、航班号、乘机人姓名、证件号、下单时间、订单状态。// FlightNumber 的典型字段设计示意 public class FlightNumber { private String flightNo; // 航班号如 CA1234 private String fromCity; // 出发城市 private String toCity; // 到达城市 private String departTime; // 起飞时间 private String arriveTime; // 到达时间 private double price; // 经济舱票价 private int remainSeats; // 余票数量 public String getFlightNo() { return flightNo; } public void setFlightNo(String flightNo) { this.flightNo flightNo; } // 其余 getter/setter 省略IDE 可以一键生成 }这段代码本身没有逻辑但它决定了上下游的接口形态。JSP 页面要读取余票量就得调用 getRemainSeats()CommonDao 要把数据库查询结果转成对象就得靠 setter 逐字段赋值。所以 JavaBean 的字段设计直接影响 DAO 层的 SQL 写法和页面的 EL 表达式取值改一个字段名三层全要跟着动。还有一个容易被忽略的细节字段类型。余票量用 int票价用 double 或 BigDecimal。用 BigDecimal 更严谨因为价格计算涉及精度double 在极端情况下会出现 0.1 0.2 ! 0.3 的经典问题。课程设计里用 double 能够顺利演示但如果你打算在这个项目上做二次开发建议把金额字段统一改成 BigDecimal。3.3 CommonDao一条 SQL 如何串起前后端CommonDao 是整个项目里最值得逐行读的类。它负责所有数据库操作是 MVC 里的 Model 层核心。典型的 CommonDao 里会有这些方法findUserByNameAndPwd()、searchFlights()、insertOrder()、updateSeatCount()。你注意观察它的命名习惯基本是动词 业务对象一眼就能看出方法在干什么。// CommonDao 中查询航班的典型 JDBC 写法示意 public ListFlightNumber searchFlights(String fromCity, String toCity) { ListFlightNumber list new ArrayList(); String sql SELECT * FROM flight WHERE from_city ? AND to_city ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, fromCity); ps.setString(2, toCity); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { FlightNumber f new FlightNumber(); f.setFlightNo(rs.getString(flight_no)); f.setFromCity(rs.getString(from_city)); f.setToCity(rs.getString(to_city)); f.setPrice(rs.getDouble(price)); f.setRemainSeats(rs.getInt(remain_seats)); list.add(f); } } } catch (SQLException e) { e.printStackTrace(); // 实际项目应换成日志输出 } return list; }这段代码里最值得说的是 PreparedStatement 的 ? 占位符。它比直接拼接 SQL 字符串安全能防 SQL 注入。初学者常见的错误是写成 SELECT * FROM flight WHERE from_city fromCity 一旦用户在搜索框输入特殊字符轻则报错重则被注入攻击。所以你在读这份源码时重点看它用的是 Statement 还是 PreparedStatement这是判断代码水平的一个简单标准。还有一处细节是 try-with-resources 写法。Connection、PreparedStatement、ResultSet 都放进 try 的括号里Java 7 之后能自动关闭资源不用手动 finally 关。如果源码里用的是老式写法你可以在二次开发时顺手重构掉这会减少大量连接泄漏问题。3.4 MenuService 与 MainServlet主页面菜单的动态渲染MenuService 这个类的存在很有意思它说明项目把菜单渲染当成了一项独立业务。很多入门项目会把菜单直接写成 JSP 里的静态 HTML但这套系统用 Service 层动态加载菜单意味着菜单数据来自数据库想加一个菜单项不用改页面改数据库就行。MainServlet 的工作方式是接收主页请求调 MenuService 拉取菜单列表和基础数据塞进 request 作用域然后 forward 到 main.jsp。JSP 页面上用 JSTL 或 EL 表达式遍历列表生成菜单。这种设计的好处在你做课程设计答辩时特别明显——老师问你的菜单是怎么来的你可以回答菜单由 MenuService 从数据库加载MainServlet 负责装配数据JSP 只做展示。这一句话就把三层职责讲清楚了。但这里也有一个容易翻车的点如果 main.jsp 里的菜单是硬编码的而 MenuService 只是空壳那这个动态菜单就是假的。你拿到源码后建议搜索一下 main.jsp 里有没有 forEach 循环或 Java for 循环确认数据到底是写死的还是从数据库读的这决定了你答辩时能不能把话说满。4. 本地部署复现Tomcat、MySQL 与 web.xml 的配置顺序4.1 环境版本怎么选JDK 8 Tomcat 8/9 MySQL 5.7 的经典组合我见过太多人在部署老项目时倒在版本上。这套 JSP 项目没有 Maven 依赖管理用的是传统 WAR 目录结构所以环境版本必须贴合项目年代。我最稳妥的建议是JDK 1.8、Tomcat 8.5 或 9.0、MySQL 5.7。这三个版本组合经过最多项目验证兼容性最好。JDK 版本尤其关键。如果你机器上装的是 JDK 17直接跑 Tomcat 8.5 会报错因为高版本 JDK 移除了部分安全模块。这时候两条路要么装一个 JDK 8 并切换要么升到 Tomcat 10 并把项目里的 javax.* 包改成 jakarta.*——后者的改动量对新手不友好。所以别较劲直接用 JDK 8 最省事。MySQL 方面5.7 是稳妥选择。8.0 也能跑但你需要注意驱动版本和连接 URL 的变化8.0 的驱动类名是 com.mysql.cj.jdbc.Driver且 URL 末尾建议加 serverTimezoneAsia/Shanghai否则时区报错会浪费你半小时。如果项目源码里的 DBUtil 用的是旧驱动名 com.mysql.jdbc.Driver在 MySQL 8.0 下会直接 ClassNotFoundException。4.2 导入数据库脚本先建库再导表zip 包里通常会带一个 .sql 文件这是整个项目能跑起来的前提。导入顺序有讲究先建库再指定库最后导表。注意不要一上来就双击执行MySQL 命令行或 Navicat 都可以关键是确认当前选中的是目标库。-- 数据库初始化按顺序执行 CREATE DATABASE IF NOT EXISTS flight_booking DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE flight_booking; -- 航班表结构示意以压缩包内 SQL 为准 CREATE TABLE IF NOT EXISTS flight ( id INT PRIMARY KEY AUTO_INCREMENT, flight_no VARCHAR(20) NOT NULL, from_city VARCHAR(50) NOT NULL, to_city VARCHAR(50) NOT NULL, depart_time DATETIME NOT NULL, arrive_time DATETIME NOT NULL, price DECIMAL(10,2) NOT NULL, remain_seats INT NOT NULL DEFAULT 60 );这里有两个参数值得你留意。第一是 DEFAULT CHARACTER SET utf8mb4它比 utf8 多了对 emoji 表情和生僻字的支持而且兼容 utf8 的所有字符直接用它就行。第二是 DECIMAL(10,2) 表示总位数 10 位、小数 2 位的定点数用它可以避免前面提到的 double 精度问题。导入完成后务必打开项目里的 DBUtil.java 或数据库配置文件核对连接串。最常见的差异是数据库名、用户名、密码对不上。一个我反复说的小技巧连接 URL 写成 jdbc:mysql://localhost:3306/flight_booking?useUnicodetruecharacterEncodingUTF-8这个写法能让你少踩两个坑——数据库乱码和连接失败。4.3 部署到 Tomcatwar 还是目录拷贝传统的 Eclipse/MyEclipse 项目会直接生成 war 包但课程设计源码经常是整个文件夹给你。部署方式取决于你手上的东西如果是解压好的项目目录直接拷贝到 Tomcat 的 webapps 目录下即可。# 假设 Tomcat 安装在 /opt/tomcat项目文件夹名为 flight_booking cp -r flight_booking /opt/tomcat/webapps/ # 启动 TomcatLinux / macOS /opt/tomcat/bin/startup.sh # 查看启动日志确认没有异常 tail -f /opt/tomcat/logs/catalina.outWindows 上是同样的逻辑把项目文件夹拷到 apache-tomcat-x.x.x\webapps 下双击 bin\startup.bat。启动后浏览器访问 http://localhost:8080/flight_booking/ 注意路径必须带项目名除非你把项目改名为 ROOT 放在 webapps 下才能直接用根路径访问。如果你在 IDE 里跑比如 Eclipse 或 IntelliJ IDEA方式略有区别IDEA 一般是通过 Artifact 打包成 war exploded 结构再部署到本地 Tomcat。但无论哪种方式底层都是把编译后的 classes、jsp 页面、web.xml 按目录结构交给 Tomcat 识别。遇到 404 先别怀疑代码先看项目名和 URL 路径是否完全匹配。4.4 web.xml 里最容易影响启动的三处配置web.xml 是传统 JSP 项目的神经中枢启动失败、路由不对、乱码多半和它有关。我挑三个高频出问题的配置点讲。Servlet 映射是第一个。每个 Servlet 必须有 servlet 和 servlet-mapping 两个标签配对前者定义类路径后者定义 URL 规律。常见的低级错误是写了 servlet 标签但忘了 servlet-mapping或者映射路径写成了 /login 但页面请求的是 login.do结果永远是 404。!-- web.xml 中的 Servlet 映射示例 -- servlet servlet-nameUserLoginServlet/servlet-name servlet-classcom.example.servlet.UserLoginServlet/servlet-class /servlet servlet-mapping servlet-nameUserLoginServlet/servlet-name url-pattern/login.do/url-pattern /servlet-mapping第二个是 Filter 配置。SetCharacterEncodingFilter 的 filter-mapping 必须放在所有需要中文处理的请求路径上且顺序要靠前。这个 filter 的配置很简单但坑在细节url-pattern 如果写 /* 就拦截所有请求如果漏写某个路径那个路径的请求就会乱码。第三个是 welcome-file 配置。打开系统时如果直接进 404不是代码有问题而是欢迎页没配对。web.xml 里的 welcome-file 决定了访问项目根路径时默认打开哪个页面一般配成 index.jsp 或 main.jsp。如果配错成 login.jsp 而登录成功后跳转又依赖 session就会出现打开系统直接跳到登录页的奇怪表现。5. 避坑记录这份 JSP 项目最常见的五个坑5.1 中文乱码Filter 顺序不对编码过滤器形同虚设现象登录页面输入中文用户名提交后数据库里存的是乱码页面显示也是问号。原因SetCharacterEncodingFilter 虽然配置了但 filter-mapping 的位置放错或者放在某些不拦截的路径上。更隐蔽的情况是 JSP 页面本身的 pageEncoding 没设 UTF-8导致页面提交时就已编码错误。解决三步走。第一步确认 web.xml 里 filter 的 url-pattern 是 /*保证拦全部请求。第二步确认每个 jsp 文件头部有 % page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%。第三步确认数据库连接 URL 带 characterEncodingUTF-8。三处都对了乱码基本绝迹。5.2 页面 404 而控制台没报错WEB-INF 下的页面不能直接访问现象把 login.jsp 放在 WEB-INF 目录下浏览器直接访问 http://localhost:8080/login.jsp 返回 404但 Tomcat 日志没有任何报错。原因WEB-INF 目录有访问保护它下面的资源对浏览器不可见只能通过 Servlet 转发访问。这是 Java Servlet 规范的安全设计不是 bug。解决用 forward 跳转而不是 sendRedirect。Servlet 里写 request.getRequestDispatcher(/WEB-INF/login.jsp).forward(request, response)把页面渲染交给服务端。如果是页面之间的正常跳转就通过 URL 访问 Servlet由 Servlet 决定渲染哪个 WEB-INF 下的 JSP。5.3 数据库连接失败驱动 jar 没进 libJDBC 直接抛 ClassNotFoundException现象项目部署后一切正常一点登录就报 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因mysql-connector-java 的 jar 包没有放进 WEB-INF/lib 目录或者放进了但 IDE 没有把它加入发布路径。解决检查 WEB-INF/lib 下有没有 mysql-connector-java-x.x.x.jar。如果没有去项目依赖目录或网上下载对应版本5.1.49 或 8.0.x拷进去后重启 Tomcat。注意重启前清理 Tomcat 的 work 目录否则可能加载不到新 jar。5.4 改了 Java 代码不生效class 没重新编译Tomcat 还在跑旧类现象在 User.java 里加了一个字段重启 Tomcat 后页面依然取不到这个值反射一看还是旧 class。原因IDE 没触发编译或者直接手动部署目录时只覆盖了 .java 文件没有编译成 .class。Tomcat 运行的是 classes 目录里的字节码不是源码。解决在 IDE 里执行 clean buildEclipse 是 Project → CleanIDEA 是 Build → Rebuild Project确认 WEB-INF/classes 下的 .class 文件时间戳已经更新。如果是手工部署先到项目根目录执行 javac 重新编译再把整个 classes 目录完整拷贝过去。血泪经验改完 Java 代码不重编译等于没改。5.5 端口被占用与 Tomcat 缓存部署半天发现跑的是旧包现象Tomcat 启动报 Port 8080 was already in use或者界面改了但访问时还是老页面。原因前一个 Tomcat 实例没关干净或者 webapps 下存在相同项目的旧目录Tomcat 解压 war 包时不会覆盖同名目录。解决端口占用时用命令查出进程并结束Windows 是 netstat -ano | findstr 8080 然后 taskkill /PID 进程号 /FLinux 是 lsof -i:8080 然后 kill。部署更新时先停 Tomcat删掉 webapps 下旧项目目录和 work/Catalina 下的缓存再拷贝新代码。Tomcat 这层缓存特别容易造成我改了代码怎么不生效的假象遇到诡异问题先清 work 目录。6. 二次开发进阶从定位页面到改功能的一小时验证法拿到源码跑通只是第一步你大概率还要改东西——课程设计加功能、面试前优化代码、或者单纯想拆开看看。我常用一套一小时验证法从改哪个文件到确认生效全程可控。先学会倒推页面定位。你在浏览器看到某个页面有问题按 F12 看请求路径或者直接看地址栏的 URL。如果 URL 以 .do 结尾说明走的是 Servlet去 web.xml 里查这个映射对应的 servlet-class然后打开那个 Servlet 看它 forward 到哪个 JSP。如果 URL 直接是 .jsp 结尾那页面文件就在 jsp 目录的对应路径下逐个找即可。这套倒推法比在 IDE 里全局搜索关键词快得多。再讲一个我的订单功能的加法。假设你要给登录用户加一个查看历史订单的页面。第一步在数据库里确认有订单表 ticket_info字段至少包含用户 ID、航班号、下单时间。第二步打开 CommonDao加一个方法 findOrdersByUserId(int userId)SQL 是按用户 ID 查订单表。第三步新建一个 OrderServlet 或者在 MainServlet 里加一个分支收到请求后调 DAO把订单列表塞进 request。第四步新建 order_list.jsp用 JSTL 的 c:forEach 遍历订单列表渲染成表格。第五步在 main.jsp 或用户菜单里加一个入口链接指向订单 Servlet 的映射路径。这五步走完功能就通了。验证时我习惯分三层第一层看 Tomcat 控制台有没有异常堆栈第二层直接访问 Servlet 的 URL看返回的页面 HTML 里有没有预期的数据第三层用 Navicat 单独执行 DAO 里的 SQL确认数据本身是存在的。如果页面没数据先查 SQL再查 JSP 的 EL 表达式最后才查 Servlet 的转发路径——这个排查顺序能帮你省大量时间。从那以后我每次改这类 JSP 源码都会强制走一遍固定动作改 Java 先编译再重启改 JSP 只刷新页面不重启改数据库先备份再执行改 web.xml 一定检查标签配对。老项目没有热部署每一步都得给足耐心。这份源码作为学习样本最大的价值就是让你在低成本的试错里把这些教训提前踩一遍希望对你有帮助。本文还有配套的精品资源点击获取
返回列表