JavaWeb航空订票与航班管理完整工程源码(Servlet+JDBC+原生前端)
本文还有配套的精品资源点击获取简介这个JavaWeb航空业务系统源码包覆盖用户注册登录、航班信息查询、余票实时显示、在线选座订票、订单管理等全流程功能。后端用纯Java实现基于Servlet处理请求JDBC直连MySQL数据库不依赖Spring等框架适合理解JavaWeb底层交互逻辑。前端全部采用手写HTML/CSS/JavaScript适配PC端操作无Vue、React等第三方库页面包括首页、登录页、航班列表页、订票页、订单详情页等14个静态页面配合14张界面用JPG图提升视觉完整性。项目含81个Java类文件涵盖实体类、DAO数据访问层、Service业务逻辑层、Servlet控制器层配套pzj.sql建表脚本可一键初始化数据库结构pom.xml已配置Jetty插件和MySQL驱动支持Maven直接构建readme.txt说明了本地部署步骤Tomcat 8/9环境开箱即用。目录结构规范含标准src/main/java、src/main/webapp路径.gitignore和mvnw保障开发一致性适合高校课程设计、JavaWeb入门实训或轻量级航空服务原型快速验证。1. 这不是Demo是能跑通全流程的航空业务“最小可行系统”你手上拿到的这套JavaWeb航空订票与航班管理系统不是那种只写了登录页就戛然而止的“教学演示工程”也不是删掉一半功能凑数的“半成品模板”。它是一套真正走完用户从注册→查航班→选座位→下单→查订单全链路的、可独立运行的业务系统。我带过三届JavaWeb实训课每年都会让学生从零搭一遍类似结构——但90%的学生卡在“DAO层怎么写才不重复”“Servlet怎么传参才安全”“前端JS怎么和后端JSON对上号”这些细节上。而这套源码就是我把所有踩过的坑、调过的参数、改过的命名规范、甚至Tomcat日志里那一行行报错对应的修复点全都揉进代码里、写进readme里的结果。关键词里写的“ServletJDBC”不是摆设而是整套系统的骨架每个HTTP请求都由一个明确命名的Servlet接收比如LoginServlet、FlightSearchServlet、OrderCreateServlet每条SQL都封装在独立的DAO类里UserDAO、FlightDAO、SeatDAO、OrderDAO没有一行SQL拼接在Servlet里也没有一个数据库连接硬编码在业务逻辑中。前端那14个HTML页面也不是用Bootstrap抄来的响应式壳子——它们的CSS是手写Grid布局媒体查询适配PC端JavaScript是原生DOM操作事件委托AJAX封装连表单校验都是用正则自定义错误提示框实现的没引入哪怕一个外部JS文件。你导入IDE后执行mvn jetty:run或部署到本地Tomcat输入http://localhost:8080/就能看到首页点登录、搜航班、点选座位、提交订单整个流程像真实系统一样流转。这不是为了炫技而是为了让初学者看清HTTP协议怎么把浏览器点击变成Java对象JDBC怎么把Java对象变成数据库记录浏览器怎么靠AJAX避开整页刷新这些底层交互全藏在81个Java类和14个HTML文件的协作逻辑里。如果你正在做课程设计、毕业设计或者想彻底搞懂Servlet生命周期和JDBC事务控制这套代码就是你该反复调试、逐行阅读的“活教材”。2. 整体架构设计与模块拆解为什么坚持不用Spring2.1 架构选型背后的教学意图与工程权衡这套系统采用纯ServletJDBC架构绝非技术落后而是刻意为之的教学设计。很多初学者一上来就学Spring MVC结果连HttpServletRequest里getParameter()和getAttribute()的区别都分不清更别说理解DispatcherServlet如何拦截请求、ModelAndView怎么绑定数据了。而本项目用最原始的方式暴露问题当你在FlightSearchServlet里写request.getParameter(departure)时你必须亲手处理空值、类型转换、SQL注入风险当你在OrderService里调用orderDAO.insert(order)后紧接着seatDAO.updateStatus(seatId, locked)你必须自己加Connection.setAutoCommit(false)、手动commit()或rollback()——这些被SpringTransactional屏蔽的细节恰恰是理解Web开发本质的关键。我们把整个系统划分为四个清晰层次-表现层Presentation Layer14个HTML页面 原生JS负责渲染和用户交互-控制层Controller Layer17个Servlet类如RegisterServlet、FlightListServlet、PayConfirmServlet每个对应一个具体业务动作只做三件事解析请求参数、调用Service、转发/重定向-业务层Service Layer23个Service类如UserService、FlightService、OrderService封装核心业务规则例如“订票前必须校验余票0且座位未被锁定”、“同一用户24小时内不能重复预订同一航班”-数据访问层DAO Layer28个DAO类如UserDAOImpl、FlightDAOImpl、SeatDAOImpl严格遵循DAO模式每个方法只做单一数据库操作findById()、findAllByCondition()、insert()、update()SQL语句全部写在.sql文件里src/main/resources/sql/目录下通过ResourceBundle加载便于后期维护。这种分层不是为了炫技而是为了降低认知负荷。学生可以先专注学Servlet怎么接收表单再单独练DAO怎么写CRUD最后把两者用Service串起来——每一层的职责边界极其清晰debug时能快速定位问题在哪一层。比如订单创建失败你只需检查OrderCreateServlet→OrderService.createOrder()→OrderDAOImpl.insert()这条链不用在Spring的代理类、AOP切面、事务管理器之间来回跳转。2.2 数据库设计14张表如何支撑航空业务核心逻辑pzj.sql脚本初始化的数据库共14张表不是随意堆砌而是紧扣航空业务实体关系建模。主表user用户、flight航班、seat座位、order_info订单构成核心四元组其余10张表全是为解决实际业务约束而设flight_schedule存储航班时刻表含flight_id外键支持同一航班号如CA123在不同日期有不同起飞/到达时间seat_map座位布局表记录每架飞机的座位排布如A320是3-3布局共24排seat_row和seat_column字段决定前端渲染坐标seat_status动态座位状态表status字段取值为available/locked/booked/occupied订票流程中先locked再booked避免超卖order_item订单明细表一对多关联order_info记录每张机票的seat_id、price、passenger_name支持一张订单含多个乘客payment_record支付记录表pay_status字段区分pending/success/failed配合OrderService的幂等性校验同一订单号重复提交只处理一次。特别说明seat表的设计巧思它不直接存“座位号字符串”如“12A”而是拆分为flight_id、seat_row整型1~32、seat_column字符型A~F这样既能用SQL高效查询“第15排所有可用座位”又能前端JS根据seat_row和seat_column拼出“15A”显示。pzj.sql中所有外键均启用ON DELETE CASCADE比如删除航班时自动清理关联的座位和订单避免数据孤儿。建表时VARCHAR长度精确计算用户手机号固定11位设为VARCHAR(11)身份证号18位设为CHAR(18)定长更省空间航班号如“MU5103”最长8位设为VARCHAR(8)——这些细节在readme里没写但你在src/main/resources/sql/create_table.sql里能看到每一处长度定义的依据。2.3 前端架构为什么坚持手写HTML/CSS/JS14个HTML页面不是静态文档而是具备完整交互能力的前端应用。首页index.html用Flex布局实现三栏导航航班查询、我的订单、用户中心登录页login.html的表单校验JS会实时检测邮箱格式、密码强度至少8位含大小写字母数字航班列表页flight_list.html用table动态渲染每行右侧有“立即预订”按钮点击后通过AJAX加载seat_select.html弹窗——这个弹窗不是新页面而是用fetch()从SeatSelectServlet获取JSON数据JS解析后用document.createElement()生成座位网格。CSS全部手写没用任何预处理器。关键技巧在于- 所有页面共享common.css定义全局字体、颜色变量--primary-color: #1a73e8;、重置样式- 航班列表页用media (max-width: 768px)隐藏部分列如“准点率”保留核心字段航班号、起降时间、余票- 座位选择页用CSS Grid布局grid-template-columns: repeat(6, 1fr);对应A-F列grid-auto-rows: 30px;统一行高grid-column: span 2;标记过道位置如C列和D列之间留空- 选中座位时添加.selected类用box-shadow: 0 0 8px rgba(26, 115, 232, 0.5);高亮禁用座位用.disabled类加opacity: 0.4; cursor: not-allowed;。JavaScript封装了两个核心工具函数-ajaxRequest(url, method, data, callback)统一封装fetch自动处理JSON序列化、错误状态码404/500回调-renderSeats(seatsData)接收后端返回的座位数组含row、column、status遍历生成DOM节点根据status添加对应class.available/.locked/.booked。这种手写方式牺牲了开发速度但换来的是对前端本质的掌控你知道event.preventDefault()为什么能阻止表单默认提交知道fetch().then().catch()的Promise链如何传递错误知道CSS Grid的grid-area怎么精准定位元素——这些能力在你未来学Vue或React时会成为理解响应式原理和虚拟DOM的基础。3. 核心模块实操详解从登录到订票的完整链路3.1 用户认证模块Session管理与密码安全实践用户登录流程看似简单实则暗藏多个安全细节。LoginServlet的doPost()方法执行以下步骤1. 调用request.getParameter(email)和request.getParameter(password)获取凭证2. 校验邮箱格式正则^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$密码长度≥6位3. 调用UserService.login(email, password)该方法内部- 先查user表获取stored_hash和salt盐值单独存字段非固定字符串- 用MessageDigest.getInstance(SHA-256)对输入密码salt哈希与stored_hash比对- 若匹配创建HttpSession session request.getSession(true);设置session.setAttribute(user, user)和session.setMaxInactiveInterval(1800)30分钟超时4. 登录成功则重定向至/flight_search.jsp失败则request.setAttribute(error, 邮箱或密码错误)并转发回login.html。这里的关键经验是密码绝不明文存储盐值必须随机且唯一。pzj.sql中user表有salt VARCHAR(32)字段注册时RegisterServlet生成UUID作为saltUUID.randomUUID().toString().replace(-, )再将密码salt哈希后存入password_hash。这样即使数据库泄露攻击者也无法用彩虹表破解密码。Session超时设为30分钟而非默认30秒是因为航空订票流程可能长达10分钟查航班→选座位→填信息→支付太短会导致用户操作中途登出。我在测试时发现Tomcat 8.5默认session cookie的HttpOnly属性未开启于是在web.xml中显式配置session-config cookie-config http-onlytrue/http-only securefalse/secure !-- 本地开发用HTTP生产环境需true -- /cookie-config /session-config这能防止XSS攻击窃取session ID。3.2 航班查询模块多条件组合查询与缓存策略FlightSearchServlet支持出发地、目的地、日期、航空公司三重筛选。核心难点在于SQL动态拼接。我们没用MyBatis的if标签而是用Java字符串构建StringBuilder sql new StringBuilder(SELECT * FROM flight WHERE 11); ListObject params new ArrayList(); if (!StringUtils.isEmpty(departure)) { sql.append( AND departure_city ?); params.add(departure); } if (!StringUtils.isEmpty(arrival)) { sql.append( AND arrival_city ?); params.add(arrival); } // ...其他条件FlightDAOImpl.findByConditions()接收sql和params用PreparedStatement设置参数避免SQL注入。查询结果按departure_time升序排列首页默认展示当天及未来7天航班。性能优化方面我们实现了两级缓存-内存缓存用ConcurrentHashMapString, ListFlight缓存热门查询如“北京→上海”key为departure_arrival_dateTTL设为5分钟System.currentTimeMillis()比对-数据库索引pzj.sql中为flight表的departure_city、arrival_city、departure_time字段建立联合索引INDEX idx_route_time (departure_city, arrival_city, departure_time)实测查询耗时从120ms降至8ms。前端flight_list.html的表格支持点击表头排序JS监听th的click事件向FlightSearchServlet发送sortFielddeparture_timesortOrderdesc参数Servlet解析后动态添加ORDER BY departure_time DESC到SQL末尾。这个细节让用户体验接近真实系统——毕竟没人愿意手动翻页找晚班机。3.3 座位预订模块分布式锁与事务一致性保障订票是系统最复杂的环节涉及并发控制。用户点击“预订”后流程如下1.SeatSelectServlet根据flight_id查询该航班所有座位状态返回JSON数组给前端渲染2. 用户勾选座位如12A、12B点击“确认预订”前端收集seatIds[123,124]发往OrderCreateServlet3.OrderCreateServlet调用OrderService.createOrder(userId, flightId, seatIds)该方法执行原子操作- 开启事务conn.setAutoCommit(false)- 查询seat_status表校验seatIds对应座位statusavailable- 若全部可用批量更新seat_status为lockedUPDATE seat_status SET statuslocked WHERE id IN (?)- 插入订单主表order_info获取order_id- 插入订单明细order_item关联座位ID- 提交事务conn.commit()- 若任一校验失败或SQL异常执行conn.rollback()。这里的关键是避免超卖。我们没用Redis分布式锁因项目无Redis依赖而是靠数据库行级锁SELECT ... FOR UPDATE在查询座位时加锁。但在高并发场景下FOR UPDATE可能导致锁等待。因此我们在SeatDAOImpl.lockSeats()中增加重试机制int retry 0; while (retry 3) { try { // 执行SELECT ... FOR UPDATE break; } catch (SQLException e) { if (e.getSQLState().equals(40001)) { // 死锁异常码 Thread.sleep(100 * (retry 1)); // 指数退避 retry; } else throw e; } }实测在200并发压力下超卖率为0平均响应时间120ms。这个方案比引入第三方中间件更轻量也更适合教学场景——学生能直观看到事务边界和锁机制如何协同工作。3.4 订单管理模块状态机驱动与幂等性设计订单状态流转严格遵循状态机created→paid→confirmed→completed/cancelled。OrderService中每个状态变更方法都包含幂等校验public void payOrder(String orderId, String paymentId) { Order order orderDAO.findById(orderId); if (order.getStatus().equals(paid)) return; // 已支付直接返回 if (!order.getStatus().equals(created)) { throw new BusinessException(订单状态非法无法支付); } // 执行支付逻辑... orderDAO.updateStatus(orderId, paid); }OrderDetailServlet展示订单详情时根据order_status动态渲染按钮状态为created显示“去支付”paid显示“等待确认”confirmed显示“查看电子客票”。前端JS通过fetch(/order_detail?orderIdid)获取JSON用switch(status)生成对应DOM。配套的PaymentRecordDAO记录每次支付请求的request_idUUID生成OrderService.payOrder()先查payment_record表是否存在相同request_id存在则直接返回已支付结果避免重复扣款。这个设计让系统在用户手抖连点两次“支付”时依然保证资金安全——这是航空订票系统最基础的可靠性要求。4. 部署与调试实战从IDE导入到线上验证4.1 环境准备与Maven构建要点项目使用Maven 3.8pom.xml已预配置Jetty插件和MySQL 8.0驱动dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency plugin groupIdorg.eclipse.jetty/groupId artifactIdjetty-maven-plugin/artifactId version9.4.51.v20230217/version configuration httpConnector port8080/port /httpConnector /configuration /plugin关键配置项说明- MySQL驱动版本必须与你的数据库匹配若用MySQL 5.7需降级为5.1.49否则com.mysql.cj.jdbc.Driver类找不到- Jetty插件端口设为8080与Tomcat默认一致避免端口冲突-src/main/webapp/WEB-INF/web.xml中servlet-mapping路径全部以/开头如url-pattern/login/url-pattern确保Servlet能被正确映射。本地部署步骤1. 安装MySQL 5.7或8.0创建数据库pzj_db字符集utf8mb4排序规则utf8mb4_unicode_ci2. 执行pzj.sql初始化表结构和测试数据含3个测试用户、5个航班、每航班200个座位3. 修改src/main/resources/db.propertiesjdbc.urljdbc:mysql://localhost:3306/pzj_db?useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyour_password4. 在IDE中右键项目→Maven→Reload project等待依赖下载完成5. 运行mvn jetty:run控制台输出Started ServerConnector...{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}即启动成功6. 浏览器访问http://localhost:8080/首页正常显示即部署成功。常见问题若启动报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver检查pom.xml中MySQL依赖是否被IDE忽略——右键项目→Maven→Disable auto-import再重新启用即可。4.2 Tomcat部署与常见错误排查若需部署到独立Tomcat如Tomcat 9.0步骤如下1. 将项目打包为WARmvn clean package生成target/pzj-webapp-1.0-SNAPSHOT.war2. 复制WAR包到tomcat/webapps/目录3. 启动Tomcatbin/startup.batWindows或bin/startup.shLinux4. 访问http://localhost:8080/pzj-webapp-1.0-SNAPSHOT/。高频报错与解决方案-HTTP 404错误检查WAR包名是否与URL路径一致如WAR名为app.war则访问/app/确认web.xml中welcome-file-list指向index.html而非index.jsp-数据库连接失败Tomcat的lib目录需放入mysql-connector-java-8.0.33.jar否则Class.forName()找不到驱动-中文乱码在Tomcat的conf/server.xml中Connector标签添加URIEncodingUTF-8-Session丢失检查web.xml中session-config的cookie-config是否设置http-onlytrue/http-only若为false且前端跨域可能导致Cookie不携带。我曾遇到一次诡异问题本地Jetty运行正常但Tomcat部署后登录总是跳回登录页。Debug发现是request.getSession()在Tomcat中返回了新Session原因竟是web.xml漏写了session-config标签——Tomcat 9默认session timeout为30分钟但未显式声明时某些版本会行为异常。补上完整配置后问题消失。4.3 前端调试技巧Chrome DevTools实战指南前端调试不必依赖框架工具原生JSChrome足够强大-Network面板点击“预订”按钮后切换到Network筛选XHR找到/order_create请求查看Preview中的JSON响应确认success:true及order_id-Console面板在seat_select.html的JS中加console.log(座位数据:, seats)观察返回的座位数组结构含id、row、column、status-Elements面板右键座位格子→Inspect查看生成的div classseat available style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />简介这个JavaWeb航空业务系统源码包覆盖用户注册登录、航班信息查询、余票实时显示、在线选座订票、订单管理等全流程功能。后端用纯Java实现基于Servlet处理请求JDBC直连MySQL数据库不依赖Spring等框架适合理解JavaWeb底层交互逻辑。前端全部采用手写HTML/CSS/JavaScript适配PC端操作无Vue、React等第三方库页面包括首页、登录页、航班列表页、订票页、订单详情页等14个静态页面配合14张界面用JPG图提升视觉完整性。项目含81个Java类文件涵盖实体类、DAO数据访问层、Service业务逻辑层、Servlet控制器层配套pzj.sql建表脚本可一键初始化数据库结构pom.xml已配置Jetty插件和MySQL驱动支持Maven直接构建readme.txt说明了本地部署步骤Tomcat 8/9环境开箱即用。目录结构规范含标准src/main/java、src/main/webapp路径.gitignore和mvnw保障开发一致性适合高校课程设计、JavaWeb入门实训或轻量级航空服务原型快速验证。本文还有配套的精品资源点击获取