拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpringBoot+Vue机票预定系统毕设全攻略:从数据库设计到部署上线

毕设选题悬着的感觉我太懂了。打开知乎、B站、CSDN翻来覆去就是“图书管理”“学生选课”“校园商城”几个老面孔老师看到题目就把分数压了三成。如果你现在正盯着“SpringBootVue机票预定系统”这个标题犹豫那我直接说结论这个题目比大多数“管理类”项目值钱得多。它不是一个简单的增删改查堆砌而是把用户认证、检索、下单、库存扣减、订单状态流转、后台管理这些真实业务链路串在了一起技术上又刚好卡在“不超出本科毕设范围、但足够让答辩老师有东西可问”的位置。这篇文章我会把这套系统的完整内容拆开讲技术栈怎么选、数据库怎么建、核心下单逻辑怎么写才不超卖、论文怎么搭骨架、最终怎么部署到服务器上跑起来。给的是我从需求到上线的完整思路照着走你能少踩很多我自己踩过的坑。1. 选题逻辑与功能边界机票预定系统为什么是毕设黄金题先想明白一个问题不是“这个系统还能加什么功能”而是“为什么这个题目适合做毕设”。1.1 业务模型足够经典老师和评审都能一眼看懂毕业答辩有一个隐形规则题目如果需要评审老师花十分钟才能理解业务背景你的答辩难度就自动升级了。机票预订不一样买过票的人都知道流程查航班、选航班、填乘机人、下单支付、生成订单。这个流程不需要额外解释老师拿到系统就能直接判断“功能对不对”“逻辑完不完整”沟通成本极低。与此同时它又不只是一个“增删改查”。机票预订天然带有搜索条件组合、余票数量变化、订单状态流转和多角色权限这几个点每一个都能牵扯出有价值的技术讨论。同样是写代码图书管理系统只能聊“怎么把书借出去”机票系统可以聊“怎么防止两个用户同时抢到最后一趟航班的最后一张票”这个讨论深度完全不一样。1.2 系统规模可控适合独立完成也适合演示机票预订系统的完整功能虽然不少但每个模块的边界非常清晰。前端、后端、数据库三块都有明确职责做起来不会出现“代码全堆在一个Controller里”的失控状态。时间充裕可以做深入一点比如加图形化统计时间紧张也可以先保住核心链路比如用户登录、航班查询、下单、订单管理、后台航班维护做成一个五脏俱全的可运行系统工作量估算比较有把握。系统最终演示效果也直观。操作流程天然带“场景感”——登录后搜索航班、选座位对应的余票变化、下单后订单状态从待支付变为已支付这些交互在答辩现场展示时非常加分不需要解释太多就能让人懂。1.3 核心功能模块与角色边界以最常见的毕设工程量做基准这套系统可以划分为两个角色、六块核心功能角色功能模块核心需求用户端注册登录使用用户名密码注册登录后签发Token用户端航班查询按出发城市、到达城市、出发日期组合查询用户端下单购票选择航班、填写乘机人、生成订单并模拟支付用户端订单管理查看订单列表、取消待支付订单管理端航班管理新增、编辑、上下架航班维护基础票价与余票管理端订单管理查看全部订单、按状态筛选、手动标记出票如果是自己用来做毕设还可以在这个基础上加一个简单的“首页数据统计”比如今日成交订单数和销售额用ECharts画两张图视觉观感会立刻不一样论文里也能多一小节“数据可视化模块”。2. 技术栈分工SpringBoot 负责什么Vue 又负责什么2.1 前后端分离的边界划分这套系统采用典型的前后端分离架构。后端使用SpringBoot提供RESTful接口负责所有业务逻辑、数据校验、权限校验和数据库操作前端使用Vue构建单页面应用负责页面渲染、用户交互和调用后端接口。两者通过JSON格式的HTTP请求通信。绝大多数毕设项目在这一点上容易犯错前端页面把后端的逻辑也干了一半比如直接把用户信息存在localStorage里不分权限或者在后端接口里不做任何校验。正确做法是任何关键业务逻辑都必须在后端完成前端只负责“展示”和“友好提示”。原因很简单前端代码只要打开浏览器控制台就能改后端接口才是真正的安全边界。2.2 技术选型参考技术版本的选择直接决定后面前端依赖安装、后端打包是否顺利。我建议以“稳”为主后端SpringBoot 2.7.x MyBatis-Plus 3.5.x JJWT 0.9.x Lombok前端Vue 2 Element UI Axios Vue Router ECharts数据库MySQL 5.7 或 8.0构建工具Maven 3.8Node.js 16这里特别说明一个常见问题SpringBoot 3.x 要求JDK 17起步而很多学校机房和网上教程还停留在JDK 8用SpringBoot 3经常遇到“版本太高导致插件不支持”的报错。作为毕设项目除非你明确需要新特性否则不建议一开始就上SpringBoot 3.x能用稳定的老版本就不要冒险。Vue 这边同理。Vue 3配合Element Plus虽然新但很多旧教程、旧组件的写法不兼容对时间紧张的同学来说踩坑成本偏高。用Vue 2 Element UI生态成熟找任何组件问题都能很快搜到答案。2.3 核心配置文件参考后端application.yml里需要重点关注这几项server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/ticket_system?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNullserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplzeroDateTimeBehaviorconvertToNull这个参数容易被忽略但非常重要。MySQL里的0000-00-00 00:00:00这种默认时间在Java 8的日期类型解析时会直接报错加上这个参数才能正常读取。前端这边需要统一封装Axios实例把Token自动放到请求头里并在响应拦截器里统一处理401登录失效。这段代码在几乎所有SpringBootVue项目里都是通用的做好了能让后面每个页面模块开发都轻松很多import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(new Error(res.message || 请求失败)) } return res }, error { return Promise.reject(error) } ) export default request3. 数据库建模与核心业务链路从航班查询到下单不超卖3.1 核心表结构数据库是毕设项目的底盘表设计不好后面写接口、写论文、回答答辩问题都会非常难受。这套系统最核心的抽象是“航班”“用户”“订单”三个实体再加上“乘机人”来贴合真实场景。我给出最常用的建表SQL已去掉不必要字段控制在适合毕设的复杂度CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码(Bcrypt加密), real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, role TINYINT DEFAULT 0 COMMENT 角色:0-用户 1-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE flight ( id BIGINT NOT NULL AUTO_INCREMENT, flight_no VARCHAR(20) NOT NULL COMMENT 航班号, airline VARCHAR(50) DEFAULT NULL COMMENT 航空公司, dep_city VARCHAR(50) NOT NULL COMMENT 出发城市, arr_city VARCHAR(50) NOT NULL COMMENT 到达城市, dep_time DATETIME NOT NULL COMMENT 出发时间, arr_time DATETIME NOT NULL COMMENT 到达时间, price DECIMAL(10,2) NOT NULL COMMENT 票价, total_seats INT DEFAULT 180 COMMENT 总座位数, remain_seats INT DEFAULT 180 COMMENT 余票数, status TINYINT DEFAULT 1 COMMENT 状态:1-正常 0-停飞, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT航班表; CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id BIGINT NOT NULL COMMENT 下单用户, flight_id BIGINT NOT NULL COMMENT 航班ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单金额, status TINYINT DEFAULT 0 COMMENT 状态:0-待支付 1-已支付 2-已出票 3-已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE passenger ( id BIGINT NOT NULL AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT 所属订单, name VARCHAR(50) NOT NULL COMMENT 乘机人姓名, id_card VARCHAR(20) NOT NULL COMMENT 证件号, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT乘机人表;这个结构虽然看起来简单但它覆盖了数据库设计中的几个关键考点一对多关系一个订单对应多个乘机人、冗余设计订单里存了 total_amount避免后期从航班表反复查价格、索引设计外键字段建索引。论文里的E-R图直接照这个画逻辑不会乱。3.2 订单状态流转订单表里的status字段是整个业务的核心状态机。最开始把它定为“待支付、已支付、已出票、已取消”四态是因为真实业务中还有“退款”等场景但毕设项目做到这四态已经完全够用。状态流转必须是单向且有约束的不能出现“已出票突然变回待支付”这种逻辑漏洞。具体约束可以写在Service层也可以在前端做状态按钮的显示控制。后端约束才是重点比如只有待支付状态的订单才允许支付和取消已支付的订单只能由管理员标记出票。3.3 防止超卖核心事务逻辑“两个人同时买最后一张票”这个问题是答辩老师最可能追问的场景也最值得在代码里下功夫。很多初学者第一版会写这样的逻辑先用SELECT查出余票数如果余票大于0再执行UPDATE减少余票。这在并发情况下是危险的两个请求同时查出余票为1然后都执行了减1最终余票变成-1超卖了。解决方式不是加个锁就算了而是在SQL层面做原子扣减Update(UPDATE flight SET remain_seats remain_seats - #{count} WHERE id #{flightId} AND remain_seats #{count}) int reduceRemainSeats(Param(flightId) Long flightId, Param(count) Integer count);这条SQL的意思是只有当当前余票数remain_seats大于等于本次购买数量时才执行扣减。如果影响行数为0说明余票不足下单失败。这是业内非常典型的“乐观锁/CAS思想”不需要额外引入分布式锁也足够完成毕设场景下的并发安全。配合Transactional注解把“扣减余票”和“创建订单”放在同一个事务里保证要么同时成功要么同时失败Transactional(rollbackFor Exception.class) public OrderCreateVO createOrder(OrderCreateReq req) { Flight flight flightMapper.selectById(req.getFlightId()); if (flight null || flight.getStatus() ! 1) { throw new BizException(航班不存在或已停飞); } int updated flightMapper.reduceRemainSeats(req.getFlightId(), req.getPassengerCount()); if (updated 0) { throw new BizException(余票不足下单失败); } Orders order new Orders(); order.setOrderNo(T System.currentTimeMillis()); order.setUserId(req.getUserId()); order.setFlightId(req.getFlightId()); order.setTotalAmount(flight.getPrice().multiply(new BigDecimal(req.getPassengerCount()))); order.setStatus(0); orderMapper.insert(order); for (Passenger reqPassenger : req.getPassengers()) { Passenger passenger new Passenger(); passenger.setOrderId(order.getId()); passenger.setName(reqPassenger.getName()); passenger.setIdCard(reqPassenger.getIdCard()); passengerMapper.insert(passenger); } return new OrderCreateVO(order.getOrderNo(), order.getTotalAmount(), order.getStatus()); }这里有个小细节订单号我用了System.currentTimeMillis()在毕设里够用但严格来说会有撞号的可能性所以order_no字段建了唯一索引。论文里如果被问到可以补一句“生产环境可以使用雪花ID”这个回答会显得你有扩展思维。4. 代码工程实战接口设计、权限控制与关键代码写法4.1 后端工程结构SpringBoot的包结构建议直接按模块分成清楚层次不要把所有类都塞在controller里com.example.ticket ├── common // 通用类Result、BizException、JwtUtil ├── config // 配置类跨域配置、拦截器注册 ├── controller // 接口层 │ ├── AuthController.java │ ├── FlightController.java │ ├── OrderController.java │ └── AdminFlightController.java ├── entity // 数据库实体 ├── mapper // MyBatis-Plus Mapper接口 ├── service // 业务逻辑 ├── dto // 请求/响应对象 └── interceptor // 登录拦截器这种分层结构不光是代码好看更关键的是论文里的“系统设计”章节可以直接按这个结构画包图整个项目逻辑会显得非常清楚。分层不是给别人看的是给你自己维护省时间的写订单接口的时候你不需要在Controller里处理业务规则只负责接收参数和返回结果逻辑都在Service层控制。4.2 JWT的用户认证流程用户认证这一块对比传统的Session方案JWT是当前主流选择答辩时也能讲出亮点。流程是用户提交用户名密码后端校验通过后用JwtUtil生成Token返回给前端前端把Token存到localStorage并在每次请求时放入请求头后端拦截器读取请求头里的Token解析成功后放行失败则返回401。Token工具类可以自己封装也可以直接用io.jsonwebtoken库。一个常见的拦截器写法是public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BizException(401, 未登录请先登录); } Claims claims JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, claims.get(userId)); return true; } }需要注意一个常见坑放了拦截器之后登录接口、注册接口这些不需要Token的接口必须单独放行否则会出现“还没登录就被拦截器拦住无法登录”的死循环。可以用WebMvcConfigurer的addInterceptors方法配置excludePathPatterns把/api/auth/login、/api/auth/register排除掉。4.3 前端页面结构与会话保持Vue前端通常按页面拆分。作为参考页面清单如下src ├── api // 接口请求封装 │ ├── auth.js │ ├── flight.js │ └── order.js ├── router // 路由配置 ├── store // 状态管理非必需可以用localStorage ├── views │ ├── login.vue │ ├── register.vue │ ├── user │ │ ├── flightSearch.vue │ │ ├── orderList.vue │ │ └── orderConfirm.vue │ └── admin │ ├── flightManage.vue │ └── orderManage.vue └── utils └── request.js路由守卫是防止用户未登录直接通过URL跳转到后台页面的一层保障router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.requiresAdmin role ! 1) { next(/) } else { next() } })不过要强调路由守卫只是“体验层”的优化真正的权限控制必须以后端接口为准。比如管理端删除飞机的接口后端必须校验当前用户角色是管理员才能执行否则任何人都可以伪造请求删除数据。我在实际开发中遇到不少同学把前端路由守卫写在meta里就以为万事大吉了答辩时老师问一句“接口直接调用怎么办”就很难答好。4.4 管理端表格页面的高效开发模式管理端的航班管理、订单管理页面结构非常相似基本都是一个表格加搜索表单加分页再加一个弹窗表单用来新增或编辑。这个模式可以复用一套代码模板页面加载时调用分页查询接口拿到表格数据和总条数搜索表单点击查询时重置页码为1重新请求新增/编辑共用一个Dialog组件保存时根据是否有记录ID来决定走新增还是更新接口操作列放“编辑”“删除/下架”按钮删除前用$confirm确认。Element UI的el-table、el-pagination、el-dialog、el-form组合起来很快就能开发完一个管理页面。这块写起来没有太多难度但要控制好代码整洁度不要把搜索参数、分页参数和表单参数全混在一个对象里。我会在data()里分开定义queryParams、form和pagination这样每块职责清楚排查问题也方便。5. 论文结构与答辩问题储备让答辩老师挑不出大毛病5.1 论文怎么搭骨架毕设论文的核心逻辑是问题背景→技术选型→需求分析→设计→实现→测试。机票预定系统的论文通常可以参考以下章节安排章内容与系统的对应关系1 绪论背景、意义、国内外现状说明机票预订系统的研究价值带出你做的系统目标2 相关技术SpringBoot、Vue、MyBatis-Plus、MySQL、JWT每个技术介绍控制在1页内突出“为什么选它”3 需求分析可行性分析、功能需求、非功能需求、用例图把第1节的功能模块表转成需求描述4 系统设计总体架构、功能模块设计、数据库设计把第2、3节的内容整理成图和表5 系统实现各模块实现界面和核心代码说明每个核心功能配一张截图和一段关键代码6 系统测试测试环境、功能测试用例表、结果分析写10个左右测试用例覆盖“登录失败”“超卖”等场景7 总结与展望总结成果、点出不足承认扩展空间比如可以接入真实支付这里提一个容易得分的做法数据库设计里除了建表SQL至少要有一张“核心表结构说明表”把每张表的字段名、类型、约束写清楚。这个表格在论文里占空间又显得认真是典型的加分项。5.2 答辩高频问题预演答辩不是让老师看你代码多能写而是看你能不能解释清楚几个关键“为什么”。根据这套系统的技术特点我把最高频的问题和回答思路列一下问题1TPS/QPS大概能支撑多少并发老老实实说“毕设场景没有做压测但核心的防超卖逻辑已经通过数据库原子更新保证正确性”。不要吹自己高并发那是给自己挖坑。问题2为什么用JWT不用Session讲三点JWT无状态服务端不用存session适合前后端分离和分布式部署校验逻辑由签名保证。同时也可以说JWT的不足——注销难、token过期难控制所以短线业务可以配合Redis做黑名单这是加分回答。问题3MyBatis-Plus和MyBatis有什么区别MyBatis-Plus是MyBatis的增强工具内置了单表CRUD方法不需要手写XML也能通过LambdaQueryWrapper实现动态条件查询。可以说自己用它的原因是开发效率高同时也能写注解SQL处理复杂业务逻辑。问题4如果航班退票余票怎么加回来这个问题很考验真实业务理解。会答“在取消订单时通过update语句把剩余票数加回去并且同样要放在事务里确认订单状态变更和余票增加同步完成。”这个回答会显得你考虑问题很完整。问题5前端权限控制有什么用能不能直接权限就放前端要回答“安全边界在后端”前端路由守卫只能提高用户体验真正要防的是绕过页面直接调用接口所以后端接口必须有角色校验。6. 部署流程与常见坑从本地跑通到服务器上线的完整步骤6.1 本地开发环境运行步骤按下面这个顺序操作比直接看README省时间初始化数据库用Navicat或命令行新建数据库ticket_system导入项目里的sql/init.sql确认四张表生成成功且执行了一条查询语句有数据返回。启动后端用IDEA打开后端工程等待Maven依赖下载完成检查application.yml里的数据库账号密码是否与本机一致直接运行启动类。看到控制台输出“Started Application”就成功了。启动前端在项目根目录执行npm install安装完成后执行npm run serve浏览器访问http://localhost:8081如果你配置了不同端口就按配置来。本地联调最常见的坑是前端接口地址写死。建议在前端根目录创建环境变量文件.env.development写入VUE_APP_BASE_URL /api同时在vue.config.js里配置开发服务器代理module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端代码里只出现/api/xxx不会出现跨域问题也方便部署时直接切换地址。6.2 打包部署到服务器部署流程我推荐最简单可靠的方案SpringBoot打jar包 Nginx托管前端静态文件。以Linux服务器为例完整步骤是后端打包并启动mvn clean package -DskipTests执行完之后target目录下会多出一个ticket-system-0.0.1.jar文件。把jar包上传到服务器推荐用nohup方式启动nohup java -jar ticket-system-0.0.1.jar --spring.profiles.activeprod ticket.log 21 用--spring.profiles.activeprod来指定生产环境配置是为了让开发环境本地数据库和生产环境服务器数据库配置分离。在application-prod.yml中单独配置服务器上的数据库地址和账号密码。前端构建并部署npm run build构建完成后dist目录里的文件就是纯静态文件。在Nginx配置里指向这个目录并把/api路径反向代理到后端jar包端口server { listen 80; server_name your-domain-or-ip; root /www/wwwroot/ticket/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置里try_files $uri $uri/ /index.html;这一行是因Vue Router的history模式所需的。如果没有这行刷新一个二级页面比如/order/list就会变成404这是一个我在实际部署中排查了很久才想明白的典型问题。6.3 部署阶段最常见的几个问题部署环节很多同学会卡在我上面说的环境问题上这里集中列一下问题1接口通了注册也成功了但登录后请求其他接口一直401。大概率是前端请求头里没带Token检查Axios请求拦截器的localStorage.getItem(token)是否正常以及后端拦截器是否配置了/api/auth/login放行。问题2前端打包后访问首页正常刷新页面404。就是上面Nginx的try_files配置没写加上即可。问题3时间显示比本地时间早了8小时。这是MySQL连接串的serverTimezone没设置在JDBC URL里加上serverTimezoneAsia/Shanghai就能解决。问题4部署完成后页面样式乱了或图片加载不出来。检查前端构建时有没有配置publicPath建议设置为./或绝对路径避免部署到二级目录时资源路径不对。6.4 建议准备的一份“演示脚本”最后分享一个非常实用的经验部署完成后不要直接裸演示。提前准备一份演示脚本按“正常流程→异常流程→后台流程”三步走。比如正常流程注册新用户→登录→搜索航班→下单→模拟支付→在订单列表看到已支付状态异常流程把某个航班的余票设置为1再开两个浏览器窗口同时下单同一航班验证只有一个人能成功后台流程用管理员账号登录看到该订单标记出票前端订单状态变成已出票。这个演示脚本既能展示系统完整度又能主动把“并发安全”这个亮点抛给老师看是答辩现场非常加分的一手准备。实际跑通这套流程之后你会发现机票预订系统这个题目不只是能交差它还能让你在准备它的过程中把前后端联调、数据库事务、权限校验这些工作中真正用得到的技术点完整过一遍。这大概也是它值得被做成一份完整源码加论文加部署文档的原因——你拿到的不只是一个项目而是一整套可以被反复解释和扩展的工程经验。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门