ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MySQL铁路订票系统毕设全流程实战详解

SpringBoot+Vue+MySQL铁路订票系统毕设全流程实战详解 做毕设那会儿我身边不少同学选题都奔着好写去最后答辩现场十个里有六个是图书管理、宿舍管理、仓库管理题目一报出来老师基本就知道后面是什么套路了。我自己选了铁路订票管理系统用SpringBoot Vue MySQL这套组合做了完整的一轮最大的体会是这题目在难度可控和有的可讲之间平衡得非常好。它不是纯CRUD余票查询、下单锁座、订单状态流转、退票改签这些业务点随便挑一个都能展开聊十分钟代码量和论文篇幅都很容易撑起来。这篇文章不整虚的我从选题论证开始到数据库设计、后端接口开发、前端页面联调再到论文撰写和打包部署把完整流程和踩过的坑一条条写清楚。如果你正在纠结毕设选题或者已经选了同类订票/预约系统可以直接把这篇当参考地图用。1. 毕业设计选题铁路订票系统为什么比XX管理系统更值得做先聊选题逻辑这是很多人忽略但实际最影响后几个月心情的一步。同样是Web项目题目选得好不好直接决定你写代码时是越写越有劲还是憋到答辩前想换个题重来。1.1 业务复杂度恰到好处演示时不会三分钟露馅普通的图书管理系统、仓库管理系统核心其实是一张主表的增删改查再加两个关联表的查询。这类系统不是不能做而是答辩演示时很容易陷入尴尬给老师看一遍新增、修改、删除三分钟就讲完了剩下的时间全靠未来可以扩展来撑。铁路订票系统的业务链天然更长用户注册登录、车次列表展示、余票实时查询、下单锁座、订单支付、退票释放余票、个人订单管理……每一环都有真实的业务规则在里面。比如下单时余票不能为负一个订单最多买5张票退票后余票要回补。这些规则在代码里是逻辑判断在论文里是需求分析和功能设计的素材无论从哪个角度讲都让你有得写、有得说。1.2 技术栈选型为什么是SpringBoot Vue MySQL而不是别的每年都有人问我要不要把后端换成Python的Flask或Django前端要不要上React数据库要不要试试PostgreSQL。我的建议是除非你本来就很熟这些技术否则不要为了显得新去换。SpringBoot在Java后端的地位不用多说毕设答辩时老师几乎人手都会用你报出这个技术栈老师对项目的结构心里就有数。Vue在国内高校前端的普及率也很高而且它和SpringBoot的搭配模式非常干净前端静态资源打包后可以直接丢进SpringBoot的static目录也可以用nginx单独部署两条路都有大量现成教程。MySQL更不用讲关系型数据库的入门标准资料多到任何报错都能搜到解决方案。这套组合最实际的好处是安全感毕设季凌晨两点卡住的时候你能搜到答案比什么都重要。1.3 功能边界怎么划别打算做一个12306选了这个题目后第一件事是控制功能范围。很多同学一激动就想把选座、改签、候补购票、积分兑换全做进去结果做到一半发现工作量完全失控连核心链路都没跑通。我的做法是把系统分成必须做和可选做两层。必须做用户注册登录、车次查询、余票展示、创建订单、支付模拟、个人订单查询、管理员后台车次管理、余票管理、订单查看。可选做退票改签、图表统计、Excel导出、多角色权限细分。先花三分之二的时间把必须做做扎实有余力再碰可选做。铁路订票系统的核心价值在于业务流程完整而不是功能数量多这个边界想清楚之后后面开发节奏会稳很多。2. 数据库设计先画ER图再建表余票和订单的并发问题从这里就埋下种子很多新手拿到题目第一反应是打开Navicat开始建表边建边想字段建到一半发现缺关联又回头改。这个习惯在毕设里特别吃亏因为数据库设计是要写进论文的一章后面接口开发、前端取数全都依赖表结构返工代价很高。我自己的流程是手绘ER图 → 工具再画一遍 → 写建表SQL → 补测试数据。2.1 五张核心表谁跟谁是一对多必须一开始就理清铁路订票系统最核心的表有五张用户表、车次表、余票表、订单表、订单明细表如果需要记录多张票的乘客信息。用户和订单是一对多一个用户能下多个订单。车次和余票是一对多一个车次按座位等级拆成多条库存记录。订单和车次是多对一多个订单可能指向同一趟车。这层关系不复杂但一定要在ER图阶段理清楚否则后面写SQL时ON条件很容易写错。下面是车次表和余票表的参考结构我在实际建表时调整过几版最后定下来的是这套CREATE TABLE t_train ( id BIGINT PRIMARY KEY AUTO_INCREMENT, train_no VARCHAR(20) NOT NULL COMMENT 车次编号如G1234, train_type VARCHAR(10) COMMENT 高铁/动车/普快, start_station VARCHAR(50) NOT NULL, end_station VARCHAR(50) NOT NULL, depart_time DATETIME NOT NULL, arrive_time DATETIME NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_ticket_stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, train_id BIGINT NOT NULL, seat_type VARCHAR(20) NOT NULL COMMENT 一等座/二等座/硬座/软卧, price DECIMAL(10,2) NOT NULL, total_count INT NOT NULL, remaining_count INT NOT NULL, UNIQUE KEY uk_train_seat (train_id, seat_type) );余票表单独拆出来而不是把余票数量直接塞进车次表是因为一趟车有多种座位等级每个等级的票价和余票数都不一样。如果全部塞进车次表后续加一种座位类型就得改表结构而拆成独立表之后加等级只是加一条记录的事非常简单。2.2 几个想当然字段的坑身份证、手机号、状态值有些字段看起来简单真做起来全是细节。手机号和身份证号不要用bigint存用varchar。原因很实际Java后端接MySQL时如果字段是bigint返回给前端JSON后超过一定长度的数字会出现精度丢失身份证号后几位直接变0这种bug排查起来非常像玄学。密码字段不要用明文也别用简单的MD5Spring Security自带的BCryptPasswordEncoder在SpringBoot里集成很方便用一次就记住了。订单状态这种字段建议用整型配合枚举值而不是直接存待支付已支付这种中文。整型状态0待支付、1已支付、2已取消、3已退票代码里写一个OrderStatus枚举类可读性一点不比字符串差而且写SQL统计时更方便。论文里的数据库设计规范也能水一段文字属于一个选择服务两处需求。2.3 余票扣减的并发问题从先查再改到行级锁订票系统最值得在论文里写的技术点就是并发场景下的余票扣减。如果代码写成先查remaining_count判断大于0再减1两个请求同时查到余票为1时就会超卖。处理方案有很多毕设阶段我推荐最直观的一种对余票表的记录加行级锁先锁住再更新。SELECT * FROM t_ticket_stock WHERE id ? FOR UPDATE;事务内先通过FOR UPDATE锁住这条余票记录然后判断余票数是否充足充足才执行UPDATE扣减并插入订单。因为同一趟车同一种座位的余票记录只有一条行锁会让第二个请求排队等待从机制上杜绝超卖。这个思路写进论文就是基于悲观锁的并发控制老师一听就知道你考虑过真实业务场景比单纯写CRUD强太多。3. SpringBoot后端落地分层结构、核心接口与事务边界数据库定了后端开发就有了地基。我用的SpringBoot版本是2.7.x配套MyBatis-Plus做ORM这套组合写起来很快而且MyBatis-Plus的代码生成器能直接把单表CRUD代码生成出来把时间省给业务逻辑。3.1 包结构设计按业务模块分不按技术层次分后端包结构有两种常见组织方式按技术层分controller、service、mapper下面再分业务或者按业务模块分。我的经验是毕设项目按业务模块组织更清晰因为铁路订票系统的业务边界非常明确。com.example.train ├── controller │ ├── UserController.java │ ├── TrainController.java │ ├── OrderController.java │ └── AdminController.java ├── service │ ├── UserService.java │ ├── TrainService.java │ ├── OrderService.java │ └── TicketStockService.java ├── mapper ├── entity ├── config ├── common │ ├── Result.java │ ├── JwtUtil.java │ └── GlobalExceptionHandler.javaconfig目录放WebMvc配置、CORS跨域配置、MyBatis-Plus分页插件配置。common目录放统一返回结果类Result、JWT工具类和全局异常处理器。这套包结构就是论文里系统总体架构那章的配图来源画上去非常符合教学惯例。3.2 余票查询接口联表查询的一次实践车次列表要展示的信息不只是车次本身还有各座位的余票数和票价。最自然的方式是左连接余票表查出来之后在Java代码里按车次做聚合。前端拿到的是一个车次对应多个座位等级的数据结构展示时按小组件循环渲染即可。ListMapString, Object list trainMapper.selectTrainWithStock( new QueryWrapperTrain() .like(train_no, keyword) .ge(depart_time, queryDate) );这里有个小经验分页插件一定要配置否则MyBatis-Plus的Page查询在超过一条数据时不会自动组装分页信息。我当时漏配了这个车次列表一页永远只显示10条但不带总数前端分页组件总是显示只有一页排查半天才反应过来插件没注册。3.3 下单接口的事务边界哪些操作必须放同一事务下单是整个系统最核心的接口涉及的操作包括校验用户是否登录、查询余票、锁余票记录、扣减余票、创建订单、生成订单号。这些操作必须在一个事务里完成否则可能出现余票扣了但订单没建成或者订单建了余票没扣的数据不一致问题。我在OrderService里用Transactional标注下单方法事务边界就划在扣余票建订单这一段。生成订单号时用时间戳加随机数再加一点业务前缀比如ORD加当前年月日时分秒再加四位随机数足够保证演示场景下不会重复。这里还应该加一个幂等判断同一个用户对同一车次同一座位等级在短时间内提交两次要能识别出重复下单。我的处理是在代码里先查该用户是否已有相同车次且状态为待支付的订单有就直接返回请勿重复下单。3.4 JWT登录鉴权与拦截器配置毕设级别的登录鉴权没必要引入Spring Security做一套完整的授权体系用JWT加一个HandlerInterceptor就能解决问题。用户登录成功后后端签发一个token返回给前端前端存在localStorage里每次请求在header里带Authorization: Bearer token。拦截器统一校验放行登录、注册和车次查询等公开接口其余接口必须带有效token。public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); if (JwtUtil.verify(token)) { return true; } } response.setStatus(401); return false; } }这套实现代码量不大但前后端分离下如何做登录状态管理这个点写进论文非常加分而且面试或答辩时老师大概率会追问你只要答清楚token的无状态特性和刷新机制就算过关。4. Vue前端开发从页面骨架到一条完整的购票链路前端用Vue 2配合Element UI这个组合和SpringBoot后端在毕设生态里的搭配非常成熟文档、组件、踩坑记录都是现成的。4.1 页面路由与组件拆分让后端同学也能看懂的结构前端页面不需要太多核心是登录注册页、首页车次查询页、车次详情/下单页、订单列表页、订单详情页、管理后台页。路由用Vue Router配置好懒加载方式引入组件不然首页首次加载会把所有JS一次拉下来本地打开还好部署到服务器上会明显变慢。组件拆分的原则是一个业务页面一个文件夹。比如下单页拆成车次信息卡片、座位等级选择、乘客信息表单、订单确认提交四个子组件父组件负责数据聚合和提交逻辑。这套拆分方式在写论文前端界面设计章节时可以直接画组件树比我见过很多硬掰的架构图自然得多。4.2 余票查询页与车次列表前端最需要耐心调的部分余票查询页的逻辑是用户选择出发城市、到达城市、出发日期点击查询前端调用后端的车次列表接口拿到数据后渲染表格表格里每一行显示车次号、始发站、终点站、发车时间、到达时间、各座位等级的余票数和票价。余票数是红还是绿、按钮是可选还是置灰都需要在拿到数据后做条件判断。这里容易踩的坑是时间格式。后端返回的depart_time如果直接拿Java的LocalDateTime序列化成JSON前端拿到的可能是2025-06-18T10:30:00这种带T的字符串显示在页面上很丑。我当时的处理是在实体类时间字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)前后端一天解决不用前端再写格式化方法。4.3 axios封装、跨域与会话保持联调阶段的三个老熟人axios封装我习惯放在src/utils/request.js里统一设置baseURL和请求拦截器。const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; });跨域问题在开发环境用Vue CLI的proxy解决在vue.config.js里把/api前缀的请求代理到后端8080端口。生产环境如果是把前端打包后放进SpringBoot的static目录就不存在跨域问题了。联调时最常遇到的接口通但数据不对大部分是字段名对不上Java后端用驼峰命名前端也按驼峰接基本不会出错。提交订单接口调用时要注意防重复提交的交互设计。我前端的做法是点击提交后立即把按钮变为loading状态同时禁用请求成功或失败后再恢复。配合后端的幂等判断双保险足够应对答辩演示时手滑连点两次的尴尬场景。5. 论文撰写与答辩准备如何把代码量转化成分数论文部分的工作量不该被低估很多代码写得不错的同学最后卡在论文格式上。我的建议是论文不是最后一个月开始写而是开发到哪个模块就写哪个模块的章节最后一个月只做整合和排版。5.1 论文结构映射每一章对应系统实现的哪部分毕设论文通常有固定的六章结构绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试。我写的时候把每一章和真实代码对应起来避免出现论文写了一套代码是另一套的尴尬。绪论写研究背景和意义铁路票务行业的信息化需求这一段可以引用一些公开数据但别写成行业报告。相关技术介绍SpringBoot、Vue、MySQL各自的特点篇幅不用多每个技术写清楚在系统里承担什么角色比罗列特性更有说服力。需求分析用例图加功能需求和非功能需求铁路订票系统的用例非常清晰用户端和管理员端各一张用例图就够。系统设计架构图、功能模块图、数据库ER图、核心表结构这部分对应我前面讲的数据库设计章节。系统实现按模块写每个模块放一个核心代码片段加运行截图。系统测试功能测试用例表格加结论再写一点性能测试可以简单提一下用JMeter压测了余票查询接口200并发无报错之类。5.2 数据库设计章节的写法要点数据库设计章节是答辩老师最爱翻的部分也是最容易看出你是否真做过系统的地方。不要只贴建表语句要把每张表的用途、表之间的关系、关键字段的设计理由写清楚。比如余票表为什么单独建、订单状态为什么用整型而不是字符串、为什么在数据库层面做唯一约束。这些为什么就是你论文的深度所在也是答辩时提问的主要来源。5.3 答辩高频提问清单根据我的答辩经历和旁边同学的反馈老师针对这类系统通常问这几个问题系统的角色权限是怎么控制的答JWT拦截器加管理员标记字段余票扣减如何防止超卖答SELECT FOR UPDATE行锁加事务如果用户下单后不支付余票什么时候释放答订单有支付超时时间定时任务扫描超时订单并回补余票你的系统有什么可以改进的地方答引入Redis缓存热点车次余票用消息队列异步处理订单最后一个问题其实是送分题提前准备好两个可扩展点把改进思路讲清楚比现场临时想答案效果好十倍。6. 打包部署与踩坑记录从开发环境到能演示的系统毕设评分的一个硬指标是系统能跑起来。很多项目代码写得挺完整结果在答辩前的部署环节翻车最后只能开着IDEA和浏览器演示一旦现场网络波动就心态崩掉。6.1 本机环境版本怎么选所有怪问题的源头环境版本是最容易出问题又最容易被忽略的。我整理了一份我实测稳定的版本组合组件推荐版本备注JDK1.8 或 11SpringBoot 2.7.x 对应JDK 8都行Maven3.6不要用太老的版本Node.js16.x 或 18.xVue 2 项目在这个版本段最稳MySQL5.7 或 8.0注意驱动版本差异npm8.x装依赖用 npm别用 cnpm 也行但慢版本不匹配会出现很多莫名其妙的问题。例如MySQL 8.0之后驱动类变成了com.mysql.cj.jdbc.Driver有些老教程还在写com.mysql.jdbc.DriverSpringBoot启动直接报驱动找不到。这种问题搜起来很耗时最好一开始就用对版本。6.2 前后端如何打包一个就够了两个也行铁路订票系统的部署有两条路线。路线一后端打成jar包前端npm run build之后把dist目录下的文件复制到SpringBoot的src/main/resources/static/里一起打进jar包一个命令java -jar xxx.jar就能启动整个系统。这条路线最适合答辩前应急日志里能看到静态资源请求部署成本最低。路线二后端jar包用java -jar跑前端dist目录丢给nginx代理同时把/api开头的接口请求反向代理到后端端口。这条路线更接近企业真实部署方式但涉及nginx配置文件对第一次做的同学来说可能要多花点时间。我建议两条都试一下开发过程用nginx方式答辩前一夜加固成单jar包方式。6.3 部署阶段容易踩的坑端口、路径、数据库连接部署最常见的坑有三个。第一个是端口冲突。后端用的8080端口如果被其他程序占了启动日志里会看到端口占用异常改application.yml里的server.port就好别在不清楚情况时反复重启。第二个是数据库连接配置。如果前后端分离部署后端配置文件里的spring.datasource.url要写服务器能访问到的MySQL地址别图省事直接写localhost。另外生产环境记得在数据库初始化时把建表SQL和初始数据都执行一遍我之前就是忘了一开始往库里插管理员账号结果后端能起但管理后台怎么都登不进去。第三个是前端路由刷新404问题。如果用的是history路由模式直接部署到nginx时刷新某个子页面会出现404。解决方案是前端改成hash模式或者在nginx配置里加try_files $uri $uri/ /index.html。毕设演示用hash模式最省心问题少一多半。写到最后的一点体会做完整个项目回头看铁路订票管理系统这个题目最值钱的地方不是技术有多难而是它逼着我在一个像样的业务场景里把前后端分离开发的完整链路走了一遍。从设计表结构时考虑并发扣减到下单接口划事务边界再到前端处理loading态和重复提交每一个点都是实际开发中会碰到的事做一遍比看十遍教程管用。如果你正在做类似的毕业设计我建议别急着写代码先把功能边界、表结构、页面流转这三件事想清楚后面至少能少走一半弯路。剩下的时间该就熬夜该优化优化答辩的时候你会感谢当初那个没有划水的自己。
返回列表