news 2026/9/15 18:00:35

SpringBoot+Vue影院订票系统:从环境搭建到并发选座完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue影院订票系统:从环境搭建到并发选座完整实战

打开GitHub或者各类毕设资源站,搜索“影院订票系统”几个字,你能看到几十个面孔几乎一模一样的项目,技术栈也高度统一:SpringBoot+Vue,前端一个后台管理,后端一套RBAC权限,再加几张表和一份接口文档。看上去很完整对吧?但真正拿到手开始改的时候,问题全冒出来了——要么数据库脚本在MySQL 8.0上跑不过,要么Vue版本和依赖对不上编译直接报错,要么项目一启动Spring版本太高和JDK版本冲突,最后折腾几天连登录都进不去。

这篇文章想跟你聊的,就是一个彻底跑通、能做毕设答辩、也能写进简历的SpringBoot+Vue影院订票系统。我会从项目结构、数据库设计、后端接口、前端页面、联调部署、答辩准备这几个维度完整拆解一遍,包括哪些表该怎么建、座位怎么锁、订单状态怎么流转、前端路由守卫怎么写、打包之后为什么白屏这类具体问题。内容偏实操,尽量少讲空泛理论,适合正在准备Java Web方向毕业设计、或者想用一套全栈项目练手找工作的同学参考。

1. 从选型到跑通:一套影院订票系统的完整技术拼图

先说结论:这套项目的技术栈选得非常稳。后端用SpringBoot,前端用Vue,数据库用MySQL,接口文档用Swagger或者Apifox导出,整个链路没有花哨的东西,但足够覆盖一个毕业设计需要展示的全部知识点。

1.1 各端角色划分与项目结构

一个影院订票系统从业务上拆,至少要分成两条线:用户端和后台管理端。

  • 用户端:面向普通观众,提供注册登录、电影列表、电影详情、场次选择、座位选择、生成订单、模拟支付、查看我的订单这些功能。
  • 后台管理端:面向影院运营人员,提供电影信息管理、场次排片管理、影厅座位管理、订单查看与校验、用户管理等能力。

对应到代码工程上,一般拆成两个部分:

├── cinema-backend // SpringBoot 后端工程 │ ├── src/main/java/com/example/cinema │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ ├── entity │ │ ├── config │ │ ├── common │ │ └── utils │ ├── src/main/resources │ │ ├── mapper // MyBatis XML │ │ ├── application.yml │ │ └── sql/cinema.sql │ └── pom.xml └── cinema-frontend // Vue 前端工程 ├── src │ ├── api │ ├── assets │ ├── components │ ├── router │ ├── store │ ├── views │ ├── App.vue │ └── main.js ├── package.json └── vue.config.js

这个结构的好处是前后端职责清晰,写论文章节的时候也方便展开。数据库脚本独立放在sql/目录下,评审老师打开项目能一眼看到,这是一个不错的加分细节。

1.2 版本选择是最容易踩的坑

我在热搜词里看到大量类似“SpringBoot版本太高”“idea创建springboot项目报错”“vue安装及环境配置失败”这样的搜索词,这其实是毕设项目翻车的第一大原因。

不同版本的组合差异非常大,我直接给一套实测稳定的版本搭配:

组件推荐版本说明
JDK1.8 或 11SpringBoot 2.x 对这两个版本支持最好
Maven3.6.x3.8以上偶尔会遇到镜像源问题
SpringBoot2.7.x不要用3.x,3.x部分依赖用法变了,很多教程过时
MyBatis2.3.x(spring-boot-starter)配合SpringBoot 2.7完全没问题
MySQL5.7 或 8.0.x注意驱动名差异很大
Vue2.6.x 或 2.7.x如果你更熟Vue3也可以用,但2.x生态资料多
Element UI2.15.x配合Vue 2使用,表单表格开箱即用
Node.js14.x 或 16.x18版本太高,安装某些依赖会报OpenSSL错误

提示:如果你坚持用SpringBoot 3.x,对应的JDK必须是17以上,同时MyBatis依赖要换成mybatis-spring-boot-starter-3.x,数据库驱动也要选新版。这里面的很多坑对于一个毕设阶段的项目来说完全没有必要自找麻烦,直接锁死SpringBoot 2.7.18最省事。

1.3 从零开始初始化一个后端工程

用IDEA新建Spring Initializr项目,SpringBoot版本选2.7.18,依赖勾选:Spring Web、MyBatis Framework、MySQL Driver、Lombok。要是你已经建好了项目,也没关系,手写pom依赖也行。

一个干净的pom.xml核心依赖长这样:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

这里有个非常容易被忽略的细节:SpringBoot 2.7.x 的mysql-connector-j是8.0.33版本,数据库连接串的驱动类应该用com.mysql.cj.jdbc.Driver,而不再是老的com.mysql.jdbc.Driver。同时记得在连接串上加serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true,否则你会遇到时区警告和各种SSL连接报错。

2. 数据库设计:订单、场次、座位这些表怎么建模才不返工

影院订票系统看起来业务不算复杂,但真正开始建表的时候,很多人会想当然地把“场次”和“影厅座位”混在一起,导致后面写选座逻辑时疯狂返工。这一节我从实际执行的顺序入手,把核心表结构和它们的关系捋一遍。

2.1 核心表关系与建表顺序

推荐按照依赖关系从基础表开始建,顺序是:

  1. 用户表(sys_user)
  2. 电影表(film)
  3. 影厅表(hall)
  4. 场次表(session)
  5. 座位表(seat)
  6. 场次座位状态表(session_seat)—— 很多新手漏掉的一张表
  7. 订单表(orders)
  8. 订单明细表(order_detail)

整条业务链路的核心关系是:用户创建订单,订单关联场次,场次关联影厅和电影,座位的占用情况则由session_seat表记录。

这种设计最关键的地方在于:座位并不是实时从seat表去锁的,而是每个场次生成时,给该场次复制一份座位状态数据,用户选座时锁定的是这份“场次座位状态”里的行。这样不同场次之间的座位状态互不影响,同一个影厅在不同时间段可以分别管理。

2.2 建表SQL中容易忽略的字段约束

直接给一段核心的建表SQL,你可以拿去改:

CREATE TABLE `film` ( `film_id` int NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '电影名称', `cover_url` varchar(255) DEFAULT NULL COMMENT '封面图URL', `director` varchar(50) DEFAULT NULL COMMENT '导演', `actors` varchar(500) DEFAULT NULL COMMENT '主演', `duration` int DEFAULT NULL COMMENT '片长(分钟)', `release_date` date DEFAULT NULL COMMENT '上映日期', `description` text COMMENT '简介', `status` tinyint DEFAULT '1' COMMENT '1上映 0下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`film_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `session_seat` ( `id` int NOT NULL AUTO_INCREMENT, `session_id` int NOT NULL COMMENT '场次ID', `seat_id` int NOT NULL COMMENT '座位ID', `row_no` int NOT NULL COMMENT '排号', `col_no` int NOT NULL COMMENT '列号', `seat_type` tinyint DEFAULT '1' COMMENT '1普通 2情侣 3VIP', `status` tinyint DEFAULT '0' COMMENT '0可售 1已售 2锁定', `version` int DEFAULT '0' COMMENT '乐观锁版本号', PRIMARY KEY (`id`), UNIQUE KEY `uk_session_seat` (`session_id`, `seat_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里几个字段的设计一定要想清楚:

  • status为什么要区分“已售”和“锁定”?因为用户在选座页面停留的时候,座位应该先被“锁定”一段时间(比如15分钟),防止被别人抢走;如果超过了锁定时间,状态要自动回滚为“可售”。这就是“预占”的概念。
  • version字段是我建议加的。后端在做座位抢购时可以用乐观锁 —— 靠UPDATE ... SET status = 1, version = version + 1 WHERE id = ? AND status = 0这种条件更新来保证同一个座位不会被两个人同时买到。毕设答辩时提到这个设计,评审老师通常都会点头。

订单表也单独说一下。状态字段是核心,我建议用order_status来设计,取值范围包括:0待支付、1已支付、2已出票、3已取消、4已退款。

CREATE TABLE `orders` ( `order_no` varchar(32) NOT NULL COMMENT '订单号', `user_id` int NOT NULL, `session_id` int NOT NULL, `total_price` decimal(10,2) NOT NULL, `order_status` tinyint DEFAULT '0', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `pay_time` datetime DEFAULT NULL, `expire_time` datetime DEFAULT NULL COMMENT '锁定过期时间', PRIMARY KEY (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

订单号建议用“时间戳+随机数”或者“日期+自增序列”的方式生成。不要用自增主键直接暴露给用户看,订单号会到处出现,比如回调、查询、对账,设计成独立字段更灵活。

2.3 座位文件的导入方式

还有一个看起来很麻烦但其实很简单的功能:座位初始化。你不需要在页面上一个一个建座位,直接用SQL脚本插入即可。比如一个影厅10排、每排12个座位,用一段循环插入脚本就能搞定。

DELIMITER $$ CREATE PROCEDURE init_session_seat(IN p_session_id INT, IN p_hall_id INT) BEGIN DECLARE r INT DEFAULT 1; DECLARE c INT DEFAULT 1; WHILE r <= 10 DO SET c = 1; WHILE c <= 12 DO INSERT INTO session_seat(session_id, seat_id, row_no, col_no, seat_type) VALUES(p_session_id, (SELECT seat_id FROM seat WHERE hall_id = p_hall_id AND row_no = r AND col_no = c), r, c, 1); SET c = c + 1; END WHILE; SET r = r + 1; END WHILE; END$$ DELIMITER ;

这段存储过程本身很简单,重点在于:场次排片完成后,立刻自动初始化对应场次的座位状态。实际项目中可以在排片接口里直接用Java代码循环调用插入,而不需要真的跑存储过程。两种方式都行,如果你的论文章节想写“存储过程”,就保留SQL脚本;如果想让代码更清晰,就在Java代码里写循环。

3. 后端业务逻辑:登录、选座、下单的接口设计思路

后端是整套系统的中枢。这部分我不会给你全量代码,而是挑几个最关键、答辩也最爱问的业务场景展开说清楚设计思路和实现要点。

3.1 登录鉴权:JWT还是Session?

传统Java Web课程里学的是Session+Filter的写法,但放到前后端分离的项目里,我推荐使用JWT(JSON Web Token)做无状态鉴权。原因主要有三个:

  1. 前端Vue项目和后端SpringBoot项目分开部署时,Session不便于跨域处理。
  2. JWT可以很方便地在后面的移动端扩展中直接复用。
  3. 毕设答辩时引出“Token机制”这个话题,有明显加分。

实现上,只需要引入一个JWT工具类(比如基于jjwt库),在用户登录成功后生成一个token,设置过期时间为24小时;拦截器里放行登录、注册相关接口,其余接口从请求头Authorization里解析token,解析成功就放行。

@Component public class JwtInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if ("OPTIONS".equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && stringRedisTemplate.hasKey("login:token:" + token)) { return true; } response.setStatus(401); return false; } }

拦截器配置在WebConfig里,指定拦截/api/**下的路径,同时排除/api/user/login/api/user/register/api/film/list这类公开接口。这里有一个非常容易踩的坑:跨域请求的预检请求(OPTIONS)一定要直接放行,不然前端调用后端的接口时,浏览器先发的OPTIONS请求就被拦截器拦截了,报401,前端页面上永远看到“请求失败”。

3.2 电影列表与详情的分页查询

用户端第一个主要接口就是电影列表。这个接口设计得是否规范,直接影响后面前端页面的开发效率。通常电影列表要支持:按名称模糊搜索、按状态筛选、分页查询,返回数据要带上封面URL、片长、上映时间等关键字段。

接口设计如下:

GET /api/film/list?pageNum=1&pageSize=10&title=流浪&status=1

后端用PageHelper分页插件,代码非常简洁:

public PageResult<FilmVO> listFilm(FilmQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List<FilmVO> list = filmMapper.selectFilmList(query); return new PageResult<>(list); }

PageResult统一封装分页结果,包括总条数、总页码、当前页数据列表。前端就只需要调用一次接口,拿到totallist两个字段就能完成分页组件的渲染。

3.3 选座与下单的并发安全

这是整个系统里最核心、也最容易写砸的部分。业务链路是:用户点击“选座购票”→ 进入场次座位图 → 选中座位 → 创建锁定订单 → 支付 → 状态更新。

这里要特别强调:座位锁定和订单创建必须是同一个数据库事务里的两步操作,不能先在界面上锁定座位,等用户下单时再检查座位状态。因为高并发场景下,两个人可能同时看到同一个座位是空的,然后同时提交订单。

我推荐的做法是:

  1. 用户选中座位后,前端调“预占座位”接口。
  2. 后端开启事务,同时执行:
    • session_seat表中选中的几行数据做条件更新:UPDATE session_seat SET status = 2, version = version + 1 WHERE id = ? AND status = 0
    • 如果更新的行数等于选中的座位数,说明全部锁定成功,继续创建订单,订单状态为待支付。
    • 如果更新的行数不等于选中的座位数,抛异常回滚,返回“座位已被购买”。
  3. 前端支付成功后,后端把座位状态从“锁定”改成“已售”,订单状态改为已支付。

乐观锁的条件更新之所以能防超卖,是因为数据库行锁保证了同一时刻只有一个事务能更新一行数据。第二个事务执行相同条件的UPDATE会匹配不到行(因为status已经不是0了),影响行数为0,于是判定抢座失败。

3.4 订单超时未支付的自动取消

很多同学做到支付就停了,没有再处理“用户锁定座位但一直不付款”的情况。这个功能如果不做,就相当于一个Bug:占着座位不付钱,其他人买不了。

实现思路有几种:

  • 最朴素的:每次查询座位图时,把“创建时间早于现在15分钟、状态为锁定”的座位自动重置为可售,同时把关联的待支付订单标记为已取消。这个方案不需要额外定时任务,但存在一个缺点,只有有人查询到这个场次才会触发清理。
  • 更正规的做法:使用DelayQueue和定时扫描任务。建议用一个Spring定时任务,每5分钟扫描一次订单表,把超时未支付订单置为已取消,同步释放座位。
@Component public class OrderTimeoutTask { @Scheduled(fixedDelay = 5 * 60 * 1000) public void cancelExpiredOrders() { // 1. 查询所有状态为待支付、且expire_time < now 的订单 // 2. 将订单状态改为已取消(order_status = 3) // 3. 将订单关联的session_seat状态重置为0 } }

定时任务里别忘了加@EnableScheduling注解,否则不生效。这个逻辑写完以后,可以手动把数据库里某条订单的expire_time改成过去时间,然后跑一次任务验证,这是答辩现场很加分的演示环节。

3.5 编码上容易被忽略的坑

几个我在联调中反复踩过的坑,先写在这里帮你避雷:

  • 金额字段不要用float/double。票价的精度要求高,用BigDecimal做运算。MySQL里对应decimal(10,2)类型。
  • 后端统一返回体。每个接口不要直接返回数据库实体,最外层包一层统一数据体{ "code": 200, "message": "success", "data": ... }。前端只需要统一处理一次响应,后续加接口非常高效。
  • 配置跨域。SpringBoot里直接注册一个CorsFilterBean,允许所有来源的GET、POST、PUT、DELETE请求,同时设置允许请求头Authorization,否则前端带token的请求过不来。
  • 接口不可缺少参数校验。可以使用@Validated+@NotNull这些注解,但更基础的是先做好空值判断。答辩时老师问“如果前端传参数少了怎么办”,你要能回答出参数校验方案,说明你考虑过健壮性。

4. Vue前端跑起来:环境搭建、路由设计、打包白屏的完整复盘

前端部分,我假设你已经会创建Vue项目,这里重点展开那些让很多人崩溃的细节:路由配置怎么设计、登录状态怎么保存、打包部署后为什么白屏、m3u8视频播放怎么打通。

4.1 项目结构和环境搭建

用Vue CLI创建一个Vue 2项目:

npm install -g @vue/cli vue create cinema-frontend

创建时选择:Babel、Router、Vuex、Linter。之后安装开发依赖:

npm install element-ui --save npm install axios --save npm install js-cookie --save

这里有个老生常谈的问题:如果你用的是Node 17以上版本,npm install可能会报Error: error:0308010C:digital envelope routines::unsupported。这个错误的原因是Node 17+默认启用了OpenSSL 3.0,而旧版webpack还在用MD4哈希算法。解决办法是设置环境变量后再执行:

export NODE_OPTIONS=--openssl-legacy-provider

或者直接换用Node 16版本,一劳永逸。我在开发这套项目时用的就是Node 16.20.2,从来没见过这个OpenSSL报错。

4.2 路由设计与导航守卫

路由设计要区分用户端和管理端。用户端页面包括:首页、电影列表、电影详情、场次选择、选座、订单确认/支付、我的订单、个人中心。管理端页面包括:登录页、电影管理、场次管理、订单管理。

router/index.js里配置路由meta信息,区分哪些页面需要登录权限、哪些是管理端专属:

const routes = [ { path: '/', component: Home, meta: { title: '首页' } }, { path: '/film/:id', component: FilmDetail }, { path: '/login', component: Login }, { path: '/admin', component: AdminLayout, meta: { requiresAdmin: true }, children: [ { path: 'film', component: FilmManage }, { path: 'session', component: SessionManage } ] } ]

导航守卫用来实现“未登录不能进”的逻辑:

router.beforeEach((to, from, next) => { const token = Cookie.get('token') if (to.meta.requiresAuth && !token) { next('/login') } else if (to.meta.requiresAdmin) { // 检查用户角色判断是否管理员 if (store.state.user.role !== 1) next('/') else next() } else { next() } })

这里有个坑:刷新页面时Vuex里保存的用户信息会丢失,所以你需要从Cookie或者localStorage重新读取用户信息。比较好的方案是登录成功后除了存token,把用户基本信息也序列化放进localStorage;路由守卫里从localStorage读取并初始化Vuex。

4.3 axios封装:请求拦截器和响应拦截器

axios不能直接用,一定要封装一层。我在src/utils/request.js里统一创建axios实例,设置baseURL/api,这样前端所有接口请求都走相对路径,后面部署时只需要通过代理配置把/api转发到后端域名,不用改动业务代码。

const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = Cookie.get('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { Cookie.remove('token') router.push('/login') } return Promise.reject(error) } )

拦截器一定要写,不然每个页面请求都要手动加Header和判断状态,代码会非常臃肿。同时,响应拦截器里统一处理401跳转登录,这个在答辩演示未登录访问时是一个亮眼的细节。

4.4 选座页面怎么渲染座位图

选座页是整个前端交互最复杂的部分。我的做法是:

  • 页面加载时调用接口,拿到该场次的座位数据列表,结构类似[{seatId: 1, rowNo: 1, colNo: 1, status: 0}, ...]
  • mounted生命周期里,把座位列表转成一个二维数组seatMap[row][col],数据结构以“排”为行、以“列”为列。
  • 渲染时用两层循环,每排渲染成一个div.flex,每个座位用一个div.seat-item渲染,通过状态字段控制颜色(可售灰色、锁定中橙色、已售红色、当前选中蓝色)。
  • 点击座位时,判断状态,可售则切换为选中状态,同时维护一个已选座位的数组。

选座完成后点击“立即购买”,把选中的座位ID、场次ID传给后端,走后端预占接口。预占成功后前端再跳转到支付确认页。这里的交互状态管理可以用Vuex,也可以直接在页面组件内部维护,取决于你选座和订单确认是不是同一个页面。

4.5 视频预告片的m3u8播放方案

热搜词里出现了“vue播放m3u8”“vue视频m3u8”,大概率就是在这个项目里要做电影预告片播放时遇到的。m3u8是HLS流媒体协议的视频索引文件格式,浏览器原生<video>标签不支持直接播放,需要引入hls.js这个库。

npm install hls.js --save

播放器封装成组件,核心代码如下:

<template> <video ref="video" controls autoplay style="width: 100%"></video> </template> <script> import Hls from 'hls.js' export default { props: { src: { type: String, required: true } }, mounted() { if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(this.src) hls.attachMedia(this.$refs.video) } else if (this.$refs.video.canPlayType('application/vnd.apple.mpegurl')) { // Safari 原生支持 this.$refs.video.src = this.src } } } </script>

当然,很多人其实没有真实的m3u8视频源,毕设又要求能播。那么你可以做两手准备:数据库里存一个视频URL字段,接口返回的数据里如果URL以.m3u8结尾就走hls.js逻辑,否则直接设置给video标签加载。兼容两种播放源,演示的时候就不会因为视频格式不对而卡壳。

4.6 vue打包后部署到服务器上的布局异常

热搜词里“vue 打包后 布局异常”我也是看了好几遍,这个问题的出现频率实在太高了。

先说为什么会白屏或者样式丢失。Vue CLI默认构建出的静态资源路径是绝对路径/js/app.js,如果你的前端文件部署在Tomcat的webapps下的某个子目录里,而不是域名的根路径下,那么这些绝对路径就全部404了,页面资源加载不出来,自然白屏。

解决办法是在vue.config.js里设置:

module.exports = { publicPath: './', assetsDir: 'static', outputDir: 'dist' }

publicPath: './'表示构建后的资源路径使用相对路径,这样部署到任意子目录都能正常加载。这一行配置能解决90%的打包后白屏和布局问题。

布局异常还有一个常见情况:打包后刷新页面404。因为Vue Router默认是history模式,刷新某个子路径时,服务器上没有对应的物理文件,于是返回404。解决办法有两种:

  • router改成hash模式,路径上会带#号,但部署最简单。
  • 在nginx或者Tomcat里配置路由重写,把所有请求重写到index.html

毕设阶段如果不追求路径美观,我建议直接用hash模式,少折腾。

5. 拿到完整源码后如何一天内跑起来并改成自己的项目

如果你是从网上下载别人分享的SpringBoot+Vue影院订票系统源码,大概率会遇到“看着什么都有,但就是跑不起来”的问题。这一节我把从零启动一个别人项目的完整流程和注意事项写清楚。

5.1 后端启动的先后顺序

先确认本地环境:JDK 1.8、Maven 3.6+、MySQL 5.7/8.0、IDEA旗舰版或社区版。

第一步,用IDEA打开后端工程,等待Maven自动下载依赖。如果IDEA用的是国内网络,第一次下载可能很慢或者超时,建议在Maven的settings.xml里配置阿里云镜像:

<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

第二步,创建数据库并导入SQL脚本。右键SQL文件在IDEA的Database面板里执行,或者用MySQL命令行执行:

mysql -u root -p < cinema.sql

导入成功后,确认sys_user表有一条管理员账号记录,默认密码可能是123456,也可能是内存MD5加密后的值。如果源码里默认密码是加密存储的,要找到源码里的加密类,手动生成一个admin123的密文更新进去。这是很常见的卡点之一。

第三步,修改application.yml里的数据库账号密码:

spring: datasource: url: jdbc:mysql://localhost:3306/cinema?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的密码

改完启动Application类,后端如果正常启动,控制台会出现Tomcat started on port(s): 8080。

5.2 前端启动与本地联调

前端工程打开后,先安装依赖:

npm install

如果npm install报了一堆错,优先检查Node版本。建议用nvm管理Node版本,切换到16.x,重新rm -rf node_modules后再次安装。依赖安装成功后:

npm run serve

这里有个关键配置:Vue CLI开发服务器默认在8080端口,而SpringBoot后端默认也是8080,必定冲突。两步解决:

  1. 在后端application.yml里把端口改成9090,或者在前端vue.config.js里设置port: 3000
  2. vue.config.js配置开发代理:
module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true } } } }

这样前端开发服务器监听3000端口,所有/api开头的请求自动转发到9090后端。前后端分离联调的基础就建立起来了。

5.3 换皮改项目:从“影院订票”到“自己风格”的合理改动方式

很多同学不希望直接交别人的项目,想改出自己的风格。有几个建议方向,性价比很高:

  • 改主题和文案。前端Element UI的primary颜色变量一改,整体风格就变了。比如把默认蓝色改成紫色或黑色,影院名称换成你自己起的品牌名,Logo换掉,这些改动不涉及业务逻辑,难度低、效果明显。
  • 加一个简单功能。比如原来没有“电影评论”,你可以加一张评论表,加一个FilmCommentController,前端详情页加一个评论列表和发表评论的输入框。这个功能独立于核心链路,实现起来难度不大,但答辩的时候能讲出“我在原项目的基础上增加了XX功能”,比原封不动交上去强非常多。
  • 优化一个已有的模块。比如把原来的明文密码改成BCrypt加密存储,把登录日志写入一张独立表。这类优化点虽然小,但很容易用一两句话讲清楚价值。

6. 从代码到论文:接口文档、部署演示和答辩引导

项目跑通之后,真正的“毕业设计工作”刚开始——你需要能把它讲清楚。接口文档、部署演示、论文配图,这三样是决定答辩顺不顺利的关键。

6.1 接口文档怎么整理最省力

不要手动用Word写接口文档,直接使用Apifox。后端Controller写完,导入SpringBoot项目就能自动生成接口列表,然后逐个补充每个接口的请求参数和返回示例。

一个标准的接口定义应该包含:

接口名称请求方法路径请求参数返回说明
用户登录POST/api/user/loginusername, password返回token和用户信息
电影分页列表GET/api/film/listpageNum, pageSize, title返回分页数据
获取场次列表GET/api/session/list?filmId=1filmId返回场次及影厅信息
锁定座位POST/api/order/preLocksessionId, seatIds返回订单号
支付订单POST/api/order/payorderNo返回支付结果

接口文档至少要包含“请求示例”和“成功响应示例”。响应里统一放code/message/data结构,前端拿起来方便,文档看起来也专业。

6.2 本地演示环境和云服务器部署二选一

答辩前至少要提前一周把系统跑顺。演示方式推荐二选一:

  • 本地演示(最稳妥):提前在笔记本上启动后端和前端,把所有演示案例预置好数据。注意演示前把浏览器缓存清掉,不要打开一堆没用的标签页,避免现场卡顿。本地演示对网络没有依赖,不会出幺蛾子。
  • 云服务器部署(加分项):如果时间充裕,可以买一台最低配的云服务器,把项目打包部署上去。后端用mvn clean package打成jar包,nohup java -jar cinema.jar &启动;前端部署到nginx,用try_files $uri $uri/ /index.html解决Vue Router history模式刷新404问题。部署成功后,答辩时直接用手机访问演示,侧面证明你具备前后端分离部署能力,这个加分点非常实在。

6.3 答辩时能被问到的高频问题清单

我把历届学生被问过的问题整理成几个方向,你提前准备:

  1. 为什么选择前后端分离架构?答:前端部署在nginx,后端部署在Tomcat,后端只提供Restful API,前后端可分别横向扩展;开发效率高,解耦清晰。
  2. 你的系统怎么防止SQL注入?答:使用MyBatis预编译,#{}会生成PreparedStatement,参数作为占位符传入;对比${}直接拼接字符串会存在注入风险。数据访问全部走Mapper接口,没有手写字符串拼接SQL。
  3. 同一场次的两个用户同时选同一个座位,系统如何处理?答:数据库乐观锁+条件更新,UPDATE ... WHERE status = 0,影响行数为0则说明座位刚刚被抢走。这是一个标准的并发控制方案。
  4. 如果用户下单后不支付怎么办?答:定时任务扫描超时未支付订单,释放座位;同时订单标记为已取消。
  5. 用户的密码是怎么存储的?答:不存明文,使用BCrypt加密,每次校验用matches方法比对。如果你原项目没有实现,这里建议改成BCrypt,不然这个问题会卡住。

6.4 论文配图怎么做才高效

论文里最费时间的就是几张架构图、流程图和ER图。我强烈建议别用专业画图工具一个一个画,而是用几个技巧提高效率:

  • 数据库ER图:直接用IDEA Database面板的Diagram功能,能自动生成表关系图,导出图片后直接放进论文。
  • 系统架构图:用ProcessOn或者draw.io画,画出浏览器→Vue前端→Nginx→SpringBoot后端→MySQL的箭头关系即可。画图时保持颜色统一,不要花哨。
  • 核心流程图:重点画“用户购票”这一段——选座→预占座位→创建订单→支付→出票。这个流程是论文的核心业务线,画得清晰,评审老师一下就能看懂你做的业务闭环。

7. 毕设之后这套项目的价值延展

做完影院订票系统,并不意味着“交完毕设就扔”。这套项目的价值还可以延续到几个方向。

第一,简历上的项目经验。大多数应届求职者简历里的项目都写着“SpringBoot+Vue”,但很多人连部署都不会。如果你能把影院订票系统做成“已上线运行、可手机访问、包含JWT鉴权、乐观锁并发控制、定时任务”这样的描述,在技术面试里很容易跟面试官展开话题。

第二,给这个项目做持续迭代。比如加一个优惠券模块、加一个基于Redis的当日票房排行、加一个基于WebSocket的实时座位状态推送,每一个点都可以单独做成一篇文章分享到掘金、CSDN或者知乎,连续写几篇实战文章,技术博客的起步就有素材了。

第三,如果你将来做的是Java开发方向,这套项目里的很多代码体感会成为你的基本功。比如事务的粒度怎么控制、乐观锁和悲观锁分别在什么场景下使用、JWT续期怎么设计、定时任务怎么处理边界条件。这些不是你背面试题能背会的,必须真的写一遍、跑一遍、踩过坑才印象深刻。

我在做这个项目时,印象最深的就是选座接口的并发问题。第一次写的时候没有加乐观锁,用JMeter跑了两百个并发请求买同一个座位,结果产生了20多个订单都成功了,座位超卖得离谱。后来加上WHERE status = 0条件更新之后重新压测,再也没有超卖过。那一刻你会真正理解数据库行锁和事务到底意味着什么,而不是停留在理论层面背概念。这种体会,才是做毕业设计最值钱的部分。

希望这份拆解能帮你把项目顺利跑通,答辩顺利通过。如果你在实操过程中遇到版本不对、依赖冲突、前端乱码这类问题,先别急着怀疑人生,检查一下自己的版本和环境是否和我上面说的一致,多数问题其实都能从环境上找到答案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 17:58:40

跨境电商物流渠道选择与评估模型搭建指南

1. 跨境物流渠道选择的现实困境做跨境电商的朋友们应该都深有体会&#xff0c;每次打开物流服务商的报价单&#xff0c;密密麻麻的渠道选项简直让人眼花缭乱。空运专线、海运快船、邮政小包、商业快递...每种渠道的时效、价格、稳定性差异巨大&#xff0c;更别提不同国家、不同…

作者头像 李华
网站建设 2026/9/15 17:57:54

图覆盖与软件测试充分性:从控制流图到覆盖准则全解析

很多人刚开始学软件测试时&#xff0c;对“覆盖率”三个字的第一反应是&#xff1a;覆盖率越高&#xff0c;测试质量越好。这个想法方向没错&#xff0c;但很容易让一个人走进死胡同——因为只看百分比&#xff0c;根本说不清楚“缺口到底在哪里”。真正把测试思路打通、让我知…

作者头像 李华
网站建设 2026/9/15 17:56:03

douyin-downloader 完整指南:批量下载抖音无水印视频与主页作品

douyin-downloader 完整指南&#xff1a;批量下载抖音无水印视频与主页作品 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallb…

作者头像 李华