1. 项目背景与整体拆解思路1.1 项目到底解决了什么问题机票管理系统这种题目说实话在毕业设计和课程设计里属于“常青树”类型。因为它的业务链路够完整用户注册登录、航班查询、预订锁座、订单支付、退票改签、后台录入航班、统计报表几乎把一个信息管理系统该有的模块全串起来了既能体现数据库设计能力又能体现业务逻辑编码水平。这个项目用 Java 系的 SSM 框架做主体再用 Django 处理辅助模块属于典型的一主一辅双技术栈组合。很多同学拿到这类题目第一反应是“我要把代码跑起来就行”。但实际上如果只停留在“能跑”答辩和文档阶段会很被动——面试官或评审老师随便问一个“订单状态怎么流转的”或者“退票时库存怎么恢复的”很容易卡壳。所以这篇内容我不光讲代码结构和功能模块还会把设计决策背后的理由拆开讲为什么订单表要单独设计一张状态字段、为什么退票和改签要分开两张表、为什么查询功能一定得用动态 SQL。把这些想明白了系统才能真正变成“你的系统”。适用人群上这个项目比较适合两类读者一类是正在做 Java Web 课程设计或毕业设计的学生需要一份结构完整、注释清晰、能讲得清楚的源码作为基础另一类是刚学完 SSM 想找一个综合案例练手的开发者通过机票业务把 Spring MVC、MyBatis、Django ORM 的整体协作流程跑通。1.2 一主一辅的双技术栈组合怎么理解才合理要澄清一个常见误区SSM 和 Django 不是在同一台 Tomcat 里共存而是各司其职。SSM 负责核心业务——航班管理、订单、用户、退改签原因是它的事务控制成熟MyBatis 写复杂查询非常灵活适合业务规则密集型系统。Django 则负责辅助性功能比如数据统计看板、航班动态爬取接口对接、定时任务调度等用 Python 生态的 requests、pandas、celery 更顺手。这种组合在真实项目中并不少见。很多公司的主业务系统用 Java 系写但数据分析和报表模块会单独拿 Python 服务来跑两边通过 HTTP 接口或共享数据库交互。放到课程设计里这种设计就多了一个亮点你可以明确回答“为什么要有两个框架”而不是让评审觉得是硬拼接。我见过不少同学的 SSMDjango 项目Django 部分只是个空壳页面这种做法如果被问到“Django 在这里起到什么作用”基本答不上来。我的建议是让 Django 真正管一个能独立运行的子模块比如航班动态聚合接口或管理员数据看板这样整个系统的逻辑就闭环了。2. 架构设计与数据模型规划2.1 整体架构与请求流转路径项目采用标准的 B/S 三层架构表现层、业务层、数据访问层在 SSM 这边分得很清楚表现层用 JSPJSTL 渲染页面Ajax 负责局部刷新比如查询结果列表的分页、订单状态的即时变更业务层用 Spring 管理 Service 组件事务边界切在 Service 方法上保证一个操作要么全成功要么全回滚数据访问层用 MyBatis 的 Mapper 接口 XML 文件复杂查询写自定义 SQL简单 CRUD 用自动映射。Django 侧单独跑在 8000 端口负责两个事情一是管理端数据可视化看板把订单量、航班热度、退改率统计出来二是航班动态定时任务用 Django Command 定时从第三方接口拉取延误信息写入共享数据库的 flight_status 表。SSM 后台管理页面里直接读取这张表展示状态这样两边就以数据库为媒介完成了协作。之所以不让 SSM 直接去拉第三方接口是因为 Java 做 HTTP 轮询也没问题但 Django 侧可以用更轻量的 Celery Beat 做定时任务管理失败重试、任务日志都更清晰。而且 Python 处理 JSON 数据再入库代码量比 Java 少一半。2.2 数据库表设计与字段细节数据库是整个系统的地基我按民航业务常见模式设计了 7 张核心表这里挑关键几张讲user用户表。字段有 id、username、passwordMD5 加盐存储、real_name、id_card、phone、role。role 区分普通用户和管理员登录后通过拦截器写入 session。flight航班表。字段有 id、flight_no、airline、departure_city、arrival_city、departure_date、departure_time、arrival_time、price、discount_price、total_seats、booked_seats、status。注意价格和折扣价分开存而不是只存一个实际价格因为后面活动促销可能随时改折扣比例。orders订单表。字段有 id、order_no、user_id、flight_id、passenger_name、passenger_id_card、seat_class、original_price、pay_price、status、create_time、pay_time。订单号和主键 id 分开订单号对外展示主键对内关联。refund_order退票申请表。字段有 id、order_id、refund_reason、apply_time、audit_status、refund_amount。退票单独成表是因为一次申请可能涉及多张联程票且审核状态需要独立跟踪。change_order改签申请表。字段有 id、order_id、old_flight_id、new_flight_id、change_reason、apply_time、audit_status、extra_pay。flight_status航班动态表。字段有 id、flight_id、actual_departure_time、actual_arrival_time、gate_number、delay_reason、update_time。admin_log管理员操作日志表。记录谁在什么时间录入了航班、修改了价格便于追溯。2.3 核心字段的设计思路挑几个设计上容易被忽略的点说清楚。第一订单表为什么不用 flight_id 直接关联还要冗余一份 flight_no 和航班时刻快照因为机票订单有历史性用户今天买了一张票明天航班时刻调整了但订单里的出发时间不能跟着变——订单记录的是交易时刻的事实。所以我在 orders 表里冗余了 departure_time、arrival_time、price 这些快照字段查询历史订单时不受航班表变更影响。这个思路在真实电商系统里叫“快照模式”。第二库存字段用 total_seats 和 booked_seats 两个字段而不是一个剩余字段。原因很简单退票和改签时要反向恢复座位如果只存剩余数并发场景下容易丢更新。用 booked_seats 做增量更新配合 WHERE booked_seats 1 total_seats 这种条件 SQL能有效避免超卖。这一点在答辩时非常加分因为很多同学只做了简单的数量减一没有考虑并发问题。第三状态字段全部用整型而不是字符串。比如订单状态 0 待支付、1 已支付、2 已出票、3 退票中、4 已退票、5 改签中、6 已改签、7 已过期。整型存储省空间而且后续用 swith 判断比字符串 equals 更高效。页面上通过枚举类或者 JSP 自定义标签翻译成中文展示。3. 核心功能模块实操拆解3.1 航班查询动态条件组合的 SQL 实现航班查询是这个系统最核心高频的功能难点在于查询条件不固定用户可能只填出发地可能只填日期可能填了城市又填了舱位。如果用 if else 拼 SQL 字符串代码会非常丑陋。这里用 MyBatis 的动态 SQL 是最合适的选择。select idsearchFlights resultTypecom.example.vo.FlightVO parameterTypecom.example.dto.FlightQueryDTO select f.id, f.flight_no, f.airline, f.departure_city, f.arrival_city, f.departure_date, f.departure_time, f.arrival_time, f.price, f.discount_price, f.total_seats, f.booked_seats from flight f where if testdepartureCity ! null and departureCity ! and f.departure_city #{departureCity} /if if testarrivalCity ! null and arrivalCity ! and f.arrival_city #{arrivalCity} /if if testdepartureDate ! null and departureDate ! and f.departure_date #{departureDate} /if if testarrivalTime ! null and arrivalTime ! ![CDATA[ and f.arrival_time DATE_SUB(#{arrivalTime}, INTERVAL 1 HOUR) ]] /if if testseatClass ! null and seatClass ! and f.seat_class #{seatClass} /if /where order by f.departure_time asc /select注意几个细节。第一where标签会自动处理掉第一个条件前面的 AND这样即使第一个 if 不成立也不会出现语法错误。第二时间范围查询里用DATE_SUB做一小时区间模糊匹配是为了支持“大概几点”这样的搜索场景而不是硬性要求精确点对点。第三出发时间和到达时间分两个字段存储不要用 DateTime 合并因为航班业务里出发日和到达日可能跨天比如 23:50 起飞次日 01:30 到达如果合并成一个 datetime 反而处理麻烦。前端页面用 Ajax 请求/flight/search接口返回 JSON 数组后渲染表格。分页我用 PageHelper 插件配置上只需要在 MyBatis 配置文件里加一行插件注册然后 Service 层调用PageHelper.startPage(pageNum, pageSize)分页参数就自动作用于下一条查询非常方便。这里有个坑PageHelper 只能作用于紧接着的这条查询语句如果中间穿插了其他 SQL分页就会错位所以查完分页数据要立即取结果。3.2 机票预订库存扣减与事务控制预订是整个系统最考验代码功底的地方。用户选中一个航班后前端把 flightId、乘客姓名、身份证号、舱位类型提交到/order/create后端处理流程包含四步校验航班是否存在、状态是否可售校验当前剩余座位是否足够。根据舱位类型和折扣价计算支付金额生成唯一订单号。更新 flight 表的 booked_seats 字段加 1。插入订单记录状态为“待支付”。这四步必须放在同一个事务里因为任何一步失败前面的操作都要回滚尤其是座位数不能出现订单没建成功但座位被扣掉的情况。事务配置推荐用注解方式在 Service 方法上标注Transactional(rollbackFor Exception.class)注意 rollbackFor 一定要写。Spring 默认只在 RuntimeException 时才回滚如果你在方法里 catch 了异常并抛出一个自定义业务 Exception不写 rollbackFor 的话它会认为不需要回滚这是很多人踩坑的地方。库存更新的 SQL 我刻意写成带条件的更新update flight set booked_seats booked_seats 1 where id #{flightId} and booked_seats 1 total_seats这个 SQL 的巧妙之处在于如果剩余座位不足受影响行数为 0业务层就可以通过判断 updateResult 是否等于 1 来决定是否抛出“航班已满员”的异常。这样即使两个用户在同一个毫秒并发下单数据库行锁也会让其中一个更新失败避免超卖。订单号的生成不建议用数据库自增 ID对外显示不友好。我用的是“时间戳 随机四位”的方式比如 202406121030152345保证唯一性也方便用户念客服时快速报号。3.3 退票改签业务规则与状态联动退票和改签是机票系统里最容易被做成“假功能”的部分很多示例代码只改了一个订单状态字段就完事了库存不恢复、退款金额不计算这显然不合格。退票的完整流程是用户提交退票申请填原因系统根据订票时间计算退票手续费然后把订单状态改成“退票中”同时把库存恢复。这里恢复库存需要注意时机——如果用户提交申请但管理员还没审核座位能不能重新销售业务上分两种策略如果系统是及时出票模式申请退票后座位立刻可售如果是审核制则要等审核通过才恢复库存。我们这个系统采用“先恢复后审核”的模式理由是简化状态管理用户提交退票即释放座位如果管理员驳回再重新扣回座位。实际运行中这种情况极少而且对用户体验友好。退款金额的计算规则写成一个独立方法提前 24 小时以上退票退 90%24 小时内退 70%起飞后不退。这个规则不要硬编码在 Controller 里单独抽出RefundCalculator类方便后续调整费率。改签比退票多一个维度要选新航班。业务流程是验证新航班座位是否充足计算差价新航班价格减去原订单支付价格如果差价大于零要求用户补差价如果小于零则退还差额。改签成功后原订单状态变成“已改签”同时生成一张新订单两张订单都保留但不重复占用座位。为什么要保留原订单因为后续可能要查历史记录、开发票如果删除原订单账目就对不上了。从实现上看改签本质上可以做两层事务外层事务创建新订单内层事务更新旧订单状态。这里我建议设计成同一个 Service 方法里串行操作因为它们都依赖同一个数据库连接的事务没必要拆成两个分布式事务增加复杂度。3.4 航班信息录入与前端表格校验后台管理员录入航班是管理系统必备的功能。前端用 Table 动态添加行每行包含航班号、航司、起降城市、日期、起降时间、价格、座位数等字段。提交前要做基本的格式校验航班号正则、时间格式、价格范围。这里分享一个前端的坑如果在 JSP 页面里用form:form绑定了后端对象动态添加行时 input 的 name 属性必须写成flights[0].flightNo、flights[1].flightNo这种数组格式Spring MVC 的RequestBody ListFlight才能正确绑定。否则你看到的总是 empty list 报错。如果想简单点可以直接用ListFlight接收 JSON 数组然后在 Controller 里通过RequestBody转换。录入之后建议做一个批量校验接口后端一次性校验所有航班是否有重复航班号、起降时间是否合理到达时间必须晚于起飞时间跨天则日期1。这个校验在 Controller 里做循环判断就行但要记得如果其中一行失败要返回具体是第几行错了前端才能定位。4. 部署调试与踩坑实录4.1 环境准备与源码导入开发环境推荐JDK 1.8、Maven 3.6、Tomcat 8.5、MySQL 5.7、IDEA。Django 侧建议 Python 3.8虚拟环境安装 Django 3.2 LTS 版本。整套环境无需额外中间件部署成本很低。源码拿到手后建议按这个顺序操作用 IDEA 以 Maven 项目方式打开 SSM 工程等待依赖下载完成。如果网络不好建议把 Maven 镜像切到阿里云否则 spring-context 等大包可能下到一半失败。修改jdbc.properties里的数据库连接信息注意 url 里要加useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai。serverTimezone 这个参数不加MySQL 8.x 驱动会报时区错误。执行sql目录下的建表脚本按顺序执行注意外键依赖关系。配置 Tomcat 的 Deployment选择 war exploded 方式这样改 JSP 后可以热部署不用重启。Django 侧在虚拟环境里pip install -r requirements.txt然后执行python manage.py migrate和python manage.py runserver 0.0.0.0:8000。启动过程中如果发现 SSM 项目启动会报 404先别急着查代码多半是项目名没配好访问路径应该是http://localhost:8080/项目名/IDEA 默认部署名称通常带上下文路径建议在 Artifact 设置里把 Output root 直接设成项目根目录这样可以用http://localhost:8080/直接访问。4.2 高频问题排查速查表我把自己调试过程中遇到最多次的问题整理成一份表格同学们可以直接对照现象可能原因解决方案启动报 ClassNotFound缺少包或 Tomcat 没加载 lib 目录IDEA 里 Artifact 添加 lib 到 WEB-INF 下数据库访问中文乱码连接 URL 没加 characterEncodingurl 加characterEncodingutf8JSP 顶部声明 UTF-8POST 表单中文乱码编码过滤器未生效web.xml 配置 CharacterEncodingFilter注意 filter 顺序查询列表接口返回空Mapper XML 里的 namespace 和接口路径不一致检查 namespace 与 Mapper 接口全限定名一致PageHelper 分页失效分页后执行了其他 SQL分页参数只对紧邻的一条查询生效加锁座位更新一直失败XML 中 update 的 where 条件没走索引flight 表 id 建主键索引事务不生效Service 方法被同类内部调用Spring AOP 基于代理内部自调用不走代理Django 连接 MySQL 报错缺少 mysqlclient 或版本不匹配用 pymysql在__init__.py里pymysql.install_as_MySQLdb()Django 模板里静态文件加载不出来settings 的 STATICFILES_DIRS 没配置对确认 manage.py 所在目录路径推荐 Path(file).resolve().parent.parent排查问题有一个通用原则先看日志再看代码最后才怀疑框架。SSM 项目在logback.xml里把 MyBatis 的 SQL 日志级别调到 DEBUG运行时就能看到每条 SQL 语句和参数很多问题一眼就能定位。Django 侧 DEBUG 模式打开后页面会直接显示错误堆栈基本不用猜。4.3 调试心得一个真实的库存并发问题我在测试阶段模拟过两个用户同时抢最后一个座位结果发现座位被卖出去了两张订单表里确实出现了两条记录。排查过程很有意思。刚开始怀疑是库存 SQL 的问题但单测发现逻辑正确。后来才意识到是我第一次提交订单时没有做库存预校验直接走的是先查剩余数、再更新库存的方式。这种“先查后更”的处理在两个并发请求同时读到剩余数为 1 时两个请求都会认为还有座位然后都执行更新出现超卖。后来改成先执行有条件更新 SQL通过影响行数判断库存是否充足再插订单问题就解决了。这个案例特别值得写进项目文档里因为它能说明你是真正考虑过并发场景的。5. 项目亮点与可扩展方向这套系统目前做到“能用、够用、可演示”但离完整的商用系统还有距离。如果时间充裕可以从下面几个方向做二期扩展一是接入真实支付接口比如支付宝沙箱或微信支付把“待支付”状态变成真正有异步回调驱动的流程。需要注意支付回调的幂等性回调可能因为网络重试多次处理逻辑里必须加订单状态判断已支付的订单不再重复修改。二是引入 Redis 做航班热点数据的缓存。热门航线搜索量很大每次查询都打 MySQL 压力不小。可以把查询结果缓存 30 秒或者用 Redis 维护航班余票数查询时先读缓存。这个点在讲述时可以提“性能优化考虑”自然会加印象分。三是把 SSM 后端改造成 RESTful API 风格前端用 Vue 单独开发。目前 JSPMVC 模式的好处是简单、无需跨域配置但前后端分离后布局和数据绑定更灵活也是现在企业开发的主流方式。四是扩展国际机票管理模块。国际机票比国内航线复杂得多涉及多个航段、多币种计价、护照信息采集、舱位等级不同退改规则。如果项目名里带了“国际机票管理”这块可以做成一个独立的子模块单独建 international_flight 表加币种字段支持往返程拼接。业务上相当于把两个航班组合成一个订单这时候订单表的设计就要改成订单主表和订单航段子表的一对多结构。我个人实际操作的体会是这类管理系统项目的价值不在代码量而在于把业务规则理解透、把边界情况考虑全。一个能把退票为什么恢复库存、订单号为什么单独生成、事务为什么必须加 rollbackFor 讲清楚的同学哪怕代码没那么炫也明显比那些拿了套模板只会改改页面文字的强得多。最后再分享一个小技巧答辩或面试前把自己项目里的表结构画出来然后用一张纸把核心业务流转路径写出来——从用户搜索航班到下单到支付到出票到退改签每一步涉及哪张表、哪个 Service 方法、状态怎么从 0 变到 6。能不看代码讲清楚这条链路比背十篇八股文都管用。
企业数字化 ERP 产品动态
相关推荐
Tesseract OCR中文识别实战:语言包配置、Python调用与预处理优化 简介:Tesseract OCR 安装包与中文语言包合集的 RAR 压缩包,面向需要离线部署 OCR 识别环境、并在 Python 或 Java 项目中集成文字识别能力的开发者。包内不仅包含可执行安装程序,还附带了中文语言训练数据,用户无需额外搜索即可完… · 2026/9/26 5:27:38
pgBadger实战:从PostgreSQL日志中快速定位慢查询与性能瓶颈 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:27:32
WPS离线办公配置指南:关闭登录依赖与联网检查的五个关键设置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 5:27:32
DeepSeek接入harness-sdk:从零部署到多智能体编排的实战指南 最近和同事聊得最多的一个词,就是harness。起因是我在折腾harness-sdk这套工具链,想把DeepSeek接入到多智能体编排环境里,让模型不仅会聊天,还能自主调用工具、拆解任务、互相协作。结果一搜关键词,页面上同时出现了An… · 2026/9/26 6:02:33
Substrate区块链开发框架全解析:从核心机制到实战避坑 substrate这个词在不同语境里含义完全不同——做材料的想到基材,做生物实验的想到酶底物,但过去这几年,技术圈里提到substrate,大概率说的是Parity那套区块链开发框架。从个人角度讲,它是我见过最接近"把造链从手… · 2026/9/26 6:02:33
中秋过的是“养藏”关口 又到中秋了。街上挂满灯笼,货架上堆满月饼,朋友圈里,满屏的团圆、祝福、玉兔、桂花。人人都在说同一句话:过节了。 可有一个字,被所有人悄悄丢掉了。那个字,叫"过"。 过节过节。这个"过&q… · 2026/9/26 6:02:33
Substrate实战指南:从选型到无分叉升级的完整技术路径 第一次在终端里看到 Substrate 节点的区块高度稳定往前跳,我承认自己那一刻有点兴奋过头,连着截了几张图发到项目群里。朋友问:这不就是拉个现成模板跑了三条命令?我一时还真没法反驳。但很快我就意识到,从“能跑起一条… · 2026/9/26 6:02:27
TypeScript的本质是信任:从编译选项到团队协作的工程落地 干这一行十多年,前后过手多少个 TypeScript 项目我已经数不清了。面试里也经常听到这类提问:TypeScript 到底是什么,类型系统有什么用,为什么团队非要上 TS 不可。我现在的答案很简单:TypeScript 的本质并非类型&#… · 2026/9/26 6:02:27
Substrate框架解析:从架构原理到Pallet开发与免分叉升级 打开搜索框输入 substrate,大概率会看到两类完全不同的结果:一类是生物化学里的酶底物,一类是材料科学里的衬底。但如果你是一个写代码的人,最近两年反复刷到的那个 substrate,大概率是另一回事——Parity 团队开源的区… · 2026/9/26 6:02:27
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46