ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue电影院购票系统实战:前后端分离开发全解析

SpringBoot+Vue电影院购票系统实战:前后端分离开发全解析 1. 项目概述与技术选型思路1.1 这套电影院购票系统到底做了什么后台一直有人问我springboot vue前后端分离的项目到底该怎么练手。说实话电影院购票系统是我比较推荐的一个方向原因很简单这个业务场景大家都有亲身体验需求理解成本几乎为零但真正动手去做的时候你会发现它把Web开发里的核心知识点串得非常完整——用户注册登录、电影信息展示、场次查询、座位选择、订单创建、模拟支付、状态流转、超时处理每一个环节都是真实商业系统里会碰到的问题。这套系统我整理了一份完整版本包含SpringBoot后端源码、Vue前端工程、MySQL数据库脚本和配套文档。功能上分用户端和管理端两条线用户端支持注册登录、浏览电影列表、查看电影详情、按影院和场次选座、提交订单、模拟支付、查看我的订单管理端支持电影信息管理、影厅管理、场次排片管理、订单查询与核销。整体来说是一个业务闭环完整、可以直接跑起来的项目拿来作为课程设计、毕业设计或者想熟悉前后端分离开发流程的练手项目都非常合适。从技术维度拆解后端使用SpringBoot框架搭建RESTful API配合MyBatis-Plus操作MySQL数据库登录鉴权用JWT实现前端用Vue全家桶Vue Router Axios Element UI实现页面交互和接口通信。整条链路从前端页面到数据库落库没有任何中间省略适合照着源码一行一行去跟。1.2 为什么选SpringBoot Vue这套组合这套技术栈今天已经是Java Web开发的绝对主流但选择它不仅仅因为“流行”而是因为它确实能帮你把复杂度和学习成本控制在一个合理的范围。先看SpringBoot。它对Spring生态做了大量的自动化配置过去SSH时代要写一堆XML配置文件才能跑起来的项目现在一个启动类加几个注解就搞定了。这对学习项目的意义很大——你可以把精力放在业务逻辑本身而不是浪费在环境搭建上。同时SpringBoot对RESTful API、参数校验、全局异常处理、定时任务这些常见需求都有非常成熟的解决方案社区资料也极其丰富遇到问题基本都能搜到答案。再看Vue。Vue的双向数据绑定和组件化开发让前端代码的可维护性远高于传统的jQuery写页面而且它的学习曲线比React平缓很多对后端出身、前端基础一般的开发者非常友好。配合Element UI这类组件库不需要从零手写CSS快速就能搭出一个像模像样的管理后台和用户界面。选这套组合还有一个很现实的原因——市面上能找到的参考资料和踩坑记录最多。也就是说你跑不通的时候大概率不是一个人跑不通搜一下就能找到解决方案这对于新手来说远比技术本身“先进”更重要。1.3 系统整体架构与功能模块划分整个系统采用典型的前后端分离架构。前端是一个独立的Vue工程通过HTTP请求调用后端接口后端是独立的SpringBoot工程负责业务逻辑处理和数据库交互MySQL负责数据持久化。前端部署在Nginx上后端打包成Jar包运行前后端通过JSON格式的数据交互。功能模块我分成两条线来看用户端核心流程是“浏览电影 - 查看场次 - 选择座位 - 生成订单 - 支付 - 查看订单”。这个流程看似简单但每一步都有细节要处理比如场次列表要关联电影信息和影厅信息座位布局要根据影厅的行列数动态生成订单创建时要做座位锁定防止超卖。管理端核心流程是“维护基础数据 - 排片 - 查看销售情况”。管理员可以维护电影、影厅、场次这三类基础数据新增一场排片后用户端立刻就能看到。订单管理模块可以让管理员查看所有订单状态实现在线核销。系统中还设计了一个隐藏的定时任务模块用于处理“锁定座位后超时未支付”的释放操作这个后面会在订单部分的章节里展开也是整个系统里比较有含金量的设计点。2. 数据库设计五张核心表与座位状态机2.1 数据库关系设计与核心字段解析数据库是整个购票系统的地基设计得好不好直接决定后面业务代码写起来顺不顺畅。这套系统的数据库脚本我提供了完整的建表和初始化数据语句核心表一共五张外加一张订单明细关联表下面逐个说清楚。用户表user字段包括主键id、用户名、密码、昵称、手机号、头像、注册时间。密码字段我用了MD5加密存储演示项目图省事但如果要放到生产环境建议换成BCrypt这类加盐哈希算法这是唯一一个我建议读者自行升级的点。电影表movie字段有电影id、片名、海报地址、导演、主演、时长、类型、上映日期、剧情简介和状态。状态字段用来控制上下架下架的电影在用户端就不再展示这个设计在实践项目里非常重要。影厅表hall和场次表session是一对多的关系。影厅表记录影厅名称、排数row_count、列数col_count比如某个厅8排10列那这个厅就有80个座位。场次表记录哪个电影在哪个影厅、什么时间放映同时带上票价、语言版本国语/原声、放映格式2D/3D/IMAX票价放在场次里而不是电影里因为同一个电影在不同时间段可能有不同定价这是还原真实业务的设计。订单表orders是整个系统的核心业务表字段包括订单号、用户id、场次id、总金额、订单状态、创建时间、支付时间。订单状态我用整数类型存储0待支付1已支付2已取消3已退款。为了方便展示“我的订单”页面还冗余了电影名称、海报、影厅名称、场次时间、座位编号这些字段。这种冗余虽然在严格范式上不完美但在实际项目中能大幅减少联表查询的次数属于典型的空间换时间做法。座位相关的表是整个设计里最值得讲的部分。我没有用单独的座位表去维护每个影厅的静态座位而是直接生成一张session_seat场次座位表记录每个场次下每个座位的状态。字段为session_id、hall_row、hall_col、seat_code和seat_status。seat_code类似“A1”“B5”这种编号seat_status用0可选、1锁定、2已售三种状态。2.2 座位状态流转的设计思路为什么要在场次下单独生成一份座位数据而不是在影厅下面维护一份固定座位这是很多初学者容易搞混的地方。一个影厅有固定数量的座位没错但座位状态是跟着场次走的——上午10点场次的座位和下午2点场次的座位虽然物理上是同一把椅子但可购买状态完全独立。所以我在创建场次的时候会根据影厅的行列数批量生成该场次的全部座位记录初始状态全部为0。座位状态流转是整个购票系统的核心业务规则可以用一句话概括用户选座后座位从可选变成锁定支付成功后从锁定变成已售超时未支付则从锁定回到可选。这里锁定的作用类似于电商系统里的“库存预占”目的是在用户还处于支付环节时临时为TA保留座位防止其他人抢走。你可能要问为什么不让用户选完座位直接变成已售因为已售意味着资金已经入账而用户还没付钱这时候已经售出的座位如果用户取消订单状态回滚起来会很麻烦。引入锁定这个中间状态就把问题拆成了两个阶段锁定阶段只需要管理座位预占支付阶段才真正涉及资金和订单状态变更。这种状态拆分的方式在预订类系统中非常常见比如酒店订房、机票选座都是这个思路。2.3 初始化数据与锁定策略的选择SQL脚本里我除了建表语句还准备了一份初始化数据包括几部热门电影、三个影厅小厅3排5列、中厅5排8列、大剧场8排12列、以及覆盖最近几天的部分场次。这么做的好处是项目下载下来执行完SQL脚本就能直接看到页面上有数据不需要手动去后台一条条录入体验会好很多。座位锁定策略这点我单独强调一下。代码里提供的是基于数据库操作的实现方式创建订单时在同一个事务里先查询符合条件的座位记录使用SELECT ... FOR UPDATE加行锁然后更新座位状态为锁定再插入订单记录。这样在高并发场景下两个用户同时点击同一个座位时后一个操作会阻塞在前一个事务提交之后数据库层面的行锁天然帮我们避免了超卖问题。如果你手头有Redis环境也可以在这个基础上做一层分布式锁但坦白说对于课程设计和绝大多数业务规模数据库行锁已经完全够用。我选择这个方案是刻意为之因为它的核心逻辑都在MySQL里不依赖额外中间件部署起来更简单也更容易理解和调试。3. 后端SpringBoot核心功能落地3.1 项目初始化与依赖配置后端工程我建议直接用IDEA的Spring Initializr创建Java版本选择JDK 1.8或11SpringBoot版本用2.7.x。这里有一个非常现实的提醒SpringBoot 3.x默认要求JDK 17如果你的本机环境是JDK 8创建完项目后会发现一堆依赖报错排查起来很痛苦。所以学习阶段我推荐用2.7.x版本生态最成熟网上踩坑记录也最多跑起来最省心。核心依赖配置在pom.xml里包括spring-boot-starter-web、spring-boot-starter-validation、mybatis-plus-boot-starter、mysql-connector-java、jjwt用于JWT生成和解析、lombok、以及spring-boot-starter-test。MyBatis-Plus这里多说一句它是在MyBatis基础上做的增强工具单表操作完全不需要手写SQL内置了分页插件做管理端的列表查询效率会高很多。配置文件application.yml里需要配置数据源、MyBatis-Plus的驼峰映射和分页插件、以及JWT的自定义密钥和过期时间。数据库连接串记得加上serverTimezoneAsia/Shanghai参数不然连接MySQL 8.x时会报时区相关的异常这是新手几乎必踩的第一个坑。另外建议加上createDatabaseIfNotExisttrue启动时如果数据库不存在会自动创建方便你在本地快速跑起来。3.2 登录鉴权JWT的设计与落地登录模块说来不算复杂但涉及安全相关的东西我习惯严谨一点。用户注册时把明文密码做MD5加密后存入数据库登录时先按用户名查询用户比对MD5后的密码是否一致一致则生成JWT令牌返回给前端。JWT令牌我用jjwt库来生成payload部分放入用户id和用户名设置过期时间为24小时。密钥用一段固定字符串配置在yml里。前端拿到token后存放在localStorage中之后每次请求在请求头带上Authorization: Bearer token后端通过一个拦截器统一校验token的合法性。拦截器的实现方式是在SpringBoot中注册一个HandlerInterceptor重写preHandle方法放行登录、注册、电影列表、场次查询这些不需要认证的接口其余接口统一校验token。校验失败时直接返回401状态码和一个JSON错误信息。为了不污染Controller代码我在拦截器中就把用户信息解析出来放入Request作用域中Controller方法里直接通过HttpServletRequest取用户id不用每个接口都去解析一遍token。这个设计看似简单但在实际项目中非常实用能让业务代码干净很多。3.3 电影列表与场次查询的接口设计用户端首页是一个电影列表这个接口比较常规分页查询movie表支持按电影名称模糊搜索和按类型筛选status字段只查上架状态的记录。MyBatis-Plus的Page对象配合分页插件一行代码就能拿到总条数和当前页数据返回给前端的时候用统一的Result对象包装里面包含状态码、消息和数据三个字段这样前端处理起来非常统一。场次查询接口就稍微有点讲究了。用户点进电影详情页看到的是“选择日期和场次”的列表。接口接收电影id和日期两个参数查询session表同时联表查出影厅名称和剩余座位数。剩余座位数这个字段没有直接存储而是通过统计session_seat表中该场次下座位状态为0的数量得到的用MyBatis-Plus的selectCount方法就能实现。这里有个性能小技巧如果场次很多每次查剩余座位都要做一次统计数据量大时会有压力。实际项目中通常会在场次表里冗余一个remain_seat字段每次座位状态变化时同步更新。演示系统数据量小无所谓但把这个思考过程讲出来是想让读者理解项目工程化演进的方向。3.4 选座锁座与订单创建并发安全的处理方式这是整个系统里我最想详细展开的部分也是面试官最喜欢问的环节。这一小节我讲讲代码层面怎么实现。前端提交订单时携带的参数是场次id和选中座位的列表。后端接收请求后第一步校验座位是否都属于当前场次、数量是否超过限制我设置为单次最多选5张第二步开启事务遍历座位id列表用SELECT ... FOR UPDATE锁定这些记录然后检查每条记录的seat_status是否为0。如果有一个座位不是可选状态直接抛出业务异常事务回滚提示“座位已被他人选择请重新选座”如果全部是可选的批量更新座位状态为1锁定然后生成订单记录插入订单表最后提交事务。这里有个细节值得注意就是异常判断的顺序。我见过很多资料是先更新后判断这样会导致要把更新通过反向SQL再改回去非常容易出错。先查询、再判断、后更新顺序在任何并发场景下都是安全的。另外SELECT ... FOR UPDATE的加锁动作一定要放在事务内部并且锁的粒度是数据库行记录如果用户选了5个座位那这5行都会被锁定直到事务提交或回滚才释放。两个用户如果同时选中有交集的座位后进入的请求会一直等待前面的锁释放。为了让这个场景可以体验我在代码里故意留了一个测试方式用两个浏览器登录两个账号同时打开同一场次的选座页面然后对一个座位先后点击购买第二个操作一定会得到“座位已被锁定”的提示。这个现象在操作层面试给评审老师看是非常加分的演示点。3.5 模拟支付与订单超时释放支付接口我在代码里做了模拟处理不会真的去调微信或支付宝的SDK。接口逻辑是根据订单号查询订单校验订单归属人是否为当前登录用户校验状态是否为待支付通过后把订单状态改为已支付同时把订单关联场次座位的状态从1锁定更新为2已售最后记录支付时间。整个过程在一个事务里完成保证订单和座位状态的一致性。订单超时释放是系统里另一个容易忽略但很重要的功能。用户选座后如果一直不支付座位不能永远锁着不放否则其他用户就没法购买了。我的实现方案是使用SpringBoot自带的定时任务Scheduled写一个任务方法每隔30秒扫描一次订单表和座位表找到创建时间超过15分钟且状态仍为待支付的订单将其状态更新为已取消同时将关联的座位状态重置为0可选。为了让这个逻辑可测试我把超时时间设置得比较短15分钟如果你在本地做演示可以临时改成1分钟选座后不支付等定时任务跑起来后观察订单状态和座位释放情况。定时任务在生产环境一般会调整成更灵活的调度策略甚至可以替换成消息队列的延迟消息来实现但原理是一样的逾期未付释放资源。4. 前端Vue从搭建到选座组件4.1 工程初始化与项目结构前端工程我用Vue CLI创建选择Vue 2版本配合Element UI。之所以在2024年还推荐Vue 2不是因为它新而是因为Element UI只兼容Vue 2而且Vue 2 Element UI的学习资料实在太多遇到问题基本一搜就有答案。如果你已经有Vue 3基础也可以换成Element Plus组件API差别不大迁移成本可控。项目目录结构保持常规的Vue工程组织方式src/api目录统一存放接口请求方法src/router放路由配置src/store放Vuex状态管理src/views放页面级组件src/components放可复用组件。静态资源放src/assets。这样一个结构虽然简单但对于单人或小团队项目已经完全够用前后端联调时找接口、找页面都非常清楚。特别说明一下src/api目录里的封装方式。我用Axios创建了一个统一的实例设置baseURL为/api并配置了请求拦截器——从localStorage中取出token存在时自动加到请求头。响应拦截器统一处理返回数据当后端返回状态码401时自动跳转到登录页当状态码为业务错误时弹出Message提示。这样一来每个页面里的接口调用代码可以非常简洁只需要关心成功或失败的业务分支不用重复处理token和错误提示逻辑。4.2 路由配置与页面权限控制前端路由我配置了首页、电影详情、选座、订单确认、登录注册、个人中心、后台管理这些页面。路由守卫逻辑上使用Vue Router的beforeEach钩子判断目标路由的meta.requireAuth属性如果为true则检查localStorage中是否存在token不存在就跳转到登录页同时带上redirect参数登录成功后自动跳回原页面。管理端的路由我单独配置了/admin前缀下的几个子路由包含电影管理、影厅管理、场次管理、订单管理四个模块。管理端的权限控制是基于登录用户的role字段普通用户role为1管理员role为0。登录时后端在token中携带角色信息前端路由守卫中解析token得到角色只有role为0的用户才能访问/admin下的页面。这种控制方式在单页面应用里比较常见但要注意它只是前端层面的页面控制后端接口同样需要权限校验否则懂技术的人可以直接绕过前端调用管理员接口。所以在后端拦截器里我也对/admin开头的接口做了角色校验前端后端双保险这个是安全设计中非常重要的一环。4.3 首页、电影详情页与接口联调首页的布局比较常规顶部是导航栏左侧Logo中间菜单右侧是登录注册按钮或用户头像下拉菜单。下方主体部分使用Element UI的Carousel轮播组件显示几张热门电影的宣传图轮播图下方是电影列表区域。电影列表用了栅格布局一行展示4个电影卡片每个卡片包含海报、片名、类型、主演和购票按钮。鼠标悬停时有简单的缩放效果这类细节虽然小但能让页面看起来更精致。电影列表的数据在页面created钩子中调用API获取参数是当前页和每页条数。下拉加载更多用了Element UI的InfiniteScroll无限滚动指令滚动到底部时自动请求下一页数据。分页参数记录在data中请求成功后将新数据追加到已有的电影数组。对比直接用分页组件的方式无限滚动更符合现在移动端和Web端的浏览习惯体验会好很多。电影详情页在路由中携带电影id页面加载时调用详情接口获取电影的基本信息页面下半部分根据日期切换展示场次列表。场次列表展示放映时间、影厅名称、票价和剩余座位数点击“选座购票”按钮跳转到选座页面。这里有个数据传递方式的设计我没有用Vuex去存储选座页需要的数据而是在跳转路由时用query参数直接传递电影id、场次id、电影名称、票价这些信息。选座页面加载后如果有必要再通过接口拉取详细的座位布局数据。这种方式更简单直接也避免了刷新页面后Vuex数据丢失的问题。4.4 选座页面座位图的动态渲染与交互实现选座页面是整个前端工程里最体现功力的部分也是用户购票流程中核心中的核心。页面布局分为三部分顶部显示电影信息和场次信息中间是座位图区域底部是已选座位和结算栏。座位图的数据来自接口返回的二维数组结构大概是每一排一个数组数组里每个元素是一个座位对象包含行、列、座位编码和状态。渲染方式我用两层v-for嵌套外层循环渲染一排内层循环渲染该排的每个座位。每个座位是一个自定义组件SeatItem根据状态值控制底色和鼠标样式0可选是浅绿色1锁定是浅灰色且不可点击2已售是深灰色且不可点击选中状态是橙色高亮。点击选座的实际交互逻辑里我定义了一个selectedSeats数组。用户点击一个状态为0的座位时先判断数组长度是否已达到5达到则弹出提示“单次最多选择5个座位”否则将座位对象加入数组再次点击同一个座位则从数组中移除。判断座位是否在已选列表里我用的是find方法根据seat_code匹配。底部结算栏实时计算总价选中座位数量乘以场次单价。确认选座后点击“立即购买”跳转到订单确认页将选中的座位编码列表通过query参数或者Vuex传递给下一页。诚实地讲这个选座组件虽然写起来不算特别难但前后的交互边界情况比较多比如跨排选座、取消选择、后选的座位顶掉先选的座位等都需要一一测试到位。我建议读者把这块代码反复看几遍最好自己手动实现一遍这对前端状态管理的理解提升会非常大。5. 部署运行与常见问题排查5.1 本地部署运行完整流程从头开始跑这个项目我按顺序把步骤写完整照着走基本不会卡壳。第一步准备环境。安装JDK 1.8或11、Maven 3.6、MySQL 5.7或8.0、Node.js 14和 npm。这些基础环境装好后用java -version、mvn -v、node -v分别验证是否安装成功。第二步初始化数据库。用Navicat或其他数据库工具连接本地MySQL执行项目里sql目录下的init.sql脚本会自动创建数据库和数据表并插入初始化数据。执行完成后可以检查一下几个关键表的记录数确认数据是否完整。第三步启动后端。用IDEA打开后端工程等待Maven下载依赖完成后修改application.yml中数据库的用户名和密码为本地实际配置然后运行启动类的main方法。看到控制台打印启动成功日志后用浏览器访问http://localhost:8080/api/movie/list?page1size5能返回JSON数据就说明后端正常的。第四步启动前端。命令行进入前端工程目录执行npm install安装依赖装完后执行npm run serve。开发服务器默认端口是8080但后端已经占用了8080所以Vue CLI会提示是否换端口直接回车它会自动切换到8081。浏览器访问http://localhost:8081就能看到首页。这里有一个关键配置要提前处理就是前端开发服务器的代理。我在vue.config.js里配置了devServer的proxy选项把/api开头的请求都代理到http://localhost:8080这样开发环境中前后端的接口联调就不会有跨域问题。这个配置是本地运行项目的核心环节建议读者先确认这一步配好了再试运行前后端联调。5.2 本地运行常见问题速查我把实操中遇到频率最高的几个问题整理成一个速查表每个都是我自己或者学生实际踩过的坑照着排查能省不少时间。问题现象可能原因解决方案后端启动报数据库连接失败数据库服务未启动、用户名密码不对、时区参数缺失检查MySQL服务状态核对application.yml配置确认连接串带serverTimezone参数后端启动时报端口被占用8080端口被其他程序占用修改application.yml中server.port换成8082等未占用端口前端npm install报错网络原因导致依赖下载失败换成淘宝镜像源npm config set registry https://registry.npmmirror.com后重试前端页面无法获取接口数据代理未配置或后端未启动检查vue.config.js的proxy配置确认后端控制台是否启动成功登录后无法访问需要权限的接口token未正确传递检查Axios请求拦截器是否从localStorage中读取token并放入请求头选座时提示座位不存在前端传入的座位id与后端不一致检查选座接口参数名与后端Controller参数名是否对应第一条是最高频的坑时区问题我前面提过这里再强调一次MySQL 8.x连接串不加serverTimezone基本必报错直接把它当成条件反射来配置就行。5.3 我的一些使用心得与后续扩展方向这个项目我前前后后跑了很多遍也帮不少人排查过问题。就我自己实际使用的感受来说最想分享的一点是系统里最容易出Bug的地方不在某个具体功能而在于“订单”和“座位”两张表的状态一致性。你单独看某个接口可能觉得都没问题但把整个流程串起来跑一遍多试几次不支付的场景就会发现很多边界情况——比如用户选了座位后关掉浏览器不支付定时任务释放座位后用户又通过旧页面重复提交订单这时候后端要做好校验才能避免数据混乱。解决这类问题的通用思路我在代码里已经体现了一部分核心就是“后端永远不要信任前端传过来的数据”。选座时后端要重新校验座位状态创建订单时要从后端重新计算价格而不是直接信任前端传来的总价支付时也要校验订单归属人和订单状态。把这些校验都做到位系统的健壮性会明显上一个台阶。后续扩展方向上我建议读者从三个方向入手。第一个方向是接入真实的第三方支付微信支付和支付宝的沙箱环境可以在线申请替换掉模拟支付接口学习完整的支付回调流程。第二个方向是增加座位分区定价同一个影厅不同区域的座位票价可以不一样比如前排便宜、中后排贵这需要在座位表和场次表中增加价格策略字段。第三个方向是将定时任务替换成消息队列延迟消息学习RabbitMQ或RocketMQ的延迟队列机制这个方向对理解异步架构会很有帮助。说到底电影院购票系统这类项目的价值不在于功能多花哨而在于它能让你把Web开发的核心链条完整地跑通一遍。前端、后端、数据库、接口设计、状态管理、并发控制这些技能单独拆开都有很多资料可以学但能把它们串成一个完整系统才是真正考验工程能力的地方。这套源码和文档希望能成为你迈出这一步的助推器。
返回列表