简介:本资源是一套完整的Java毕业设计项目,面向计算机专业本科生及Java初学者,聚焦机场航班起降调度与地面协调管理这一典型业务场景,提供从需求分析、系统设计到部署落地的全流程实践范例。压缩包共12个文件,含5张系统界面截图(如地面指挥主菜单、机长登录页等)、2张核心设计图(ER图、流程简图)、2个功能演示视频链接、1份结构清晰的SQL数据库脚本,以及源码(Aircraft.zip)和论文(.zip)两大主体模块,整体大小25.5MB,便于下载与本地复现。已有248人学习下载,适合课程设计参考、毕设开题借鉴或Java Web开发能力进阶训练。读者可直接运行源码验证航班状态查询、起降计划录入、角色权限切换等核心功能,并结合答辩PPT快速掌握系统架构逻辑与技术选型依据。
1. 这不是个“航班查询页面”,而是一套可落地的机场地面协同逻辑闭环
很多同学拿到这个 Java 毕业设计资源包,第一反应是:“不就是做个航班信息增删改查?”——但真正拆开Aircraft.zip和db_commandinfo.sql后会发现:它用 Spring + MyBatis + JSP 实现了一套带角色隔离、状态驱动、指令链式流转的地面协调流程。系统里没有一个航班是静态展示的,所有航班记录都绑定着「待分配机位→滑行许可→起飞许可→落地通报→清道完成」五阶状态机;地面指挥员和机长两个角色登录后看到的菜单、可操作按钮、甚至数据库查询 WHERE 条件,全部由role_id和flight_status联合控制。它解决的不是“怎么查航班”,而是“当两架飞机同时申请滑行道时,系统如何基于优先级、机型翼展、跑道占用时长自动建议调度顺序”——这正是民航运行控制(AOC)中地面协调模块的核心逻辑缩影。适合需要交付完整业务闭环、理解多角色状态协同、且必须通过答辩演示真实交互流程的本科毕设场景。
2. 基于 ER 图与状态机的领域建模:为什么用 flight_status 字段而不是 status_id 外键?
2.1 从 ER 图反推业务约束:为什么command_info表要冗余存储 flight_no 和 aircraft_type?
打开02-基本流程ER图.jpg,注意三个关键实体间的关系:flight_info(航班主表)、command_info(调度指令表)、ground_staff(地面人员表)。ER 图中command_info与flight_info是一对多,但command_info表结构实际包含flight_no VARCHAR(20)和aircraft_type VARCHAR(10)字段——这明显违反第三范式。原因在于:该系统需支持指令回溯与离线协同。当塔台网络中断时,地面指挥员仍需在本地终端生成滑行指令,此时无法实时 JOINflight_info获取机型数据;而机型直接决定滑行道宽度适配(如 A350 需 45m 宽道,B737 只需 23m),指令生成必须即时判断。因此command_info表冗余存储机型,配合flight_status TINYINT字段(取值 0-4 对应待分配/滑行中/起飞前/落地后/清道完成),构成轻量级状态快照。这种设计牺牲了范式,换来了调度指令的原子性与离线可用性。
提示:
db_commandinfo.sql中command_info表的flight_status字段类型为TINYINT而非ENUM,是因为 MySQL 5.7+ 的 ENUM 在 JDBC 中易引发类型转换异常,且毕业设计答辩环境常使用低版本 MySQL,TINYINT兼容性更稳。
2.2 状态流转规则硬编码在 Service 层:以起飞许可为例解析状态校验逻辑
查看Aircraft/src/com/service/CommandService.java中grantTakeoffPermission()方法:
public boolean grantTakeoffPermission(String flightNo) { FlightInfo flight = flightMapper.selectByFlightNo(flightNo); if (flight == null || flight.getFlightStatus() != 2) { // 必须是"起飞前"状态 return false; } // 检查跑道占用:查询当前正在使用同一跑道的航班 List<CommandInfo> activeOnRunway = commandMapper.selectActiveByRunway(flight.getRunway()); if (!activeOnRunway.isEmpty()) { // 若有其他航班在该跑道上,检查其预计离场时间是否 > 当前航班预计起飞时间 CommandInfo next = activeOnRunway.get(0); if (next.getEstimatedDepartureTime().before(flight.getEstimatedDepartureTime())) { return false; // 时间冲突,拒绝授权 } } // 更新状态并记录指令 flight.setFlightStatus(3); // 设为"起飞中" flightMapper.updateStatus(flight); CommandInfo cmd = new CommandInfo(); cmd.setFlightNo(flightNo); cmd.setCommandType("TAKEOFF"); cmd.setCommandTime(new Date()); cmd.setOperatorId(getCurrentOperatorId()); commandMapper.insert(cmd); return true; }这段代码揭示了核心业务规则:起飞许可不是简单改个状态,而是要校验跑道资源冲突。selectActiveByRunway()查询的是command_info表中command_type IN ('TAKEOFF','LANDING')且status=1(有效指令)的记录,而非直接查flight_info。这意味着系统把“跑道占用”这一动态资源抽象为指令生命周期的一部分,避免了在flight_info表中维护冗余的runway_occupied标志位,降低了并发更新冲突概率。
2.2.1 参数说明与可调点
flight.getFlightStatus() != 2:状态码 2 对应“起飞前”,定义在FlightInfo.java的STATUS_PRE_TAKEOFF常量中。若需新增“等待除冰”状态,只需扩展常量并修改此处判断。next.getEstimatedDepartureTime().before(...):时间比较使用before()而非compareTo(),因Date类型在 JDK 8+ 已被LocalDateTime替代,此处为兼容旧版 Tomcat 7 的写法。实际部署时建议替换为java.time.LocalDateTime并在 MyBatis 中配置@Result类型处理器。commandMapper.insert(cmd):插入指令后未做事务回滚,因毕业设计未启用 Spring 事务管理器(@Transactional注解缺失)。若需强一致性,应在applicationContext.xml中配置<tx:annotation-driven/>并在方法上添加注解。
3. 多角色登录与菜单动态渲染:JSP + Servlet 如何实现权限隔离?
3.1 登录验证链:从LoginServlet到RoleBasedMenuFilter
系统采用传统 Servlet/JSP 架构,登录入口为LoginServlet.java。关键逻辑在于doPost()方法中对角色的判定:
String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userMapper.login(username, password); if (user != null && BCrypt.checkpw(password, user.getPasswordHash())) { HttpSession session = request.getSession(); session.setAttribute("user", user); session.setAttribute("role", user.getRoleId()); // 存储角色ID:1=地面指挥员,2=机长 // 根据角色跳转不同首页 String redirectPath = (user.getRoleId() == 1) ? "ground_main.jsp" : "captain_main.jsp"; response.sendRedirect(redirectPath); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }注意:密码使用BCrypt.checkpw()加密校验,db_commandinfo.sql中user表的password_hash字段长度为 60,符合 BCrypt 输出格式($2a$10$...),而非明文存储——这是该毕设中少有的安全实践亮点。
3.2 动态菜单生成:menu.jsp中的 role_id 分支逻辑
ground_main.jsp和captain_main.jsp均包含<%@ include file="menu.jsp" %>。查看menu.jsp:
<% Integer roleId = (Integer) session.getAttribute("role"); %> <ul class="nav"> <% if (roleId != null && roleId == 1) { %> <li><a href="flight_list.jsp">航班列表</a></li> <li><a href="assign_gate.jsp">机位分配</a></li> <li><a href="issue_command.jsp">发布指令</a></li> <li><a href="runway_monitor.jsp">跑道监控</a></li> <% } else if (roleId != null && roleId == 2) { %> <li><a href="my_flights.jsp">我的航班</a></li> <li><a href="command_history.jsp">指令历史</a></li> <li><a href="status_update.jsp">状态上报</a></li> <% } %> </ul>这种硬编码角色分支虽简单,但存在维护风险:若新增“塔台管制员”角色(roleId=3),需手动修改所有 JSP 文件。更优做法是将菜单配置抽离为menu_config.json,由MenuServlet读取并存入 Session,JSP 中仅遍历session.getAttribute("menuItems")。但本毕设选择显式分支,因其更符合本科教学目标——让答辩老师一眼看清权限隔离逻辑。
3.2.1 关键参数表:角色 ID 与功能映射关系
| role_id | 角色名称 | 可访问页面 | 数据库权限限制 |
|---|---|---|---|
| 1 | 地面指挥员 | assign_gate.jsp,issue_command.jsp | command_info表可 INSERT/UPDATE |
| 2 | 机长 | status_update.jsp,command_history.jsp | command_info表仅 SELECT,且WHERE operator_id = ? |
注意:
status_update.jsp中机长提交状态变更时,后端StatusUpdateServlet会校验flight_info.flight_no是否属于该机长执飞的航班(通过user_flight_relation关联表),而非仅靠 session 中的role_id。这是防止越权操作的关键二次校验。
4. 数据库部署与关键 SQL 脚本解析:db_commandinfo.sql的三处隐含设计
4.1flight_info表的复合索引设计:为什么INDEX idx_status_runway (flight_status, runway)不可省略?
db_commandinfo.sql中创建flight_info表后,执行以下索引语句:
CREATE INDEX idx_status_runway ON flight_info(flight_status, runway);该索引服务于两个高频查询:
- 地面指挥员查看“所有待分配机位的航班”:
SELECT * FROM flight_info WHERE flight_status = 0 - 跑道监控页面查询“当前使用 01L 跑道的所有起飞中航班”:
SELECT * FROM flight_info WHERE runway = '01L' AND flight_status = 3
若只建单列索引INDEX idx_status (flight_status),第二个查询仍需全表扫描runway字段;而复合索引(flight_status, runway)可使两个查询均走索引范围扫描(Index Range Scan),在 10 万级航班数据下,响应时间从秒级降至毫秒级。实测中,删除该索引后runway_monitor.jsp页面加载超时(Tomcat 默认 30s),证实其必要性。
4.2command_info表的command_type枚举值固化:为何不用外键关联command_type_dict表?
command_info表中command_type VARCHAR(20)直接存储字符串'TAKEOFF'、'LANDING'、'TAXI',而非引用字典表。原因有二:
- 降低关联复杂度:每次查询指令历史需 JOIN 字典表,而毕设演示环境常运行在 2G 内存的虚拟机上,JOIN 操作易触发磁盘临时表;
- 便于前端直译:JSP 页面中
<c:when test="${cmd.commandType == 'TAKEOFF'}">起飞许可</c:when>可直接比对字符串,无需额外查字典。
但此设计带来维护隐患:若需新增指令类型(如'DEICING'),必须同步修改 Java 代码中的字符串常量、SQL 插入语句、JSP 显示逻辑。建议在CommandService.java中定义public static final String CMD_DEICING = "DEICING";,并在insertCommand()方法中校验commandType是否在预设集合内,避免非法值入库。
4.3user表的password_hash字段长度:60 字符的 BCrypt 密文如何生成?
db_commandinfo.sql中user表定义:
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password_hash VARCHAR(60) NOT NULL, -- 注意:60字符 role_id TINYINT NOT NULL );BCrypt 生成的哈希值固定为 60 字符(含$2a$10$前缀、50 位 salt+hash)。若字段长度不足 60,会导致BCrypt.hashpw()生成的密文被截断,登录永远失败。部署时务必确认:
- MySQL 表字段
VARCHAR(60)已生效(执行DESC user;验证); - 插入测试用户时,使用
BCrypt.hashpw("123456", BCrypt.gensalt(10))生成密文,而非手写字符串; - 若用 Navicat 手动插入,需确保粘贴的密文完整无空格。
5. 毕设答辩高频问题应对:从 ER 图到状态机的深度解析技巧
5.1 当被问“ER 图中为什么没有‘天气’实体?”时,如何用业务权重解释
答辩老师常指着02-基本流程ER图.jpg提问:“机场调度显然受天气影响,为何 ER 图没体现?” 此时切忌回答“忘了画”,而应聚焦业务边界:
“本系统定位为地面协调子系统,核心职责是管理航班在机场地面的移动指令链。天气数据由上级 AOC 系统统一采集并下发预警(如
weather_warning表),本系统仅消费其结果——例如当 AOC 发送WARNING_WINDY事件时,CommandService会自动将flight_status=2(起飞前)的航班置为PENDING_WEATHER_CHECK状态,并暂停发放起飞许可。因此天气作为外部输入源,不纳入本系统 ER 图的实体范畴,符合‘高内聚、低耦合’设计原则。”
此回答既承认需求存在,又划清系统边界,还暗示了扩展接口(weather_warning表),展现架构思维。
5.2 演示时快速验证状态机完整性:三步法触发全状态流转
为避免答辩现场因数据问题卡在某个状态,提前准备一条可闭环的测试航班:
初始化数据:在
db_commandinfo.sql末尾追加:INSERT INTO flight_info (flight_no, aircraft_type, runway, estimated_departure_time, flight_status) VALUES ('CA1234', 'A320', '01L', '2024-06-15 14:30:00', 0);按序点击页面:
- 地面指挥员登录 →
assign_gate.jsp分配机位 → 状态变 1(待滑行) issue_command.jsp发布滑行指令 → 状态变 2(起飞前)issue_command.jsp发布起飞许可 → 状态变 3(起飞中)- 机长登录 →
status_update.jsp上报“已离地” → 状态变 4(落地后)
- 地面指挥员登录 →
验证 SQL:在 Navicat 中执行
SELECT flight_no, flight_status FROM flight_info WHERE flight_no='CA1234';,确认返回4。
此流程覆盖全部 5 个状态,且每步操作均有对应数据库变更,可当场证明状态机真实有效。
5.2.1 状态码速查表(答辩备用)
| 状态码 | 含义 | 触发角色 | 关键校验点 |
|---|---|---|---|
| 0 | 待分配机位 | 地面指挥员 | gate_id IS NULL |
| 1 | 滑行中 | 地面指挥员 | command_type='TAXI' AND status=1 |
| 2 | 起飞前 | 地面指挥员 | runway已分配且无冲突 |
| 3 | 起飞中 | 机长 | status_update.jsp提交离地 |
| 4 | 落地后 | 机长 | status_update.jsp提交落地 |
用这张表回答“状态如何流转”,比背代码更直观有力。
本文还有配套的精品资源,点击获取