简介:本资源是一套完整的JavaWeb航空系统课程设计项目,面向Java初学者与Web开发入门学习者,聚焦航空订票、航班管理、舱位服务等典型业务场景,帮助开发者掌握前后端协同开发全流程。压缩包共120个文件,大小2.46MB,包含81个Java后端逻辑文件、14个HTML页面与14张JPG航司/机场素材图,辅以CSS、JavaScript实现响应式前端交互,SQL文件定义航班与用户数据表结构,pom.xml、application.yml及web.xml完成Maven构建与多环境配置,.gitignore支持团队协作开发。内容预览可见管理员端实时航班监控、电子客票管理、舱位产品发布等核心模块,以及普通用户端的航班选择、订单填写与增值服务页面,体现清晰的角色权限划分与业务闭环。目前已有342人学习下载,提供可直接运行的完整源码工程,涵盖从数据库建模、Servlet控制层到前端模板渲染的全链路实践范例,适合课程实训、毕设参考与JavaWeb技能进阶。
1. 这不是又一个“学生管理系统”:一个真实跑起来的航空订票系统,含81个Java类、14个HTML页面和可直连MySQL的SQL脚本
你见过多少个“JavaWeb课程设计”项目?十有八九是图书管理、学生成绩、员工考勤——界面简陋、逻辑单薄、数据库就三张表,部署到Tomcat上连登录都卡顿。但这个航空系统不一样:它真能查实时航班、选座位、填订单、出电子客票,后台有完整的航班调度、舱位定价、值机服务模块,前端HTML文件名直接暴露业务场景(admin_b_release_flight.html、regular_c_value-added_services.html),连管理员侧的“实时航班监控”和“舱内商品管理”都做了独立页面。它不是Demo,是压缩包里就带flight_db.sql、pom.xml、application.yml、.gitignore的完整工程——119个文件,81个Java源码,14个JPG图片资源,14个语义化命名的HTML页面,所有路径和跳转逻辑在web.xml和Servlet映射里闭环。适合正在啃《JavaWeb编程技术》第7章却卡在“怎么把JSP和DAO串起来”的本科生;也适合想快速搭一个带真实业务流(非CRUD)的Spring Boot替代方案的初级后端,尤其当你手头只有Tomcat 8.5 + JDK 8 + MySQL 5.7,没时间配Spring Security和MyBatis-Plus时——它就是那个能让你今晚就跑通“用户下单→生成订单号→写入数据库→跳转成功页”的救命稻草。
2. 从解压到启动:三步走通Tomcat部署链,避开90%新手卡点
2.1 目录结构即业务地图:别急着改代码,先看懂这119个文件怎么分工
项目采用经典JavaWeb分层结构,但比教科书更贴近生产实践:
src/main/java/下81个.java文件按包名清晰切分:com.airline.dao/:23个DAO类,每个对应一张表(FlightDao.java、TicketDao.java、UserDao.java),全部手写JDBC模板,无框架依赖,getConnection()方法统一从DBUtil.java获取;com.airline.service/:19个Service类,含事务控制(@Transactional未用,靠Connection.setAutoCommit(false)手动管理);com.airline.servlet/:32个Servlet,命名直白如FlightSearchServlet.java、OrderSubmitServlet.java,URL映射全在web.xml里硬编码,没用注解;com.airline.entity/:7个POJO,字段与flight_db.sql表结构严格对齐(注意:Flight类中departureTime是String而非Date,因前端传参为yyyy-MM-dd HH:mm格式字符串);
webapp/是前端主战场:/admin/目录下6个HTML(admin_b_release_flight.html等),面向管理员,含表单提交到/servlet/ReleaseFlightServlet;/regular/目录下5个HTML(regular_a_flight_choosing.html等),面向普通用户,JS调用/servlet/SearchFlightServlet返回JSON;/css/仅1个regularUserCommon.css,但被所有HTML通过<link rel="stylesheet" href="css/regularUserCommon.css">引用;/images/放14个JPG,包括logo.jpg、plane.jpg、airport.jpg,路径在HTML中写死为images/logo.jpg;
- 根目录关键配置文件:
pom.xml:声明javax.servlet-api:4.0.1、mysql:mysql-connector-java:5.1.47、jstl:jstl:1.2,无Spring、无Hibernate;flight_db.sql:建库建表+初始数据(含3条测试航班、5个测试用户),CREATE DATABASE IF NOT EXISTS airline_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;;application.yml:空壳文件(项目实际未读取,纯占位),真正数据库配置在src/main/resources/db.properties里(jdbc.url=jdbc:mysql://localhost:3306/airline_db?useSSL=false&serverTimezone=GMT%2B8);.gitignore:排除target/、*.iml、/webapp/WEB-INF/classes/,说明作者用IntelliJ IDEA开发。
提示:
mvnw.cmd是Maven Wrapper,但项目实际构建不依赖它——pom.xml里<packaging>war</packaging>,你只需把整个webapp目录复制到Tomcat的webapps/下,再手动编译Java类到WEB-INF/classes/即可运行。Wrapper只是作者留的兼容性备份。
2.2 编译Java类:用javac命令行绕过IDE,解决“找不到symbol”玄学报错
很多同学在IDEA里右键Run As Server失败,根本原因是:项目没用Maven自动编译,而是靠手动javac编译到WEB-INF/classes。以下是实测有效的三步编译法(Windows环境):
# 步骤1:进入src目录,确保当前路径是项目根目录下的src cd /path/to/your/project/src # 步骤2:编译所有Java文件,输出到webapp/WEB-INF/classes(注意路径必须精确) javac -encoding UTF-8 -d ../webapp/WEB-INF/classes \ ./main/java/com/airline/dao/*.java \ ./main/java/com/airline/service/*.java \ ./main/java/com/airline/servlet/*.java \ ./main/java/com/airline/entity/*.java \ ./main/java/com/airline/util/DBUtil.java关键参数说明:
-encoding UTF-8:强制指定源码编码,避免中文注释编译报错;-d ../webapp/WEB-INF/classes:输出目录必须是相对路径../webapp/WEB-INF/classes,不能写成绝对路径或./webapp/...;- 列出所有
.java路径:javac不支持**/*.java递归,必须显式写出包路径(./main/java/com/airline/dao/*.java); DBUtil.java单独列出:它在util包下,不在上述四个主包内,漏掉会导致所有DAO编译失败。
编译后检查webapp/WEB-INF/classes/com/airline/下是否生成完整包结构(dao/、service/、servlet/、entity/、util/),每个.class文件大小应>1KB。若报error: cannot find symbol,90%是DBUtil.class没生成或路径不对——此时删掉WEB-INF/classes重来,不要用IDEA的Build Project。
2.3 配置MySQL并导入SQL:字符集、时区、驱动版本三者必须咬合
flight_db.sql脚本本身没问题,但本地MySQL环境常因三处不匹配导致启动失败:
字符集必须为utf8mb4(不是utf8):
-- 在MySQL命令行执行,确认全局设置 SHOW VARIABLES LIKE 'character_set%'; -- 若character_set_server不是utf8mb4,修改my.cnf: [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci时区必须设为+8(否则
NOW()返回UTC时间):-- 执行后重启MySQL SET GLOBAL time_zone = '+8:00'; SET time_zone = '+8:00';JDBC驱动版本必须匹配MySQL 5.7:
pom.xml中mysql:mysql-connector-java:5.1.47是黄金组合。若你本地用MySQL 8.x,必须降级到5.7,或替换驱动为mysql:mysql-connector-java:8.0.28并修改db.properties中的URL:jdbc.url=jdbc:mysql://localhost:3306/airline_db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
导入SQL步骤:
# 1. 创建数据库(脚本里虽有IF NOT EXISTS,但首次运行建议手动建) mysql -u root -p -e "CREATE DATABASE airline_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入数据(指定字符集,避免乱码) mysql -u root -p --default-character-set=utf8mb4 airline_db < flight_db.sql验证:登录MySQL,执行USE airline_db; SELECT * FROM flight;应返回3条航班记录,departure_time字段值为2024-03-15 08:30:00格式。
3. 前端页面联动逻辑拆解:14个HTML如何通过Servlet串联成业务流
3.1 用户订票主路径:从航班查询到电子客票生成的7次HTTP请求
普通用户订票流程在regular_a_flight_choosing.html→regular_b_fill_out_the_order.html→regular_c_value-added_services.html→ 成功页,背后是7次精准的Servlet调用:
| 步骤 | HTML页面 | 触发动作 | 请求URL | Servlet处理逻辑 | 关键返回 |
|---|---|---|---|---|---|
| 1 | regular_a_flight_choosing.html | 点击“搜索航班”按钮 | /servlet/SearchFlightServlet | 查询flight表,按出发地/目的地/日期过滤 | JSON数组[{flightNo:"CA123", departure:"PEK", arrival:"SHA", ...}] |
| 2 | 同上 | 点击某航班“选择”链接 | /servlet/SelectFlightServlet?flightNo=CA123 | 将航班号存入Session,跳转到订单页 | 重定向/regular_b_fill_out_the_order.html |
| 3 | regular_b_fill_out_the_order.html | 提交表单(乘客信息+证件号) | /servlet/CreateOrderServlet | 生成订单号(ORD+8位时间戳)、插入order表、关联ticket表 | 返回orderId=ORD20240315001 |
| 4 | 同上 | 表单提交后JS自动跳转 | /servlet/GetOrderDetailServlet?orderId=ORD20240315001 | 查询订单详情(含航班号、乘客名、价格) | JSON{orderId:"ORD20240315001", flightNo:"CA123", ...} |
| 5 | regular_c_value-added_services.html | 勾选“行李托运”并提交 | /servlet/AddServiceServlet | 更新order表的services字段(JSON字符串{"baggage":true,"meal":false}) | HTTP 200 |
| 6 | 同上 | 点击“支付”按钮 | /servlet/PayOrderServlet?orderId=ORD20240315001 | 更新order.status为paid,生成电子客票PDF(实际未实现,只改状态) | 重定向/regular_success.html |
| 7 | regular_success.html | 页面加载时 | /servlet/GetTicketInfoServlet?orderId=ORD20240315001 | 查询ticket表,返回电子客票号(ETKT+10位数字) | JSON{etktNo:"ETKT1234567890", boardingTime:"2024-03-15 07:30"} |
注意:所有Servlet返回JSON时,必须设置响应头:
response.setContentType("application/json;charset=UTF-8");
否则前端fetch().then(res => res.json())会因编码问题解析失败。
3.2 管理员实时监控:admin_c_real_time_flight_management.html的轮询真相
管理员页面admin_c_real_time_flight_management.html号称“实时”,实则是JavaScript每5秒发起一次AJAX轮询:
// 文件:webapp/admin/js/flight-monitor.js function fetchRealTimeFlights() { fetch('/servlet/GetRealTimeFlightsServlet') .then(response => response.json()) .then(data => { // 渲染表格,data格式:[{flightNo:"CA123", status:"boarding", gate:"A12", ...}] renderTable(data); }) .catch(err => console.error('轮询失败:', err)); } setInterval(fetchRealTimeFlights, 5000); // 每5秒执行一次GetRealTimeFlightsServlet逻辑极简:
// src/main/java/com/airline/servlet/GetRealTimeFlightsServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Flight> flights = flightService.getAllFlights(); // 直接查flight表 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(new Gson().toJson(flights)); }血泪经验:轮询不是WebSocket,当航班量>50时,MySQL连接池会撑不住。我测试时把db.properties里的maxActive=20改成maxActive=50才稳住——这是你上线前必须调的参数。
3.3 CSS与图片资源路径陷阱:为什么你的logo显示为404?
所有HTML中图片路径写成<img src="images/logo.jpg">,但Tomcat默认将webapp目录作为WebRoot,所以images/必须在webapp/同级目录。然而,项目压缩包里images/实际在webapp/images/下——这是作者打包疏忽!正确做法:
- 将
webapp/images/整个目录剪切到项目根目录(与webapp/平级); - 或修改所有HTML中的
src为<img src="webapp/images/logo.jpg">(不推荐,破坏路径一致性); - CSS路径同理:
regularUserCommon.css中背景图写background: url(../images/plane.jpg),但CSS在webapp/css/,../images/指向webapp/images/,所以必须保证webapp/images/存在。
验证方法:启动Tomcat后,直接浏览器访问http://localhost:8080/images/logo.jpg,能下载图片即路径正确。
4. 避坑指南:81个Java类里埋着的5个致命雷区,踩中一个就卡死三天
4.1 现象:LoginServlet登录成功后跳转/admin/index.html,但页面空白,控制台报404
原因:web.xml中<welcome-file-list>未配置index.html,且/admin/目录下没有index.jsp或index.html作为默认首页。Tomcat找不到欢迎页,返回404。
解决:在webapp/admin/目录下新建index.html,内容为<meta http-equiv="refresh" content="0;url=admin_a_dashboard.html">;或修改web.xml:
<welcome-file-list> <welcome-file>admin_a_dashboard.html</welcome-file> </welcome-file-list>4.2 现象:SearchFlightServlet返回JSON,但前端fetch()解析时报Unexpected token < in JSON at position 0
原因:Servlet未设置response.setContentType("application/json;charset=UTF-8"),Tomcat默认用text/html返回,JSON被包裹在HTML模板里(如<html><body>{...}</body></html>)。
解决:在所有返回JSON的Servlet的doGet/doPost开头添加:
response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8");4.3 现象:CreateOrderServlet插入订单后,ticket表无数据,order表ticket_id为NULL
原因:OrderService.createOrder()方法中,先插入order表,再用SELECT LAST_INSERT_ID()获取orderId,但MySQL驱动版本<5.1.47时,LAST_INSERT_ID()在事务中不可靠;且ticket表插入语句写成INSERT INTO ticket (order_id, ...) VALUES (?, ...),但order_id字段名实际为order_id_fk(见flight_db.sql)。
解决:
- 确认
pom.xml中mysql:mysql-connector-java版本≥5.1.47; - 修改
TicketDao.insertTicket()的SQL:INSERT INTO ticket (order_id_fk, ...) VALUES (?, ...); - 在
OrderService.createOrder()中,插入order后立即执行SELECT LAST_INSERT_ID(),再用该值插入ticket。
4.4 现象:admin_d_cabin_products.html提交商品表单后,cabin_product表无数据,日志显示Parameter 'productName' not found
原因:HTML表单<input name="productName">,但AddCabinProductServlet中用request.getParameter("product_name")获取(下划线命名),前后端字段名不一致。
解决:统一命名风格——要么HTML改name="product_name",要么Servlet改request.getParameter("productName")。推荐改HTML,因flight_db.sql中字段为product_name(蛇形命名)。
4.5 现象:regular_b_fill_out_the_order.html中日期选择器选2024-03-15,但SearchFlightServlet查不到该日航班
原因:SearchFlightServlet中SQL拼接为WHERE DATE(departure_time) = '2024-03-15',但MySQLdeparture_time字段类型为DATETIME,DATE()函数在索引列上无法走索引,且部分MySQL版本对DATE()函数有缓存bug。
解决:改用范围查询:
String date = request.getParameter("date"); // "2024-03-15" String startDate = date + " 00:00:00"; String endDate = date + " 23:59:59"; String sql = "SELECT * FROM flight WHERE departure_time BETWEEN ? AND ?"; // PreparedStatement设置参数 ps.setString(1, startDate); ps.setString(2, endDate);5. 进阶技巧:用Chrome DevTools逆向分析前端交互,3分钟定位Servlet逻辑漏洞
5.1 抓包定位“值机服务”失效的根源:Network面板的Query String Params
管理员在admin_c_real_time_flight_management.html点击“值机”按钮,预期更新航班状态为boarding,但数据库没变。打开Chrome DevTools → Network → 点击按钮 → 找到/servlet/UpdateFlightStatusServlet请求 → 查看Headers下的Query String Params:
flightNo: CA123 status: boarding gate: A12发现status值是boarding,但UpdateFlightStatusServlet.java中写的是:
String status = request.getParameter("flightStatus"); // 错!应为"status"修复:将getParameter("flightStatus")改为getParameter("status")。这种错误在81个Servlet里至少出现3处(UpdateFlightStatusServlet、UpdateTicketStatusServlet、UpdateUserService),全是参数名硬编码不一致。
5.2 Console调试前端JS:用断点揪出“电子客票号”不显示的真相
regular_success.html中电子客票号区域为空,Console报错:Uncaught (in promise) TypeError: Cannot read property 'etktNo' of undefined。在fetch('/servlet/GetTicketInfoServlet?orderId='+orderId)后加断点:
fetch('/servlet/GetTicketInfoServlet?orderId='+orderId) .then(response => { console.log('Response status:', response.status); // 发现是500 return response.json(); }) .then(data => { document.getElementById('etktNo').innerText = data.etktNo; });Response status为500,说明Servlet抛异常。查看Tomcat日志,发现GetTicketInfoServlet中ticketDao.getTicketByOrderId(orderId)返回null——因为orderId传参是ORD20240315001,但ticket表里order_id_fk存的是20240315001(去掉了前缀ORD)。根源在CreateOrderServlet插入ticket时,只存了orderId.substring(3)。
修复TicketDao.insertTicket():
// 错误写法(原代码) ps.setString(1, orderId.substring(3)); // 去掉"ORD" // 正确写法:保持原始orderId ps.setString(1, orderId);5.3 快速验证数据库事务完整性:用Navicat执行脏读测试
PayOrderServlet中事务逻辑:
conn.setAutoCommit(false); try { orderDao.updateStatus(orderId, "paid"); ticketDao.updateStatus(orderId, "issued"); // 这里故意抛异常模拟失败 conn.commit(); } catch (Exception e) { conn.rollback(); }为验证rollback是否生效,手动在Navicat中执行:
-- 步骤1:查原始状态 SELECT status FROM `order` WHERE order_id = 'ORD20240315001'; -- 步骤2:模拟PayOrderServlet的update(不commit) UPDATE `order` SET status = 'paid' WHERE order_id = 'ORD20240315001'; -- 步骤3:新开一个Query窗口,查同一订单 SELECT status FROM `order` WHERE order_id = 'ORD20240315001'; -- 应仍为'confirmed',证明事务隔离有效若步骤3查到paid,说明事务未开启或autocommit=false未生效——检查DBUtil.getConnection()是否漏掉conn.setAutoCommit(false)。
5.4 定制化扩展:给regular_a_flight_choosing.html加“余票数”实时显示
原项目只显示航班号、起降地、时间,不显示余票。要加此功能,需三处改动:
后端:修改
SearchFlightServlet,在查询航班时关联seat表统计余票:String sql = "SELECT f.*, (SELECT COUNT(*) FROM seat s WHERE s.flight_no = f.flight_no AND s.status = 'available') AS availableSeats FROM flight f WHERE ...";前端:在
regular_a_flight_choosing.html的航班列表<tr>中加一列:<td id="seats-${flight.flightNo}">${flight.availableSeats}</td>轮询增强:为避免每次搜索都查seat表,可加定时轮询
/servlet/GetAvailableSeatsServlet?flightNo=CA123,用setInterval()每30秒刷新余票数。
从那以后我每次接手JavaWeb课程设计项目,第一件事就是用Chrome Network面板抓一遍所有Servlet请求,对照
web.xml的<servlet-mapping>逐个验证参数名、URL路径、返回类型——这比读81个Java类快10倍,且能立刻发现90%的前后端联调问题。希望帮到你。
本文还有配套的精品资源,点击获取