ARTICLE DETAIL

资讯详情

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

基于SSM框架的航班订票系统设计与实现:从数据库到远程调试全攻略

基于SSM框架的航班订票系统设计与实现:从数据库到远程调试全攻略 毕业设计做航班订票系统这题我熟。每年到这个节点总有学生来问SSM框架的航班订票系统怎么搭、怎么调、怎么答辩问得多了我发现大家踩的坑其实高度重合。与其一个一个说不如把整个系统从技术选型到数据库设计再到远程调试的完整链路写下来你照着走能少走很多弯路。这篇文章不会讲那些花里胡哨的架构就围绕SSM这个经典组合聊透航班订票系统的设计与实现文末还会把远程调试那个最磨人的环节单独拿出来说清楚。1. 毕业设计选题为什么航班订票系统如此经典1.1 业务逻辑的复杂度刚好踩在“够用”的边界上先说个很多人没意识到的事毕业设计选题不是越复杂越好而是要复杂度刚好能覆盖你答辩时需要展示的知识点。航班订票系统这个题目妙就妙在它的业务逻辑复杂度处于一个非常理想的区间。如果做一个图书管理系统业务逻辑太单薄无非就是增删改查答辩时老师问两句“并发怎么处理”“事务怎么控制”你答不上来就容易冷场。但如果做一个电商秒杀系统业务复杂度又过头了光一个库存超卖问题就够你喝一壶更别说分布式会话、消息队列这些进阶内容一个本科生在毕设周期内很难做扎实。航班订票系统的业务量级正好在两者之间。它的核心业务围绕航班查询、余票管理、订票、支付状态跟踪来展开。这里面有余票扣减这种需要事务控制的写操作有用户登录注册这种需要会话管理的状态操作有订单查询这种典型的多表关联查询。你说它难吧每块单独拎出来都不算难你说它简单吧它确实涵盖了一个Web系统最常见的绝大多数场景。更关键的是航班订票系统的需求边界非常清晰不会出现那种需求蔓延到收不住的情况。用户端就是注册登录、查航班、订票、查订单管理端就是航班管理、订单管理、用户管理、公告管理。这套需求在学术界和工业界都已经被反复验证过你不需要花太多时间去纠结“这个功能到底该不该有”可以把精力集中在怎么把已经明确的功能做好。1.2 SSM在毕设场景中的生存优势选定了业务方向后紧接着就是技术选型。Spring SpringMVC MyBatis这套组合被统称为SSM在高校毕设中的地位至今无法被撼动。先说Spring。它最大的价值不在于IoC和AOP这两个概念本身而在于让你学会用容器的方式管理对象。你在写Service层和DAO层的时候不需要到处new对象交给Spring容器去管理和注入这在你做系统扩展时会非常舒服。比如你后面要加一个短信通知服务只需要新写一个类加几个注解容器会自动帮你接好。SpringMVC的价值在于它把前后端交互的流程理得很顺。DispatcherServlet作为前端控制器统一接收请求HandlerMapping决定这个请求交给哪个Controller处理ModelAndView负责把数据带到视图层。这套流程你一旦捋顺了后面去理解SpringBoot的请求处理机制也会轻松很多因为本质上是同一套思想。MyBatis最实用的地方是它把SQL从Java代码里解放出来了。你在Mapper.xml里写SQL语句通过接口绑定映射到Java方法。对于航班订票系统这种SQL逻辑占比不低、但又不至于复杂到需要JPA那种重型ORM的场景MyBatis是恰到好处的选择。而且它的上手成本极低只要你会写SQL就能很快上手MyBatis。这三者组合在一起之后整个系统的分层会非常清楚。Controller负责接收请求和返回视图Service负责业务逻辑DAO负责数据库操作。分层清晰意味着你写代码的思路清晰Debug的时候能快速定位问题在哪一层写毕业论文的时候架构图也画得理直气壮。1.3 一个选题能衍生出哪些变体航班订票系统这个题目还有一个好处就是它可以按你自己的兴趣点做变体。我见过学生在这个基础题目上做了很多有意思的扩展。有的同学对推荐算法感兴趣在标准功能之外加了一个简单的航班推荐模块根据用户的搜索历史和订票记录用协同过滤的思路推荐可能感兴趣的航线。有的同学对前端感兴趣把原来的JSP页面全部改成Vue或者前后端分离的架构技术上一下子就时髦了不少。还有的同学对数据分析有兴趣在后台管理端加了一个统计报表模块用图表展示某个时间段内的航班上座率变化趋势。当然我给你的建议是先确保标准功能扎实再考虑扩展。JSP页面前后端不分离的方式虽然老派但胜在实现简单、文档资料多对毕设来说最稳妥。你可以在数据库设计上多花点心思比如加入乘客信息表、舱位等级表这些扩展表让整个系统的数据模型看起来更丰满这在答辩时是能加分的地方。2. 需求分析与数据库设计先画清楚业务的边界2.1 用户端和管理端的功能拆解动手写代码之前先把需求边界划定这能帮你省掉后期大量的返工时间。航班订票系统的用户角色分为两类普通用户和管理员。你所有功能的展开都是围绕这两个角色的权限差异来设计的。普通用户端的核心功能有五块。第一是注册登录这是整个系统的入口没有这个模块后面所有的订票行为都无法关联到具体用户。第二是航班查询用户通过出发城市、到达城市、出发日期来检索可用航班这里的关键点在于查询条件的组合方式是精确匹配还是模糊匹配。第三是机票预订这是系统的核心业务用户在选定航班后填写乘机人信息系统完成订单创建。第四是订单管理用户可以查看自己的历史订单、当前订单状态以及取消未支付的订单。第五是个人信息管理修改密码、补充联系方式这类常规操作。管理端的功能也是五块但侧重点完全不同。第一是管理员登录区别于普通用户登录一般用独立的账号体系和权限控制。第二是航班管理这是管理端最核心的模块包含航班的增删改查也就是我们常说的CRUD操作。第三是订单管理管理员可以查看所有的订单但通常不提供修改功能只做状态更新和删除操作。第四是用户管理管理员可以查看注册用户列表处理违规账号。第五是公告管理管理员发布航班动态、系统维护通知等公告信息。权限控制的逻辑要提前想清楚不要让普通用户通过改一下URL就能访问到管理端的页面。用SpringMVC的拦截器加一个简单的角色判断就能解决这个问题配置层面的事后面会细说。2.2 航班表结构设计的关键点数据库设计是整个系统里最不能偷懒的部分。我的建议是起码设计五张核心表用户表、航班表、订单表、乘客表、公告表。有的同学会把乘客信息直接塞进订单表里这样虽然省了一张表但一旦一笔订单包含多个乘客数据冗余就会非常严重后续的统计查询也会变得别扭。航班表的设计有几个容易出错的地方值得单独拿出来说。第一个是时间字段的类型选择。很多教材上的示例代码喜欢用varchar来存时间这在简单Demo里确实方便但一旦你要做时间比较、按日期筛选航班字符串比较的效率和数据准确性都会出现问题。我的建议是直接用datetime或者date类型配合MyBatis的类型处理器在Java代码里用Date类型来接收展示时再按需格式化。第二个是座位数和余票量的拆分。有的同学图省事只在一个字段里存一个“可售数量”这会导致一个问题当用户下单但还没支付的时候这个数量到底扣不扣如果不扣并发情况下会出现超卖如果扣了用户最终没支付数量又得不到释放。所以更合理的设计是把seat_count舱位总座位数和remain_seat_count剩余可售座位数分开存储通过订单状态来控制这张票到底是锁定状态还是已售出状态。第三个是删除策略。尽量不要对航班表做物理删除否则后续的统计报表、历史订单查询都会出问题。设计一个status字段用0表示正常1表示停飞或者取消通过状态来控制可见性。这是项目上线后最常见的改造点毕设阶段就养成这个习惯会显得你考虑得很周全。2.3 库存扣减的前置思考余票扣减是航班订票系统里最核心的一个业务动作同时也是最容易出Bug的地方。毕业设计的并发量通常不会很大但老师可能会在答辩的时候问一句“并发情况下怎么保证不超卖”你最好能提前想好这个问题。最基础的做法是在Service层的订票方法上加Transactional事务注解保证扣减余票和创建订单这两个操作要么同时成功要么同时失败。但事务只能保证原子性不能解决并发冲突。举个例子两个用户同时查询到同一航班还剩最后一张票然后同时发起订票请求如果代码不加额外控制两个请求都读取到了remain_seat_count1然后各自执行remain_seat_count - 1最终结果是数据库里的值为0订单却生成了两笔。解决这个问题最直接的手段是用数据库的行级锁。MyBatis写SQL的时候用SELECT ... FOR UPDATE先把这条航班记录锁住然后再执行更新操作。这是一种悲观锁的思路在低并发场景下完全够用且实现简单。你可以在答辩的时候主动提一句“这里用到了悲观锁来避免超卖问题”老师通常都会觉得你有思考。如果你的毕设想要显得更有水平一点可以考虑在更新语句里加上条件判断也就是乐观锁的思路UPDATE flight SET remain_seat_count remain_seat_count - 1 WHERE id #{flightId} AND remain_seat_count 0这条SQL执行后如果返回的影响行数为0说明余票已经不足业务层就可以抛出“余票不足”的异常信息。这种做法不需要锁表性能更好代码也不复杂。3. 后端框架落地从SSM整合到核心业务实现3.1 SSM整合的配置思路绕开了需求分析的坑接下来就是真正动手搭项目的阶段。我默认你已经会用IDEA创建Maven项目不会的话先去补一下Maven的基础操作。SSM整合的本质就是让Spring容器去管理SpringMVC的Controller以及MyBatis的Mapper接口和SqlSessionFactory。关键配置文件有三份第一份是spring-mvc.xml主要配置mvc:annotation-driven /开启注解驱动、component-scan扫描Controller、配置视图解析器。视图解析器的作用是让Controller返回的index字符串能正确映射到/WEB-INF/views/index.jsp这个物理文件这里面前缀后缀的拼接规则别弄错。第二份是spring-mybatis.xml主要配置数据源、SqlSessionFactory和Mapper扫描。数据源我建议用Druid连接池不光是性能好它自带的监控页面在你调试的时候能帮你看到每一条SQL的执行情况。SqlSessionFactory的配置里有一个关键项是mapperLocations它告诉MyBatis去哪找Mapper.xml文件路径写错的话启动会直接报绑定异常。第三份是web.xml里面要配置Spring的ContextLoaderListener和SpringMVC的DispatcherServlet。一个经典的坑是DispatcherServlet的url-pattern配置成/会把静态资源的请求也拦截进去导致CSS、JS加载不出来。这时候需要在SpringMVC的配置文件里加mvc:resources mapping/static/** location/static//这个处理。整合完成之后建议你先写一个简单的systemController返回一个测试页面把整条链路通一遍再开始写业务代码。不要一上来就闷头写几个模块的代码结果一启动全报错到时候你排查问题的难度会翻好几倍。先把框架跑通后面写业务代码的心情会完全不一样。3.2 核心业务层的实现逻辑框架整合好了开始往里面填业务代码。这里我只挑几个核心业务实现的思路来讲完整代码量太大了展开写的话这篇文会变成一本小说。用户注册登录这块要注意密码不能明文存储。用MD5加盐的方式做一层哈希具体做法是取一个固定的盐值字符串拼在用户输入的密码后面再做MD5加密数据库只存加密后的密文。虽然MD5在安全领域已经不算强加密了但在毕业设计的背景下有这个意识已经能拿分了。登录态用Session保存用户的id和用户名方便后续的订单操作直接取userId。航班查询的核心是SQL语句的编写。要考虑查询条件的组合问题select idsearchFlights resultTypecom.example.pojo.Flight SELECT * FROM flight where if testfromCity ! null and fromCity ! AND from_city #{fromCity} /if if testtoCity ! null and toCity ! AND to_city #{toCity} /if if testdepartDate ! null AND DATE_FORMAT(depart_time, %Y-%m-%d) #{departDate} /if if teststatus ! null AND status #{status} /if /where ORDER BY depart_time ASC /selectwhere标签配合if动态条件是MyBatis里使用频率极高的写法。它能自动处理掉第一个条件前面的多余AND这个细节看起来小但写错了SQL就会报语法错误排查起来很浪费时间。订票的业务逻辑是最核心的部分流程是查询航班信息确认余票充足用乐观锁更新余票数量创建订单记录返回订单编号给前端展示。这个流程要放在同一个事务里任何一个步骤失败都要整体回滚。Override Transactional(rollbackFor Exception.class) public BookingResult bookFlight(BookingRequest request) { Flight flight flightMapper.selectByIdForUpdate(request.getFlightId()); if (flight null) { return BookingResult.fail(航班不存在); } if (flight.getRemainSeatCount() 0) { return BookingResult.fail(该航班余票不足); } int rows flightMapper.decreaseRemainSeat(request.getFlightId()); if (rows 0) { return BookingResult.fail(该航班余票不足); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(request.getUserId()); order.setFlightId(request.getFlightId()); order.setStatus(0); order.setCreateTime(new Date()); orderMapper.insert(order); return BookingResult.success(order.getOrderNo()); }Transactional注解一定要指定rollbackFor Exception.class否则只会回滚RuntimeException而不会回滚普通的Exception。这个坑我见过好几个学生在项目联调时踩过数据库里出现了一堆扣了库存但没有订单的脏数据。3.3 登录鉴权的简单方案权限控制不需要引入Shiro或者Spring Security这种重型框架毕业设计用SpringMVC的拦截器就能解决。写一个LoginInterceptor类实现HandlerInterceptor接口在preHandle方法里判断Session里有没有登录用户。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }配置层面要区分拦截规则。用户端接口拦截所有/user/**路径的访问管理端的登录判断会更严格一点/admin/**路径下不仅要判断是否登录还要判断当前登录用户的角色是不是管理员。把这些规则维护在SpringMVC的配置类里按模块分开管理后期要加新接口也不会影响已有功能。4. 前端页面的搭建与联调4.1 JSPJSTL的组织方式你要明确一个事实毕业设计的评分权重里前端UI只占很小一部分真正决定你能不能通过的是后端逻辑的完整度和数据库设计的合理性。所以我建议直接用JSP加上JSTL标签库做页面渲染不要再额外引入Vue或者React这层复杂度了。JSP页面建议统一放在/WEB-INF/views目录下面这个目录有个特性是外部无法直接访问只能通过Controller转发进去在安全性上多了一层保障。公共的部分比如导航栏、页脚、用户信息栏用JSP的% include filecommon/header.jsp %这种方式抽出来避免在每一个页面里重复写。用JSTL的c:forEach标签做航班列表的循环展示用c:if做条件判断这些用法本质上是把Java代码从页面上剥离出来。写页面的时候记住一个原则JSP页面里不要出现% %这种Java代码片段所有数据都通过EL表达式与JSTL配合来展示。这个习惯会让你的代码看起来干净很多答辩的时候老师翻源码看到这一点会加印象分。航班列表页面的核心展示样式可以这样组织一个查询条件表单区一个结果表格区。表单区用GET方式提交把fromCity、toCity、departDate三个参数传给后台后台查询后再把结果通过Model传递回页面。表格区每一行是一个航班右侧放“预订”按钮点击跳转到订票确认页通过URL路径参数携带flightId和deartTime例如/user/bookPage?flightId101。4.2 前后端联调中要注意的细节前后端联调阶段是最容易出现“小问题卡死人”的阶段。第一个高频问题是静态资源404表现是页面出来了但CSS样式全丢了控制台一堆404。检查一下web.xml中DispatcherServlet的url-pattern以及SpringMVC配置里静态资源的映射路径这两个地方90%的情况下是问题根源。第二个高频问题是请求路径404。Controller上标注的RequestMapping(/flight/list)JSP里表单的action地址也要是${pageContext.request.contextPath}/flight/list。很多学生直接把action写成了/flight/list本地部署时因为项目名刚好是根路径看起来没问题但一打包部署到Tomcat的webapps目录下项目名变成路径前缀之后所有的请求就全404了。第三个高频问题是日期格式的接收。前端把日期字符串通过表单提交给后台时如果实体类里是java.util.Date类型需要在SpringMVC的配置里注册一个日期转换器或者直接在字段上加上DateTimeFormat(pattern yyyy-MM-dd)注解。不加的话Spring在绑定参数的时候会报类型转换异常。这些细节没有一个是技术难点但每一个都真实地消耗过学生大量时间。你现在看到了就不用再去踩一遍了。5. 远程调试配置毕业设计答辩前的关键一步5.1 为什么毕业设计需要远程调试到了验收阶段远程调试的需求就浮出水面了。很多学生的开发环境是Windows本地但最终部署的时候用的是云服务器上的Linux环境。本地跑得好好的代码一到服务器上就出问题这种事太常见了。远程调试的意义在于你可以像调试本地代码一样在IDEA里打断点、观察变量、逐步执行而实际运行的程序是在远程服务器的Tomcat上。这中间的通信是通过JDWP协议来完成的Java虚拟机原生支持这个机制。除了解决环境差异问题远程调试还有一层实用性如果你的毕设是找别人帮忙调好的你需要在答辩的时候现场展示系统功能远程调试可以让你在答辩现场拿着连了服务器的笔记本直接操作就算现场网络环境复杂你的调试界面还是能稳稳地连在服务器上防止意外翻车。5.2 Tomcat远程调试的完整配置这个部分我直接给你一套经过检验的配置流程。第一步关闭本地运行中的Tomcat编辑Tomcat的bin/catalina.sh文件找到JAVA_OPTS相关的配置行追加以下参数JAVA_OPTS$JAVA_OPTS -agentlib:jdwptransportdt_socket,servery,suspendn,address5005如果你是Windows系统修改的是bin/catalina.bat追加的内容是一样的set JAVA_OPTS%JAVA_OPTS% -agentlib:jdwptransportdt_socket,servery,suspendn,address5005参数理解一下transportdt_socket表示用Socket方式传输调试数据servery表示当前JVM作为调试服务端suspendn表示JVM启动时不等待调试器连接这样即使IDEA没有连上来系统也能正常运行。address5005是调试端口号可以自己换但别和其他程序冲突。第二步在云服务器的安全组或者防火墙里放行5005端口。阿里云、腾讯云这些服务商的控制台里找到安全组规则添加一条入方向规则端口填5005来源建议不要设置成0.0.0.0/0这种全网段限定你自己的IP地址会更安全。第三步重启远程服务器的Tomcat确认端口监听正常netstat -an | grep 5005看到LISTEN状态就说明JDWP服务已经起来了。5.3 IDEA连接远程调试的完整操作回到IDEA侧配置远程调试连接。在IDEA的顶部菜单栏选择Run→Edit Configurations点击左上角的加号选择Remote JVM Debug这是IDEA 2023以上版本的名字老版本叫Remote。名称随便取一个比如ServerDebug。Host填你服务器的公网IP地址Port填5005。对于IDEA 2024.2版本最方便的地方在于它能自动生成对应的命令行参数你只需要把IDEA显示的那段参数组合起来更新到远程服务器的Tomcat启动参数里就行。配置完成之后先启动远程服务器的Tomcat然后在IDEA里点击调试按钮正常情况下控制台会显示类似Connected to the target VM, address: xxx:5005, transport: socket的提示表示连接成功。接着在本地代码里任意一个位置打上断点通过前端页面发起请求你会发现IDEA的调试面板弹出来了变量、调用栈、Watch表达式全部和本地调试一样。5.4 远程调试最常踩的坑远程调试虽然好用但有三个坑非常经典。第一个坑是address already in use也就是5005端口被占用。排查方法是在服务器上执行lsof -i:5005或者netstat -an | grep 5005看看到底是哪个进程占用了端口。有时候是之前启动的Tomcat进程没杀干净用ps -ef | grep tomcat查一下把旧进程杀掉再重启就好。第二个坑是代码版本不同步。IDEA里本地的代码和远程服务器上部署的代码不一致会导致断点位置跳行或者变量显示值不对。每次改了代码记得先重新打包部署到远程服务器再开始调试。这个坑最隐蔽因为表面上看一切都是正常的但实际上你在调试的是一个和源码不一致的程序。第三个坑是连接超时。如果你的服务器带宽比较小第一次连接的时候类加载会比较慢IDEA可能会报连接超时的错误。解决方式比较直接把IDEA里的Socket连接超时时间调大一般默认是500毫秒调到5000毫秒或者更大这个问题基本就能解决。6. 常见Bug与避坑经验6.1 Maven依赖冲突与版本选择SSM项目最常见的启动报错之一就是依赖冲突。Spring的依赖如果不小心引入了两个不同的版本启动时会出现类似NoSuchMethodError或者ClassNotFoundException的异常。我的建议是用Maven的dependencyManagement把Spring相关的依赖版本统一管理起来。一套经过验证的版本组合是这样的Spring 5.x系列统一用5.3.x的版本SpringMVC包含在其中不需要单独引MyBatis用3.5.xMyBatis-Spring适配器用2.0.x数据库驱动根据你的数据库版本来选。用这套组合能跑得很稳。另外要注意一个经典冲突javax.servlet-api和tomcat-embed-core如果都引入了且版本不一致会出现一些诡异的启动问题。解决思路是确认前者用provided作用域引入不让它打包进最终的war包。6.2 数据库连接与时区问题本地连数据库一切正常部署到服务器上就报错The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这个报错看起来很吓人实际上就是MySQL时区问题。在数据库连接URL里加上serverTimezoneAsia/Shanghai就能解决。MyBatis还有一个高频问题打印SQL日志时发现参数没传进去控制台显示 Parameters:后面是空的。这个问题通常是Mapper接口方法参数和XML里的#{...}占位符对不上导致的。检查一下接口方法上有没有加Param注解多参数的Mapper方法必须加这个注解不加的话MyBatis不知道占位符里的名字对应哪个参数。6.3 部署到服务器后的注意点当所有代码调试完毕准备部署到服务器上给老师演示的时候有几个细节一定不能忽略。不要把数据库连接的账号密码以明文形式写死在代码里至少放到jdbc.properties配置文件里统一管理。同时一定要检查一下数据库的用户权限很多学生在本地用的是root账号放到服务器上还是root这是很不好的习惯。一个合格的数据库用户应该是只对该项目数据库拥有增删改查权限的普通用户。有两样东西不要传到服务器上一是IDE的配置目录比如.idea文件夹二是target目录。这些编译产物和IDE配置与项目代码无关传上去不仅浪费带宽还可能暴露本机路径信息。用Git初始化仓库写一个合理的.gitignore文件把该忽略的文件都忽略掉。6.4 论文与代码同步的三件套最后唠叨一点论文和代码的配合。毕业论文里涉及系统实现的部分最好的做法是截图配上核心代码片段但代码不要贴大段只贴其中最核心的方法体。比如订单生成的Service方法、航班查询的动态SQL、拦截器的权限判断逻辑这三个贴进去比贴十个页面的截图更有说服力。数据库设计的章节要放ER图并且把数据库表的字段含义、类型、约束写清楚。这些内容如果你的代码注释写得好可以直接从代码里提炼。另外论文里提到的所有功能代码里必须都能跑通这个是最重要的不要论文里写了一大堆功能演示的时候点开一个报一个错那会非常尴尬。答辩的时候现场演示永远比讲PPT更容易拿高分。把核心流程从头到尾走一遍讲清楚每一步的设计意图比你背一百页PPT都有用。我自己的经验是航班订票系统这个题目之所以每年都有人选而且每年都能出好作品就是因为它给每个人留了足够的空间去发挥同时又不会因为太难而让人望而却步。你只需要脚踏实地把需求理清、把表设计好、把代码写顺、把部署跑通这四步走完你的毕业设计就已经站在了大多数人前面。
返回列表