☰
基于Spring Boot与Vue的汽车票网上预订系统设计实践
2026/10/15 5:59:20 网站建设 项目流程

接手过不少计算机毕业设计项目,从选题到答辩一路跟下来,这类系统见得多了之后,会发现一个很有意思的规律:很多学生一开始都想做“大而全”的系统,最后交付时反而被各种边角功能拖垮。汽车票网上预订系统这个题目能一直热门,恰恰是因为它处在一个“麻雀虽小五脏俱全”的甜蜜点——业务链路完整,但又不会复杂到一个人无法驾驭。

这篇文章就结合我实际带项目的经验,把这个基于Spring Boot的汽车票网上预订系统的核心设计、技术选型、数据库建模、关键业务逻辑落地,以及答辩验收时的常见问题,一次性讲透。不管你是正准备做这个题目,还是想找一个靠谱的Java毕设选题来参考,这篇内容都能帮你把技术方案和项目思路理清楚。

1. 项目整体设计与技术选型思路

1.1 汽车票预订系统的业务全景

在动笔写代码之前,一定要先把业务边界画清楚。汽车票网上预订系统,核心链路其实就一条:用户注册登录、查询车次、下单购票、模拟支付、出票成功。反向链路则是退票、订单查询。管理端的核心链路是:车次信息维护、订单管理、数据统计。

这一套流程跟电商系统的订单流程非常相似,但又比电商简单得多——不需要库存SKU、不需要物流、不需要复杂的促销规则。它最核心的难点在于余票扣减和座位分配,这是一个典型的有限资源并发控制场景。想通这一点,后续的架构设计和表结构设计就都有了方向。

我见过不少学生在这个项目里硬塞各种功能:在线选座、会员积分、优惠券、司机评价……不是说这些功能不能做,而是对于毕业设计而言,每一块新功能都会引入额外的表、额外的接口、额外的测试成本。合理的做法是先把核心主链路做扎实,再去考虑“亮点功能”。一个能正常买票退票、管理端能排班管单的系统,本身就足够达到本科毕设的完整度要求了。

1.2 技术栈选型:为什么是Spring Boot 3 + Vue 3

技术选型是答辩时老师必问的问题,所以每一步选择都得能说出理由。后端我推荐直接用Spring Boot 3.x,搭配JDK 17。曾经有段时间大家都在用Spring Boot 2.x,但那是老黄历了——现在新项目起步,直接上3.x没有历史包袱,而且内置的Jakarta命名空间、更好的GraalVM支持,都是加分项。

前端框架给学生的建议是Vue 3 + Element Plus。Vue 3的组合式API写起来比Vue 2的选项式API更清晰,Element Plus的表格、表单、弹窗组件能快速把后台管理界面搭起来。这里有一个务实考量:毕设时间有限,前端不要从零原生写,用成熟组件库把交互做好,把技术重心留在后端的业务逻辑上。

数据库选MySQL 8.0,ORM用MyBatis Plus。为什么不用Spring Data JPA?因为毕设答辩时老师最喜欢问SQL相关的问题,MyBatis Plus既有MyBatis那种手写SQL的灵活性,又提供了单表CRUD的便捷方法,还能通过条件构造器快速实现动态查询,对学生来说更友好,调试时也直观。

整个项目可以拆成两个子工程:后端模块和前端工程,前后端通过RESTful API通信,用JSON交换数据。这样部署时可以分开放置,开发时也能独立调试。

1.3 分层架构与目录设计的实用建议

很多学生的Controller里直接堆SQL,Service层形同虚设,这种做法短期看着快,后期改一个需求就要翻半天代码。参考规范的按分层架构来组织:

  • controller:接收请求参数,做参数校验,返回统一结果封装
  • service:写业务逻辑,事务控制在这一层
  • mapper:数据访问层,对应MyBatis Plus的BaseMapper
  • entity:数据库实体映射
  • dto:前端交互的数据传输对象,避免直接暴露实体类
  • vo:视图对象,给前端展示聚合数据
  • config:配置类(跨域、拦截器、MyBatis Plus分页插件等)
  • common:统一返回结果、异常处理、常量类

这样的分层结构在答辩时也容易说明白:每一层各司其职,层与层之间通过接口交互。老师问“为什么Controller这么薄”,你就可以回答“业务逻辑下沉到Service层,保证复用性和可测试性”。

2. 数据库设计与核心模块拆解

2.1 核心数据表设计:从字段反推业务

汽车票预订系统的数据表并不复杂,核心就是用户表、车次表、订单表,外围可以加一个班次表(车次+发车日期)或者直接合并到车次表里。我比较推荐把班次独立出来,因为同一趟车线路在30天内的每一天都可以是一个可售票的班次,运营上更合理。

用户表字段要做到“够用但不冗余”:id、用户名、密码、真实姓名、身份证号、手机号。密码入库前用BCrypt加密存储,绝对不要明文存密码,这是安全底线。身份证号在查询接口里要做脱敏处理,只显示前三位和后两位。

车次信息表的重点字段包括:出发城市、到达城市、出发时间、到达时间、票价、车型、总座位数、余票数、状态(运营中/停运)。这里要注意,出发城市和到达城市可以直接存字符串,如果城市多的话可以拆城市表,但毕设规模直接用字符串即可。

订单表是整个系统的核心。字段有:订单号、用户ID、班次ID、乘车日期、购买票数、订单金额、订单状态(待支付/已支付/已出票/已退票/已完成)、下单时间、支付时间。订单号不推荐用数据库自增id,因为并发场景下不够唯一且暴露业务量。实际项目里可以用时间戳+随机数的方式生成。

2.2 余票库存建模:乐观锁与状态控制

余票字段放在车次表中,每次购票时对余票做扣减。这里有个经典的并发超卖问题:如果用户A和用户B同时买最后一张票,两条请求同时读到余票为1,然后都执行了扣减操作,就会超卖。

解决方案参考的是乐观锁思路。SQL写成:update schedule set remaining_tickets = remaining_tickets - 1 where id = ? and remaining_tickets > 0,让数据库在更新时判断余票是否大于0,如果影响行数为0,说明票已经卖完了,本次下单失败。这是毕设级别的合理方案,不需要引入Redis分布式锁,但对高并发防护逻辑的掌握程度很能体现思考深度。

订单状态流转也要设计清楚。状态机基本是这样:下单生成待支付订单,模拟支付或调用支付接口成功后变更为已支付/已出票,用户申请退票后台审核通过后变更为已退票,发车时间过后自动或手动变更为已完成。状态流转要配合时间条件,落库时不允许从已退票再改回待支付。

2.3 管理端模块:车次管理与订单管理

管理端最基础的是车次管理,包括新增车次、修改车次信息、停运班次。管理端还需要对前端页面的车次状态做联动,停运的班次在前端就不能被搜索和预订。这块功能虽然简单,但非常能体现“系统闭环”的设计意识。

订单管理端负责查看所有用户订单,支持按订单号、用户名、订单状态多维筛选,还可以处理退票申请。顺便可以加一个简单的数据面板,统计今日订单数、今日销售额、各线路售票占比,用几个聚合SQL就可以实现,却是答辩时的展示亮点。

3. 关键业务逻辑落地与核心实现

3.1 车次查询:让用户快速找到需要的票

车次查询是前端页面最核心的入口。参数一般包括:出发城市、到达城市、出发日期。这个查询在后台对应的是一个动态SQL,注意三个条件都不一定是必传的,所以要使用条件构造器或者动态SQL标签来做拼接。

MyBatis Plus的条件构造器在这种场景下非常好用,可以这样写:

LambdaQueryWrapper<Schedule> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StrUtil.isNotBlank(departureCity), Schedule::getDepartureCity, departureCity) .eq(StrUtil.isNotBlank(arrivalCity), Schedule::getArrivalCity, arrivalCity) .eq(Objects.nonNull(departureDate), Schedule::getDepartureDate, departureDate) .eq(Schedule::getStatus, 1) .orderByAsc(Schedule::getDepartureTime);

注意条件的写法:第一个参数为布尔值,为true时才拼接该条件。查询结果的展示上,前端卡片列表比表格更适合乘客浏览,每张卡片展示出发到达站、时间、票价、余票数和“预订”按钮。

3.2 下单与余票扣减:最关键的事务处理

下单接口是后端最复杂、最需要谨慎处理的逻辑。它涉及多张表的修改:插入订单记录、扣减余票、如果用户余额足够还要扣用户余额。任何一个步骤失败,都必须保证整条操作回滚。

我把核心步骤拆解一下:

  1. 校验班次状态是否可售、发车日期是否晚于今天、购票数量是否大于0且不超过余票数
  2. 生成唯一订单号
  3. 扣减余票,执行带条件的update语句,并检查影响行数
  4. 如果所有操作都成功,插入订单记录并返回订单号
  5. 任何一步失败,直接抛出运行时异常,触发事务回滚

Service方法的@Transactional(rollbackFor = Exception.class)注解需要特别注意。如果不加rollbackFor参数,默认只在运行时异常时回滚,而很多自定义的校验异常如果是受检异常,就会导致事务不生效,数据已经扣了但订单没生成,这是一个特别经典的坑。

3.3 模拟支付与退票流程

毕业设计一般不需要对接真实支付网关,用模拟支付即可。用户下单后跳转到确认支付页面,展示订单信息和应付金额,点击“确认支付”后后端接口把订单状态从待支付流转为已支付,同时更新支付时间。这个过程要设置一个时限:比如15分钟内不支付自动取消订单并将余票回补。实现方法可以用定时任务扫描过期的待支付订单,也可以在查询时实时判断并做状态修正。

退票流程相对简单:用户提交退票申请,如果订单已支付且发车时间未到,则更新订单状态为已退票,同时将购买的票数回补到班次的余票字段。需要注意的是,退票操作必须判断“该订单当前状态为已支付”,防止用户对同一个订单重复退票导致余票多加。

3.4 统一返回结构与全局异常处理

前后端分离的项目,一定要在第一周就把统一返回结构定好。我习惯使用一个Result对象,包含code、message、data三个字段。成功返回code为200,业务失败返回自定义错误码,系统异常返回500。这样前端可以统一处理响应,不需要每个接口单独去判断。

后端配合全局异常处理器@RestControllerAdvice,捕获业务异常、参数校验异常和兜底Exception,统一转成Result结构返回。实际开发中经常出现的情况是后端一报错前端就白屏,有了全局异常处理后,接口返回的提示信息能直接展示给用户,排错的效率高很多。

以下是统一响应类的核心结构:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

4. 常见问题排查与技术避坑实录

4.1 时区问题导致的时间错乱

后端传时间给前端,发现差了8个小时,这是最常见的坑。根本原因是MySQL连接串没有指定时区。重新设置一下就好:

jdbc:mysql://localhost:3306/ticket_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8

另外,Java 8引入了LocalDateTime类型后,JSON序列化默认会输出数组格式,对前端很不友好,需要在配置里统一格式化:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

4.2 事务不生效的经典场景

事务不生效的排查经验值得单独讲一讲。遇到最多的场景是同一个类内部A方法调用B方法,B上有事务注解,但事务没起作用。原因在于Spring事务是通过代理对象实现的,类的内部调用走的是this本身,绕过了代理,所以注解不生效。解决办法有两个:把B方法拆到另一个Service类中调用;或者通过AopContext.currentProxy()来获取代理对象。

还有个细节:MyBatis Plus的BaseMapper自带的方法自带事务,但它执行的是单条SQL,本身不需要事务。真正需要事务的是Service层跨表操作,这点在答辩时也经常被问到。

4.3 分页查询与模糊搜索的组合

管理员查询订单列表时,通常会同时使用分页和模糊搜索。用MyBatis Plus的分页插件要注意,一定要在配置类里注册PaginationInnerInterceptor,否则分页SQL不会生效,还可能导致查询压力过大。

分页插件注册如下:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

使用Page对象进行分页查询时,把当前页和每页条数传进去即可。同时,模糊搜索条件使用LambdaQueryWrapper的like方法,注意不要拼接字符串去查询,会被SQL注入攻击。

4.4 跨域请求与前端交互问题

前端在8080端口,后端在8081端口,直接请求一定会遇到跨域问题。开发环境下的处理方式是在后端加一个CORS配置类,把所有来源放开;生产环境则通过Nginx配置反向代理,将/api前缀的请求转发到后端服务。两种方式二选一或者两者都配都不冲突,但要注意后端配置了CORS全开放时,生产环境就存在安全隐患,实际部署时建议改成指定来源。

跨域问题的另一个隐藏坑是预检请求。当前端请求头携带了自定义header时,浏览器会先发一个OPTIONS请求。后端如果没处理OPTIONS请求,前端会报CORS错误但后端日志里没有对应记录。全局拦截器放行OPTIONS请求是必须的。

5. 从开发到交付:部署与答疑经验分享

5.1 本地开发打包的推荐步骤

毕业设计走到最后一步,交付给导师验收时,环境一致性往往成为最大的坑。这里分享一套在多个学生机器上验证过的流程:

  1. 后端打完jar包,确认Java运行环境是JDK 17
  2. 前端执行npm run build生成dist目录,用Nginx托管
  3. 数据库导出最新的SQL脚本,包含建库、建表和测试数据
  4. 在目标服务器上按顺序安装JDK、MySQL、Nginx
  5. 启动MySQL并执行SQL脚本,确认数据表结构和数据无误
  6. 启动后端jar包,用命令行工具测试接口是否能通
  7. 配置Nginx将前端静态资源与后端API反向代理绑定
  8. 浏览器访问系统,完整走一遍从注册到购票的业务流程

我在实际带项目的过程中,帮助学生排查过很多次“本地能跑,服务器上起不来”的问题,绝大多数都出在数据库初始化这一步——要么SQL脚本没执行完整,要么字符集不对导致中文乱码。所以每次打包交付时,我都会反复强调重新初始化一遍数据库再测试。

5.2 答疑环节的高频问题与应对思路

项目交付后的答疑阶段,学生问得最多的是两类问题:一类是自己的运行环境问题,一类是导师提出的功能与代码细节问题。

环境类问题比较集中的有:数据库连接失败、端口占用、依赖下载缓慢或构建失败、前端打包后接口地址不对。逐个排查的经验是:数据库连接失败先检查MySQL服务是否启动、账号密码是否正确、数据库名是否存在。端口占用通常是8080被其他进程占用,换一个端口或者结束对应进程即可。前端构建失败则优先看node_modules目录是否完整,需要重新安装依赖。

导师提问类的问题,一般绕不开这几个方向:项目为什么选择这个课题、表结构怎么设计的、超卖问题如何解决、订单状态怎么管理的、如果用户规模增大应该怎么优化。回答这些问题的核心思路是先说设计,再说权衡。比如超卖问题,就可以说“在单体应用阶段采用数据库乐观锁来保证库存扣减的原子性,如果未来并发量提升,会引入Redis预扣库存的方案,在业务层做限流,数据库层最终兜底”。

5.3 让毕设答辩更有竞争力的几个亮点

同样的题目,有人答辩平平淡淡,有人能让老师眼前一亮,差别往往不在功能多,而在思考深度和完成度。我建议在完成基本功能之后,从下面几个方向里挑一个做到完善:

第一个是数据可视化。在管理端增加一个销售统计面板,用图表展示各线路一周的售票趋势、热门线路排行。实现上可以使用ECharts,后端只需要提供对应的聚合查询接口。这部分能让答辩时的演示内容充实很多。

第二个是定时关单。给待支付订单设置过期时间,通过Spring Boot的定时任务定时扫描并自动取消过期订单,同时回补余票。这个功能涉及并发环境下的状态判断,能体现出你对业务严谨性的思考。

第三个是参数校验与安全防护。比如接入了统一的参数校验注解,对手机号、身份证号做了正则校验,敏感字段做了脱敏处理。这类细节虽然看起来不显眼,但老师一旦问到安全设计,你就能拿出实际的落地措施来。

第四个是性能层面的设计说明。虽然毕设不需要真正压测,但你能主动说出哪些SQL加了索引、为什么订单表要按订单号做唯一索引、车次查询会为出发城市和到达城市建立联合索引,这些内容绝对能让答辩分数上一个台阶。

写在最后

我带过的学生里,做汽车票预订系统的人不少,最后得分高的,往往不是功能堆得最多的,而是把一条核心链路做得最完整、踩坑踩得最明白的。这个项目最值得投入精力的地方,就是那条从查票、下单到出票的业务主链路,加上一个能看出你思考过的库存与状态设计。

如果你在开发过程中出现订单状态莫名丢失、余票扣了又没订单、前端跨域报错这类问题,不用急着怀疑框架,按我上面梳理的排查思路走一遍,大概率几分钟就能定位。毕业设计最大的价值不是最后那一纸成绩,而是这个过程里你真正把Spring Boot这套技术栈跑通了一遍,这些经验在后面的实习和工作中一直都用得上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询