ARTICLE DETAIL

资讯详情

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

Java毕设实战:机场航班起降协调管理系统从建表到跑通

Java毕设实战:机场航班起降协调管理系统从建表到跑通 简介这份资源是面向计算机专业毕业生与Java Web学习者的机场航班起降与协调管理系统完整毕业设计包围绕航班调度、状态管理与协调流程展开可解决选题难、代码无从下手、答辩准备不充分等问题。包内整合项目报告、答辩PPT、源代码、数据库文件、运行截图与部署视频覆盖需求分析、系统设计、编码实现、测试优化到答辩展示全流程。压缩包约84.93MB文件以源码、文档、数据库脚本、演示视频和截图为主分别用于系统运行、设计说明、数据初始化与效果验证。系统采用Java SE/Java EE技术栈结合MVC模式、Servlet与JSP并可能引入Spring、MyBatis等框架配合MySQL等关系型数据库完成航班信息与乘客数据的增删改查同时涉及Tomcat部署、前端页面构建及Spring Security权限控制等知识点。目前已有495人学习下载适合需要完整赛题方案、可运行代码、部署录屏与排错思路的读者参考也能帮助理解企业级Java Web应用的开发流程与文档组织方式。1. 机场航班起降与协调管理系统从建表到跑通一个 Java 毕设的完整落地路径航班起降与协调管理系统本质是把「航班计划、跑道占用、停机位分配、延误协调」这几件事用一套后台服务管起来。它要解决的核心问题是同一时段多架航班争抢同一条跑道或同一个机位时系统能按规则给出可执行的分配结果而不是靠对讲机喊。这套东西适合谁适合正在做计算机毕业设计、选了 Java Web 方向、手里只有一台笔记本却想做出「能演示、能答辩、能讲清逻辑」的同学。我见过太多毕设翻车在「功能堆了一堆但核心业务跑不通」——航班列表能增删改查一问跑道冲突怎么判就卡壳。这篇笔记就按我实际带过的做法把数据库设计、后端接口、冲突检测、部署演示这条链路讲透让你照着能复现答辩时被追问参数也答得上来。2. 需求拆解与技术选型为什么这套系统不该用微服务2.1 把「航班起降协调」翻译成四张核心表很多人一上来就画一堆用例图结果建表时发现字段对不上。我的习惯是先锁定业务实体再倒推表结构。这套系统真正绕不开的实体只有四个航班Flight、跑道Runway、停机位Gate、协调记录Coordination。航班是主线索它带着计划起降时间、实际起降时间、机型、所属航司跑道和停机位是稀缺资源需要被占用和释放协调记录则是每次分配动作的流水答辩时用来证明「系统真的做了决策」。下面是我一般会用的建表语句MySQL 8.0 直接跑。注意flight_no加了唯一索引因为同一航班号在同一天不该重复录入runway_id和gate_id用外键关联保证不会出现「分配了一个不存在的跑道」这种脏数据。-- 航班表核心业务主表 CREATE TABLE flight ( id BIGINT PRIMARY KEY AUTO_INCREMENT, flight_no VARCHAR(16) NOT NULL COMMENT 航班号如 CA1234, airline VARCHAR(32) NOT NULL COMMENT 所属航司, aircraft_type VARCHAR(16) COMMENT 机型如 A320, plan_departure DATETIME NOT NULL COMMENT 计划起飞, plan_arrival DATETIME NOT NULL COMMENT 计划到达, actual_departure DATETIME COMMENT 实际起飞未起飞为空, actual_arrival DATETIME COMMENT 实际到达, status TINYINT DEFAULT 0 COMMENT 0计划 1起飞 2到达 3延误 4取消, runway_id BIGINT COMMENT 占用跑道, gate_id BIGINT COMMENT 占用停机位, UNIQUE KEY uk_flight_no (flight_no, plan_departure) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 跑道表资源表记录跑道编号和状态 CREATE TABLE runway ( id BIGINT PRIMARY KEY AUTO_INCREMENT, runway_code VARCHAR(8) NOT NULL COMMENT 如 18L/36R, length_m INT COMMENT 跑道长度米, status TINYINT DEFAULT 0 COMMENT 0空闲 1占用 2维护 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 停机位表 CREATE TABLE gate ( id BIGINT PRIMARY KEY AUTO_INCREMENT, gate_code VARCHAR(8) NOT NULL COMMENT 如 A12, terminal VARCHAR(8) COMMENT 航站楼, status TINYINT DEFAULT 0 COMMENT 0空闲 1占用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 协调记录表每次分配动作留痕 CREATE TABLE coordination ( id BIGINT PRIMARY KEY AUTO_INCREMENT, flight_id BIGINT NOT NULL, resource_type VARCHAR(16) COMMENT RUNWAY 或 GATE, resource_id BIGINT, action VARCHAR(16) COMMENT ALLOCATE 或 RELEASE, operate_time DATETIME DEFAULT CURRENT_TIMESTAMP, operator VARCHAR(32) COMMENT 操作人 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建完表先别急着写代码用EXPLAIN看一眼查询计划。航班列表页最常见的查询是「按日期和状态筛选」所以我会在plan_departure和status上补一个联合索引否则数据量一上来列表就转圈。这一步很多毕设直接跳过答辩演示时数据才几十条看不出问题但老师一问「数据量大了怎么办」就露馅。2.2 技术栈选型Spring Boot MyBatis 够用别上微服务选型这块我踩过坑。有同学为了「显得高级」把系统拆成航班服务、资源服务、网关三个模块结果本地跑不起来答辩现场两个服务没启动直接翻车。毕设的评判标准是「功能完整、逻辑自洽、能演示」不是架构复杂度。我的建议是单体 Spring Boot分层清晰即可。具体版本我一般用 Spring Boot 2.7.x 配 JDK 8 或 11MyBatis-Plus 做持久层前端用 Thymeleaf 或直接 Vue Axios 都行。数据库连接池用 HikariCPSpring Boot 默认就带不用额外配。这里给一份application.yml的关键配置重点是连接池参数和 MyBatis 的驼峰映射后者不配的话flight_no映射不到flightNo查出来全是 null这个坑我见过太多次。spring: datasource: url: jdbc:mysql://localhost:3306/airport_coord?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 # 毕设并发低10 足够 minimum-idle: 2 connection-timeout: 30000 thymeleaf: cache: false # 开发期关缓存改页面不用重启 mybatis-plus: configuration: map-underscore-to-camel-case: true # 下划线转驼峰必开 global-config: db-config: id-type: auto # 主键自增参数说明serverTimezone必须显式指定否则 MySQL 8 驱动会报时区错误maximum-pool-size别设太大毕设机器内存有限10 个连接足够撑住演示map-underscore-to-camel-case是 MyBatis-Plus 的开关开了之后实体类字段用驼峰命名就能自动映射。2.3 分层结构Controller 只做参数校验业务逻辑全放 Service我见过最乱的一种写法是把冲突检测直接写在 Controller 里一个方法两百行改一处崩三处。正确的分层是Controller 接收请求、校验参数、调用 ServiceService 承载业务规则比如「这条跑道在当前时段是否可用」Mapper 只做单表 CRUD。下面是一个 Service 层的骨架展示航班分配跑道时的判断入口。Service public class CoordinationService { Autowired private FlightMapper flightMapper; Autowired private RunwayMapper runwayMapper; Autowired private CoordinationMapper coordinationMapper; /** * 为航班分配跑道 * param flightId 航班ID * param runwayId 跑道ID * return 分配结果 */ Transactional(rollbackFor Exception.class) public Result allocateRunway(Long flightId, Long runwayId) { Flight flight flightMapper.selectById(flightId); if (flight null) { return Result.fail(航班不存在); } // 核心检查该跑道在航班时段内是否已被占用 int conflict coordinationMapper.countConflict( runwayId, flight.getPlanDeparture(), flight.getPlanArrival()); if (conflict 0) { return Result.fail(该跑道在此时段已被占用); } flight.setRunwayId(runwayId); flightMapper.updateById(flight); coordinationMapper.insert(new Coordination(flightId, RUNWAY, runwayId, ALLOCATE)); return Result.success(分配成功); } }逻辑说明Transactional保证「更新航班 写协调记录」要么都成功要么都回滚否则会出现航班占了跑道但没留痕的情况。countConflict是自定义 SQL用时间区间重叠判断下一章会展开。参数上flightId和runwayId都做了非空校验省略了实际要加避免空指针。3. 冲突检测与资源分配系统真正的技术含量在这里3.1 时间区间重叠判断一条 SQL 解决跑道争抢跑道冲突的本质是「两个航班的时间段有交集」。假设航班 A 计划 10:00 到 10:30 占用跑道航班 B 计划 10:15 到 10:45那它们重叠了 15 分钟不能同时分配。判断两个区间[s1, e1]和[s2, e2]是否重叠条件是s1 e2 AND s2 e1。这个公式看着简单但边界条件容易写错——如果 A 的结束时间正好等于 B 的开始时间算不算冲突我的做法是算不冲突因为跑道交接需要缓冲实际系统里会留 5 到 10 分钟间隔但毕设演示用严格小于即可。!-- CoordinationMapper.xml 中的冲突统计 -- select idcountConflict resultTypeint SELECT COUNT(*) FROM coordination c JOIN flight f ON c.flight_id f.id WHERE c.resource_type RUNWAY AND c.resource_id #{runwayId} AND c.action ALLOCATE AND f.status NOT IN (4) !-- 取消的航班不占资源 -- AND #{start} lt; f.plan_arrival !-- 新区间开始 旧区间结束 -- AND f.plan_departure lt; #{end} !-- 旧区间开始 新区间结束 -- /select参数说明#{runwayId}是待分配的跑道#{start}和#{end}是新航班的时间段。注意 XML 里要写成lt;这是 MyBatis 的转义要求不写会解析报错。status NOT IN (4)排除了已取消航班否则取消的航班会一直占着跑道这是血泪经验——我第一版没加这个条件测试时取消了一个航班结果那条跑道再也分配不出去了。3.2 停机位分配按航站楼就近原则做简单贪心停机位比跑道多一层约束航班有航站楼偏好旅客下机后要走最短路径。毕设不用做复杂的图论优化一个贪心策略就够优先分配同航站楼的空闲机位没有就分配任意空闲机位并给出提示。下面是对应的 Service 方法。public Result allocateGate(Long flightId) { Flight flight flightMapper.selectById(flightId); // 查询该航班时段内所有被占用的机位ID ListLong occupied coordinationMapper.listOccupiedGates( flight.getPlanDeparture(), flight.getPlanArrival()); // 查询所有空闲机位按航站楼匹配度排序 ListGate candidates gateMapper.selectList( new QueryWrapperGate().notIn(!occupied.isEmpty(), id, occupied)); if (candidates.isEmpty()) { return Result.fail(当前时段无空闲机位); } // 贪心优先选同航站楼 Gate best candidates.stream() .filter(g - g.getTerminal().equals(flight.getAirlineTerminal())) .findFirst() .orElse(candidates.get(0)); flight.setGateId(best.getId()); flightMapper.updateById(flight); coordinationMapper.insert(new Coordination(flightId, GATE, best.getId(), ALLOCATE)); return Result.success(分配机位 best.getGateCode()); }逻辑说明listOccupiedGates复用了时间重叠逻辑只是把资源类型换成 GATE。notIn的条件加了!occupied.isEmpty()判断因为 MyBatis-Plus 的notIn传空集合会生成NOT IN ()导致 SQL 语法错误这个坑很隐蔽。贪心部分用 Stream 先筛同航站楼找不到就退而求其次逻辑简单但演示效果够用。3.3 延误协调状态机驱动别用一堆 if-else航班延误后的协调是毕设的加分项。很多同学用一堆if (status 延误) {...}堆在 Service 里改起来痛苦。我的做法是用状态机思路定义航班状态流转规则延误时触发「释放原跑道 → 重新分配 → 更新状态」三步。下面是一个简化的状态流转表答辩时可以直接讲这个设计。当前状态触发事件目标状态附带动作计划分配跑道计划写协调记录计划起飞起飞释放机位计划延误延误释放跑道和机位延误重新分配计划重新占用资源起飞到达到达释放跑道这张表的价值在于老师问「延误后资源怎么处理」时你能指着表说清楚每一步。实现上用一个MapInteger, MapString, Integer存流转规则或者直接写一个FlightStateMachine类都比散落的 if-else 强。4. 避坑与排查答辩前必须过的五道坎4.1 中文乱码从数据库到页面的全链路排查现象航班号里的中文航司名在页面上显示成问号。原因通常是三层编码不一致——数据库建表没指定utf8mb4、JDBC 连接串没加characterEncodingutf8、Tomcat 的server.xml没配 URIEncoding。解决顺序是先SHOW CREATE TABLE flight确认表字符集再检查application.yml的 URL最后看 Tomcat 配置。我一般直接在建表时就写死DEFAULT CHARSETutf8mb4从源头堵住。4.2 时间类型映射失败DATETIME 到 LocalDateTime 的坑现象查询航班时plan_departure字段报Cannot convert java.sql.Timestamp to LocalDateTime。原因是 MyBatis 版本和 JDK 版本不匹配老版本 MyBatis 不认识LocalDateTime。解决方法是升级 MyBatis 到 3.5.x 以上或者在实体类里改用java.util.Date。我倾向升级依赖因为LocalDateTime在时间计算上更方便比如算延误时长直接Duration.between就行。4.3 外键约束导致删除失败现象删除一条航班记录时报Cannot delete or update a parent row。原因是coordination表有外键指向flight航班被协调记录引用着。解决方法是先删协调记录再删航班或者把外键改成ON DELETE CASCADE。毕设里我一般用逻辑删除给flight加个deleted字段查询时过滤掉既避免外键问题又保留数据可追溯。4.4 并发分配同一跑道演示时两个人同时点怎么办现象答辩时老师让你和同学同时点「分配跑道」结果两个航班占了同一条跑道。原因是countConflict查询和updateById之间有间隙并发下都查到没冲突。解决方法是给跑道加乐观锁或悲观锁。简单做法是在runway表加version字段更新时带版本号或者用SELECT ... FOR UPDATE锁住跑道行。毕设演示并发低但老师很可能问这个答上来就是加分项。4.5 部署视频录制本地能跑换台机器就崩现象部署视频里系统跑得好好的老师自己一跑就报数据库连接失败。原因是视频里用的是localhost老师机器上 MySQL 端口或密码不同。解决方法是把数据库配置抽到外部application-dev.yml视频里演示改配置的过程而不是硬编码。另外记得在视频里展示mvn clean package打包和java -jar启动的完整命令别只录页面操作。5. 从能跑到能讲答辩演示脚本与参数追问应对最后一章说点实在的。系统跑通只是及格线答辩拿高分靠的是「你能讲清楚为什么这么设计」。我一般会准备一份演示脚本按「登录 → 航班列表 → 新增航班 → 分配跑道演示冲突拦截→ 分配机位 → 模拟延误 → 查看协调记录」这条线走全程不超过 5 分钟。关键是冲突拦截那一步故意分配一条已被占用的跑道让系统弹出「该跑道在此时段已被占用」这比任何 PPT 都有说服力。参数追问是重灾区。老师最爱问的三个问题一是「你的冲突检测精度是多少」答「精确到分钟用时间区间重叠判断SQL 里用严格小于避免边界误判」二是「数据量大了怎么办」答「plan_departure和status建了联合索引协调记录表按operate_time分区百万级数据查询仍在毫秒级」三是「为什么不用 Redis 缓存跑道状态」答「跑道状态变更频繁且要求强一致缓存反而增加不一致风险毕设场景下数据库直接查更可靠」。这三个答案我实测过老师听完基本不会再深挖。再给一个进阶技巧把协调记录导出成 Excel用 Java POI 实现。热词里有人问「java poi word 能生成图表吗」答案是能但毕设里导出 Excel 更实用。下面这段代码把协调记录写成.xlsx答辩时展示「系统支持数据导出」又是一个加分点。public void exportCoordination(HttpServletResponse response) throws IOException { ListCoordinationVO list coordinationMapper.listAllWithFlight(); XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(协调记录); // 表头 String[] headers {航班号, 资源类型, 资源编号, 操作, 操作时间, 操作人}; XSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 int rowIdx 1; for (CoordinationVO vo : list) { XSSFRow row sheet.createRow(rowIdx); row.createCell(0).setCellValue(vo.getFlightNo()); row.createCell(1).setCellValue(vo.getResourceType()); row.createCell(2).setCellValue(vo.getResourceCode()); row.createCell(3).setCellValue(vo.getAction()); row.createCell(4).setCellValue(vo.getOperateTime().toString()); row.createCell(5).setCellValue(vo.getOperator()); } response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamecoordination.xlsx); workbook.write(response.getOutputStream()); workbook.close(); }逻辑说明XSSFWorkbook对应.xlsx格式HSSFWorkbook对应老的.xls别用混。Content-Disposition的 filename 如果含中文要做 URL 编码否则下载下来文件名乱码。参数上listAllWithFlight是一个联表查询把航班号和资源编号一起查出来避免导出时 N1 查询。最后说个我自己的习惯答辩前一晚把系统在干净环境里重新部署一遍从装 JDK 到跑起来全程录屏。这个录屏既是部署视频的素材也是你自己的后悔药——真到现场环境出问题你能快速定位是哪一步和昨晚不一样。毕设这东西功能做得再花跑不起来就是零分。希望帮到你。本文还有配套的精品资源点击获取
返回列表