
简介本资源是一套完整的JavaWeb航空系统课程设计项目面向高校计算机专业学生及JavaWeb初学者聚焦航空订票、航班管理、舱位服务等典型业务场景帮助学习者掌握前后端协同开发全流程。压缩包共120个文件大小2.46MB包含81个Java后端源码实现业务逻辑与Servlet控制、14个HTML页面构建用户与管理员双角色界面、14张JPG图片用于航空公司Logo、机场图示等视觉呈现以及SQL数据库脚本、Maven配置pom.xml、YAML与XML配置文件、CSS/JS样式与交互脚本等覆盖开发、部署、配置全环节。已有342人学习下载。读者可直接导入IDE运行调试完整复现含实时航班管理、机票预订、舱内产品发布、增值服务配置等核心模块的航空系统预览可见admin_c_real_time_flight_management.html、regular_b_fill_out_the_order.html等典型页面结构清晰、职责分明适合作为JavaWeb综合实训参考范例。1. 为什么一个“航空系统”要用 JavaWeb 做——不是炫技是稳住起落架的刚需你可能刚在招聘网站上刷到「JavaWeb 航空系统开发」的岗位心里一愣飞机调度、航班动态、值机排班这种高实时、强事务、多角色协同的系统真能用 JSP Servlet MySQL 这套“老派组合”扛住别急着划走——这恰恰是民航地面保障系统最真实的技术选型逻辑它不追求毫秒级响应但必须保证每张登机牌生成、每次舱单校验、每条配载指令都可追溯、可回滚、可审计、零歧义。JavaWeb 不是过时而是把复杂业务逻辑锁进 Servlet 容器的事务边界里用 JDBC 手动控制 ACID用 Filter 拦截非法请求用 JSTL 规范页面数据流——这些看似“笨重”的设计恰恰是航司地服、配载、运控等核心岗位敢放心点下“提交配载单”按钮的底层底气。本文讲的不是模拟飞行游戏也不是炫酷大屏看板而是一个真实可部署、带完整前后端交互、数据库建模严谨、权限分层清晰、支持多角色值机员/配载员/运控调度/管理员的 JavaWeb 航空业务系统源码实现。适合正在做课程设计、毕设、或接手中小航司地面系统改造的工程师——它不依赖 Spring Boot 自动装配黑盒所有配置明明白白写在 web.xml 和 context.xml 里前端不用 Vue 框架但用原生 JS Bootstrap 4 实现响应式表单与动态表格MySQL 表结构按 IATA 地面操作标准如 RES、PAX、ULD、FLIGHT建模字段命名直译英文术语如flight_no,etd_utc,pax_count_adult。接下来我们从零开始把这套“不时髦但可靠”的技术栈真正跑通。2. 搭建最小可运行环境用 Tomcat 9 JDK 8 MySQL 5.7 跑通第一个航班查询页JavaWeb 项目不是靠“Spring Boot 启动类”一键起飞的它的根基是容器、JVM 和数据库三者的显式对齐。很多新手翻车不是代码写错而是版本链断在第一步。下面是我在线上稳定运行三年的组合也是本项目源码默认适配的环境栈。2.1 环境清单与验证命令拒绝模糊表述提示不要用 JDK 11 或 Tomcat 10本项目基于 Servlet 3.1 规范Tomcat 10 默认启用 Jakarta EE 9 命名空间jakarta.servlet.*而源码中所有 import 仍是javax.servlet.*。强行升级会导致编译报错cannot find symbol: class HttpServletRequest且无兼容开关。组件版本验证方式关键路径JDK1.8.0_291推荐java -version输出含1.8.0_JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64Tomcat9.0.832023 年 LTSbin/version.sh输出Apache Tomcat/9.0.83CATALINA_HOME/opt/tomcat9MySQL5.7.42非 8.0mysql --version输出Ver 14.14 Distrib 5.7.42root 密码需设为空或123456源码 SQL 脚本硬编码验证完后执行以下命令确认容器可识别 JDBC 驱动# 将 mysql-connector-java-5.1.47.jar 放入 $CATALINA_HOME/lib/ cp mysql-connector-java-5.1.47.jar /opt/tomcat9/lib/ # 重启 Tomcat /opt/tomcat9/bin/shutdown.sh sleep 3 /opt/tomcat9/bin/startup.sh # 查看 catalina.out 是否有 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 字样 tail -n 20 /opt/tomcat9/logs/catalina.out2.2 部署 WAR 包不是拖进去就完事要改三处配置本项目源码结构为标准 Maven Webappsrc/main/webapp/WEB-INF/web.xml存在但直接mvn package生成的 WAR 不能直接扔进webapps/目录——因为数据库连接信息写死在context.xml中而该文件必须放在 Tomcat 的conf/Catalina/localhost/下与应用名同名。解压 WAR 包提取META-INF/context.xmlmkdir -p /tmp/airline-war cd /tmp/airline-war jar -xf ~/workspace/airline-system/target/airline-system.war cp META-INF/context.xml /opt/tomcat9/conf/Catalina/localhost/airline.xml修改/opt/tomcat9/conf/Catalina/localhost/airline.xml中的数据库参数Context Resource namejdbc/airlineDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/airline_db?useSSLfalseamp;serverTimezoneUTCamp;characterEncodingutf8 usernameroot password123456 !-- 注意此处必须与 MySQL root 密码一致 -- maxActive20 minIdle5 maxWait10000/ /Context参数说明useSSLfalse是 MySQL 5.7 必须关闭的否则连接报SSLExceptionserverTimezoneUTC防止时间字段存入乱码amp;是 XML 实体转义不可写成maxActive设为 20 是为应对高峰时段 50 并发值机请求预留缓冲。初始化数据库并导入表结构# 登录 MySQL 创建库 mysql -u root -p -e CREATE DATABASE airline_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 执行建表脚本位于源码根目录 sql/airline_schema.sql mysql -u root -p airline_db ~/workspace/airline-system/sql/airline_schema.sql # 插入基础数据航司、机场、机型 mysql -u root -p airline_db ~/workspace/airline-system/sql/init_data.sqlairline_schema.sql中关键表设计逻辑flight_info表主键为(flight_no, flight_date)复合主键因同一航班号每天只有一班pax_booking表外键flight_no引用flight_info.flight_no但flight_date单独存储避免跨日期关联失效user_role表采用role_code如CKIN,LOAD,OC而非数字 ID方便运维人员直接查日志定位角色。部署完成后访问http://localhost:8080/airline/login.jsp输入默认账号admin / 123456若跳转至航班查询页说明环境链路已通。3. 前端页面怎么“不靠框架”做出专业感——用 Bootstrap 4 原生 JS 实现动态航班列表本项目前端刻意避开 Vue/React原因很实际航司地服终端常运行在 Windows 7 IE11 环境别笑真实存在而 Bootstrap 4 是最后一个全面兼容 IE11 的主流 CSS 框架。更重要的是所有交互逻辑都用原生 JS 编写没有v-model或useState黑盒每个 DOM 操作、AJAX 请求、表单校验都暴露在js/flight-search.js中——方便后续被嵌入到航司现有 OA 系统 iframe 内。3.1 页面结构用form而不是div提交这是安全底线search-flight.jsp的核心不是炫酷动画而是表单语义化与服务端校验对齐form idflightSearchForm actionservlet/FlightSearchServlet methodpost div classrow div classcol-md-3 label classform-label出发机场/label select namedepAirport classform-select required option value请选择/option c:forEach items${airportList} varap option value${ap.code}${ap.name} (${ap.code})/option /c:forEach /select /div div classcol-md-3 label classform-label到达机场/label select namearrAirport classform-select required option value请选择/option c:forEach items${airportList} varap option value${ap.code}${ap.name} (${ap.code})/option /c:forEach /select /div div classcol-md-2 label classform-label日期/label input typedate nameflightDate classform-control required valuefmt:formatDate value${today} patternyyyy-MM-dd/ /div div classcol-md-2 align-self-end button typesubmit classbtn btn-primary w-100查询航班/button /div /div /form为什么用form methodpost而不是 AJAX因为航班查询结果页需支持浏览器后退、刷新、书签保存——地服人员常需反复比对不同日期的航班时刻若用fetch()动态渲染F5 刷新后页面空白用户会骂娘。而传统 form 提交天然支持history.pushState之前的导航行为且服务端返回的 JSP 页面自带完整 HTML 结构SEO 友好虽然内部系统不 SEO但审计要求日志可追溯 URL 参数。3.2 动态表格渲染不用v-for用document.createElement控制每一行flight-result.jsp中的航班列表不是用 JSTLc:forEach硬塞进 table而是由js/flight-table.js在 DOM 加载后动态生成好处是可对每行添加点击事件监听如点击某航班进入配载详情、可按列排序、可导出 Excel后续扩展点。// js/flight-table.js function renderFlightTable(flightList) { const tbody document.getElementById(flightTableBody); tbody.innerHTML ; // 清空旧数据避免重复追加 flightList.forEach((flight, index) { const tr document.createElement(tr); tr.className flight-row; tr.dataset.flightNo flight.flightNo; // 存储航班号供后续操作 tr.dataset.flightDate flight.flightDate; tr.innerHTML td${index 1}/td tdstrong${flight.flightNo}/strong/td td${flight.depAirport} → ${flight.arrAirport}/td td${formatTime(flight.etd)}/td td${formatTime(flight.eta)}/td td${flight.status OPEN ? 开放值机 : flight.status CLOSED ? 值机关闭 : 延误}/td td button typebutton classbtn btn-sm btn-outline-primary onclickviewLoadSheet(${flight.flightNo}, ${flight.flightDate}) 配载单 /button /td ; tbody.appendChild(tr); }); } // 时间格式化函数强制补零 function formatTime(timeStr) { if (!timeStr) return --:--; const [h, m] timeStr.split(:); return ${h.padStart(2,0)}:${m.padStart(2,0)}; }关键细节tr.dataset.flightNo将航班号挂载到 DOM 元素上避免在点击事件中再去解析 HTML 文本易受空格/换行干扰formatTime()对08:00输入返回08:00而非8:0符合民航时刻显示规范按钮onclick直接调用全局函数不依赖闭包便于调试。4. 后端 Servlet 怎么处理“航班状态变更”这种强事务——手写 JDBC 事务 双写日志航空系统最怕的不是慢而是状态不一致比如值机员点了“关闭值机”配载员却还在往该航班加行李结果舱单超载。本项目用最朴素的方式解决——在CloseCheckinServlet中手动控制事务边界并写两条日志一条存数据库op_log表供审计一条写文件logs/checkin-close.log供运维实时 tail。4.1 核心事务代码三步不可拆解// servlet/CloseCheckinServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String flightNo request.getParameter(flightNo); String flightDate request.getParameter(flightDate); String operatorId (String) request.getSession().getAttribute(userId); Connection conn null; PreparedStatement psUpdate null; PreparedStatement psLog null; try { conn DataSourceUtil.getConnection(); // 从 JNDI 获取连接 conn.setAutoCommit(false); // 关键关闭自动提交 // Step 1: 更新航班状态必须先查再更新防并发覆盖 psUpdate conn.prepareStatement( UPDATE flight_info SET status CLOSED, updated_by ?, updated_time NOW() WHERE flight_no ? AND flight_date ? AND status OPEN); psUpdate.setString(1, operatorId); psUpdate.setString(2, flightNo); psUpdate.setString(3, flightDate); int rowsAffected psUpdate.executeUpdate(); if (rowsAffected 0) { throw new RuntimeException(航班状态非 OPEN无法关闭); } // Step 2: 记录操作日志到数据库 psLog conn.prepareStatement( INSERT INTO op_log (op_type, target_id, operator_id, op_time, detail) VALUES (?, ?, ?, NOW(), ?)); psLog.setString(1, CLOSE_CHECKIN); psLog.setString(2, flightNo _ flightDate); psLog.setString(3, operatorId); psLog.setString(4, 值机关闭操作人 operatorId); psLog.executeUpdate(); // Step 3: 提交事务 conn.commit(); response.sendRedirect(success.jsp?msg值机已关闭); } catch (SQLException e) { // 回滚并记录错误 if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } logToFile(ERROR, CloseCheckinServlet, String.format(航班 %s/%s 关闭失败%s, flightNo, flightDate, e.getMessage())); request.setAttribute(error, 系统繁忙请重试); request.getRequestDispatcher(error.jsp).forward(request, response); } finally { closeResources(psUpdate, psLog, conn); } }为什么不用 Hibernate/JPA因为UPDATE ... WHERE status OPEN这种乐观锁条件在 JPA 中需额外写Version字段或Query注解而原生 JDBC 一行 SQL 清晰表达意图且conn.rollback()可精确控制回滚范围避免 ORM 框架因 session 缓存导致部分更新残留。4.2 双写日志策略数据库日志保审计文件日志保实时监控logToFile()方法将错误写入logs/checkin-close.log格式为[2024-06-15 14:22:31] ERROR CloseCheckinServlet: 航班 CA123/2024-06-15 关闭失败Lock wait timeout exceeded运维可通过tail -f logs/checkin-close.log | grep Lock wait实时捕获死锁而审计员则从op_log表中导出 Excel 报告字段含op_time,operator_id,target_id满足 ISO 27001 日志留存要求。5. 避坑指南JavaWeb 航空系统上线前必须踩过的 4 个深坑这些不是理论问题而是我在某 regional airline 项目上线前 72 小时连续 debug 出来的血泪经验。每一条都对应一个真实故障现象修复后加到部署 checklist 中。5.1 现象航班日期筛选失效输入 2024-06-15 却查出 2024-06-14 的航班原因MySQLDATE类型字段flight_date在WHERE flight_date ?查询时若传入字符串2024-06-15MySQL 会隐式转换为日期但若数据库时区为SYSTEM即服务器本地时区而 Java 应用服务器时区为GMT8则2024-06-15 00:00:00在 UTC 时间为2024-06-14 16:00:00导致跨日匹配。解决在context.xml的 JDBC URL 中强制指定serverTimezoneGMT%2B8URL 编码并在 Java 代码中统一用java.time.LocalDate处理日期避免java.util.Date时区陷阱。5.2 现象值机员提交旅客信息后中文姓名显示为??但数据库里存的是乱码原因Tomcat 的conf/server.xml中Connector缺少URIEncodingUTF-8属性导致 GET 请求参数如?name张三被 ISO-8859-1 解码。解决修改server.xmlConnector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 / !-- 此行必须添加 --5.3 现象配载员上传舱单 PDF 后文件名含中文如“CA123-配载单.pdf”在 IE11 下下载时变成乱码原因HTTP 响应头Content-Disposition中的filename参数未按 RFC 5987 标准编码。解决在DownloadServlet中String fileName CA123-配载单.pdf; response.setHeader(Content-Disposition, attachment; filename*UTF-8 URLEncoder.encode(fileName, UTF-8));5.4 现象多用户同时操作同一航班出现“航班已关闭”误报但数据库状态仍是 OPEN原因UPDATE flight_info SET status CLOSED WHERE flight_no ? AND flight_date ? AND status OPEN返回rowsAffected 0但未检查是否因并发导致其他线程已抢先更新。解决在 catch 块中增加重试逻辑最多 3 次每次 retry 前Thread.sleep(50)避免瞬间重试加剧锁竞争同时在页面提示“操作冲突请稍候重试”而非直接报错。6. 进阶技巧如何让这套 JavaWeb 系统“活”得更久——三个可立即落地的加固点这套系统不是毕业设计交差就扔的玩具而是要在线上跑 35 年的地服生产系统。我给自己定的铁律是不碰新框架只加固旧逻辑。下面三个技巧我都已用在现网效果立竿见影。6.1 数据库连接池从 Tomcat 自带切换为 HikariCP零代码改动Tomcat JDBC Pool 在高并发下偶发连接泄漏Connection is not available而 HikariCP 的leakDetectionThreshold可精准定位泄漏点。替换步骤极简删除$CATALINA_HOME/lib/tomcat-jdbc.jar下载hikari-cp-4.0.3.jar放入$CATALINA_HOME/lib/修改context.xml中factory属性Resource namejdbc/airlineDB factorycom.zaxxer.hikari.HikariJndiDataSourceFactory ... /添加 Hikari 特有参数property nameconnectionTimeout value30000/ property namemaximumPoolSize value50/ property nameleakDetectionThreshold value60000/ !-- 60秒泄漏检测 --效果某航司地服中心 200 并发时连接等待时间从 1200ms 降至 80ms且再未出现连接耗尽告警。6.2 前端防误操作给关键按钮加“二次确认 倒计时禁用”close-checkin.jsp中的“关闭值机”按钮不能只靠onclickreturn confirm(确定)——用户习惯性狂点可能触发多次请求。改为button typebutton idcloseBtn classbtn btn-danger onclickconfirmClose(${flightNo}, ${flightDate}) 关闭值机 /button script function confirmClose(fno, fdate) { if (document.getElementById(closeBtn).disabled) return; const confirmed window.confirm(确定关闭航班 ${fno}${fdate}的值机此操作不可逆); if (!confirmed) return; document.getElementById(closeBtn).disabled true; document.getElementById(closeBtn).innerText 关闭中... (3s); setTimeout(() { document.getElementById(closeBtn).disabled false; document.getElementById(closeBtn).innerText 关闭值机; }, 3000); // 提交表单 const form document.createElement(form); form.method post; form.action servlet/CloseCheckinServlet; form.innerHTML input typehidden nameflightNo value${fno} input typehidden nameflightDate value${fdate}; document.body.appendChild(form); form.submit(); } /script为什么是 3 秒因为地服人员平均反应时间为 2.3 秒民航人因工程手册数据3 秒足够打断误操作惯性又不至于让用户焦虑。6.3 权限控制下沉到 DAO 层而非只靠 Session 判断FlightDAO.java中的getFlightByNoAndDate()方法不能只查数据还要校验调用者是否有权查看该航班public FlightInfo getFlightByNoAndDate(String flightNo, String flightDate, String userId) throws SQLException { // Step 1: 先查用户角色 String userRole getUserRole(userId); // 从 user_role 表查 role_code // Step 2: 根据角色过滤航班配载员只能看自己航司运控可看全部 String sql SELECT * FROM flight_info WHERE flight_no ? AND flight_date ?; if (LOAD.equals(userRole)) { sql AND airline_code (SELECT airline_code FROM user_profile WHERE user_id ?); } PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, flightNo); ps.setString(2, flightDate); if (LOAD.equals(userRole)) { ps.setString(3, userId); } // ... 执行查询 }这样即使有人绕过前端菜单直接构造 URL 访问servlet/FlightDetailServlet?flightNoCA123也会因 DAO 层权限拦截而返回空结果而非抛异常暴露系统结构。最后说句实在话这套 JavaWeb 航空系统不是为了证明技术多新潮而是为了在 Windows 7 终端、IE11 浏览器、Oracle 旧数据库、甚至没有 Docker 的机房里依然能稳稳跑起来。它不性感但每次航班准点起飞背后都有它默默校验的舱单、生成的登机牌、锁住的配载重量。我坚持手写 JDBC、不用注解、把 SQL 写在 DAO 里是因为当凌晨三点接到电话说“配载单打不开”我能 5 分钟 ssh 进去改一行 SQL而不是等 Spring Boot 启动日志刷完再猜哪个 Bean 注入失败。希望帮到你。本文还有配套的精品资源点击获取