1. 项目概述
1.1 这个系统到底是什么
先把这个项目说清楚。洋州影院购票管理系统,从名字就能看出来,这是一个围绕影院售票场景做的前后端分离项目,后端用SpringBoot,前端用Vue,数据库用MySQL。我在本地把它完整跑起来之后,第一感受就是:这玩意儿确实是冲着毕业设计来的,但又不只是那种糊弄事儿的增删改查,里面该有的业务闭环基本都有。
很多同学看到"影院购票管理系统"这名字,第一反应就是"这不就是选个座位、下单付款吗"。实操下来你会发现,事情没那么简单。一个真正能拿来当毕设或者课设的项目,至少要覆盖用户端、管理端两条线,用户端要有电影浏览、选座、下单、订单管理,管理端要有电影排片、场次管理、座位价格策略、订单统计。这套系统把这些模块都串起来了,而且前后端交互用的是标准RESTful接口,数据库用的是MySQL,整个技术栈非常"教科书",非常适合用来应付答辩和展示。
我这篇文章不会给你念PPT式的功能列表,而是直接把我从下载源码、配置环境、启动项目到二次开发的全过程记录下来。你会看到我在哪里踩了坑,表结构为什么要这么设计,Vue路由为什么那样写,以及如果你想拿它做毕设,应该在哪些地方补功能、加亮点。
1.2 适合谁来用
如果你是下面这几类人,这篇文章可以直接照着操作:
- 准备做毕业设计的本科生:需要一套结构清晰、有完整前后端、有业务深度的系统源码做底子,然后自己加一点功能、换一套皮肤,就能变成自己的东西。
- Java后端方向的实习生或应届生:想找一个真实的、能跑通的项目来练手,理解SpringBoot + Vue + MySQL三者是怎么协作的,而不是只刷八股文。
- 课设作业比较急的同学:时间紧、任务重,需要一个开箱即用、文档相对完整的项目,改个名字、改个Logo就能交差。
- 想学前后端分离开发模式的初学者:这套系统里有很多值得拆解的代码细节,比如Token鉴权、Axios封装、动态路由、分页查询等等。
我必须提前打个预防针:源码能跑只是起点,你要在答辩的时候讲清楚"为什么这样设计",那才是拿高分的关键。后面我会把我认为最该讲清楚的设计点挨个拆开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心需求与技术栈拆解
2.1 影院购票的业务闭环分析
在动手写代码之前,先想清楚业务。影院购票这个场景,和我们平时在电商网站买东西本质上是同一套逻辑,但又有一些电影行业特有的规则。
我梳理了一下,这套系统的核心业务链路是这样的:
- 用户注册登录,系统保存用户基本信息。
- 用户浏览电影列表、查看电影详情(包括海报、简介、演员、时长、上映日期)。
- 用户选择某部电影,查看该电影在某一天的场次安排(也就是排片表)。
- 用户进入某个场次,看到影厅的座位布局,选座并提交订单。
- 系统生成订单,用户可以支付(这里通常是模拟支付),也可以在个人中心查看历史订单。
- 管理员登录后台,维护电影信息、排片场次、影厅座位、订单数据,还可以查看统计数据。
这中间最核心的难点是座位选择和订单状态的联动。一场电影的座位是固定的,比如一个影厅有8排,每排10个座位,那么同一场次里,一个座位只能被一个未取消的订单占用。如果两个人同时选同一个座位,系统必须保证只有一个能成功下单。这里面牵扯到数据库事务、乐观锁/悲观锁、Redis分布式锁等等,都是答辩时的加分点。
另外,影院购票还有一个电商没有的特殊概念——排片(场次)。一个影厅一天可以排很多场电影,每场电影对应一个时间段。排片表设计得好不好,直接影响后面选座逻辑的复杂度。这套系统采用的是比较经典的方案:电影表(film)、场次表(session)分开,场次表里存电影ID、影厅ID、放映时间、语言版本、价格系数等信息。
2.2 技术栈选型:为什么是SpringBoot + Vue + MySQL
这套组合在近几年的毕设和课设里几乎成了"标配中的标配",原因有三:
第一,SpringBoot降低了很多后端开发的成本。 你用传统的Spring + SpringMVC写项目,要手动配一堆XML,而SpringBoot通过自动配置把大部分样板代码省掉了。对于学生来说,不用花大量时间在环境配置上,可以直接聚焦业务代码。而且SpringBoot内置Tomcat,打一个Jar包就能跑,部署演示都很方便。
第二,Vue作为前端框架,学习曲线相对平缓。 Vue的双向绑定和组件化开发,比用jQuery写DOM要舒服太多了。特别是Element UI这种组件库,表格、表单、弹窗、分页全都封装好了,做管理后台简直就是"拖拖拽拽"的事。前端开发效率很高,界面也说得过去,答辩的时候视觉效果比传统JSP项目高出一个档次。
第三,MySQL是最主流的关系型数据库。 影院购票系统里的数据关系非常典型:用户-订单-电影-场次,一对多和多对一的关系很清晰。用MySQL的外键、事务、索引这些特性,绕不开也躲不掉,而这恰好是数据库课设和面试最常考的点。
还有一个很重要的原因是资料多。这套技术栈出问题的时候,搜索引擎上一抓一大把解决方案,对于没有太多排错经验的学生来说,这一点能救命。
2.3 源码整体目录结构与模块划分
把项目解压之后,你会看到两个主目录:一个是后端(通常叫backend或cinema-server),一个是前端(通常叫frontend或cinema-web)。
后端目录的核心结构大致如下:
code复制src/main/java/com/xxx/cinema
├── controller // 控制层,接收前端请求
├── service // 业务层,核心逻辑都在这
├── mapper // MyBatis/MyBatis-Plus的Mapper接口
├── entity // 数据库实体类
├── config // 配置类,例如跨域、拦截器、MyBatis配置
├── common // 公共返回结果、异常处理、工具类
└── utils // JWT工具、加密工具等
前端目录的核心结构大致如下:
code复制src
├── api // 封装axios请求接口
├── router // 路由配置
├── store // Vuex状态管理
├── views // 页面组件
│ ├── user // 用户端页面
│ └── admin // 管理端页面
├── components // 公共组件
└── utils // 请求封装、token存储等
这种按模块分包的方式,本身就是很规范的项目结构。答辩的时候老师如果问"你项目是怎么分层的",你可以直接指着目录说清楚每一层负责什么。这不光是代码规范问题,还体现了软件工程的分层设计思想。
3. 环境准备与项目启动全流程
3.1 环境版本与数据库初始化
在启动之前,先对齐环境版本。我用的是:
- JDK 1.8(SpringBoot 2.x版本必须用8,如果你用JDK 17跑会报各种反射错误)
- Maven 3.6+(后端依赖管理)
- Node.js 14+(前端构建,我用的14.21,Node 18也能跑但个别依赖会警告)
- MySQL 5.7 / 8.0(我本机是8.0,需要注意字符集设置)
- IDEA + VSCode(后端用IDEA,前端用VSCode,这个纯粹看个人习惯)
数据库初始化是大部分人第一个卡住的地方。项目源码里一般会附带cinema.sql或sql/init.sql,你打开这个文件会发现里面不仅有建表语句,还预置了一些电影数据和管理员账号。
我的操作步骤是:
- 启动MySQL服务,用root账号登录。
- 创建一个数据库,名字和配置文件保持一致,通常是
cinema或cinema_system。 - 执行
source命令导入SQL文件:
bash复制mysql -u root -p
CREATE DATABASE cinema DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE cinema;
SOURCE /你的路径/cinema.sql;
注意:如果你用的是MySQL 8.0,密码加密方式默认是
caching_sha2_password,而项目的数据库驱动如果是mysql-connector-java的5.x版本,连接会报错。解决办法有两个:要么把驱动版本升级到8.x,要么在创建用户时指定mysql_native_password。我个人建议直接升级驱动,因为改数据库用户密码策略有时候会引发别的问题。
导入之后,你可以先打开数据库看一眼表结构。重点看这几张表:
user:用户表,注意密码字段是MD5加密后的值。film:电影信息表,包括海报URL、简介、上映时间、状态等。session:场次表,包括电影ID、影厅ID、放映时间、票价系数等。order:订单表,包括用户ID、场次ID、座位信息、订单状态、支付状态。seat:座位表,或者是一个JSON字段来存储选座信息,不同源码设计不一样。
3.2 后端启动:配置文件与常见报错
后端启动的第一步是修改application.yml(或application.properties)里的数据库连接信息:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cinema?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 你的密码
这里要注意serverTimezone=Asia/Shanghai这个参数。如果漏了,数据库连接通常会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized之类的乱码错误,原因就是MySQL 8.0的时区设置和JDBC驱动不一致。解决方案就是加上时区参数,或者把useSSL=false也加上,避免证书警告弹个没完。
改完之后,在项目根目录执行:
bash复制mvn clean package -DskipTests
或者直接用IDEA右侧的Maven面板,双击clean再install。我第一次跑的时候,mvn package过程里下载依赖特别慢,后来换成了国内镜像源就快多了。如果你也在国内,建议在settings.xml里把mirror指向阿里云仓库,下载依赖的速度会快好几倍。
启动方式有两种:
- 在IDEA里直接运行主类(带有
@SpringBootApplication注解的那个类)。 - 把项目打包成Jar,执行
java -jar target/cinema-0.0.1-SNAPSHOT.jar。
启动成功之后,控制台会打印SpringBoot的Logo和Tomcat端口号。如果端口被占用,改server.port就行,但要注意前端配置的接口地址也要同步改。我习惯把端口固定成8080,或者干脆设置成9090避免冲突。
3.3 前端启动:npm依赖与跨域配置
前端部分是用Vue CLI(或Vite)构建的。进入前端目录后:
bash复制npm install
npm run serve
npm install这一步最容易出问题。由于不同Node版本和npm版本差异,有时候安装完依赖之后启动项目会报各种Module not found或者SyntaxError。我遇到过一个很典型的情况:某次我用Node 16安装依赖,结果npm run serve直接报错,换成Node 14重新npm install就正常了。所以如果你在别的电脑上遇到过类似问题,先考虑换Node版本,这一步比去找代码问题省时间得多。
前端启动后,默认访问http://localhost:8080,但SpringBoot后端也占用了8080,所以通常前端的端口配置在vue.config.js里,比如:
js复制module.exports = {
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
};
这里的关键是代理配置。如果你不配置代理,直接用axios请求http://localhost:8080,会遇到跨域问题。浏览器会拦截响应,前端拿不到数据。配置了proxy之后,你在前端代码里写/api/login,实际请求会被转发到http://localhost:8080/api/login,浏览器看起来就是同源的,完美避开跨域。
如果后端没有部署在8080,或者换了一台服务器,只需要改target这一个字段。
4. 前后端核心交互流程与代码解析
4.1 用户登录与Token鉴权机制
几乎所有的管理类系统都绕不开登录鉴权。这套影院系统的登录逻辑,走的是目前前后端分离项目最主流的JWT方案。
流程是这样的:
- 用户在登录页输入用户名和密码,前端把密码做一次MD5加密(或者后端收到后加密),然后把账号和加密后的密码POST到后端接口。
- 后端根据用户名查出用户,比对密码是否一致。
- 如果一致,后端起一个JWT Token,把用户ID、用户名、角色(USER或ADMIN)封装进去,返回给前端。
- 前端把Token保存在本地(localStorage或Vuex里),之后每次请求都在请求头里带上
Authorization: Bearer <token>。 - 后端拦截器校验Token,如果Token有效就放行,无效就返回401,前端根据401跳回到登录页。
我们来看一下后端拦截器大概的代码逻辑:
java复制public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
try {
Claims claims = JwtUtils.parseToken(token);
request.setAttribute("userId", claims.get("userId"));
request.setAttribute("role", claims.get("role"));
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
response.setStatus(401);
return false;
}
拦截器里要特别注意放行OPTIONS预检请求,否则前端跨域请求会失败。这个细节很多源码都没写,但实际开发中必须处理。
前端这边,Axios封装里也通常会有响应拦截器:
javascript复制service.interceptors.response.use(
response => {
return response.data;
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token');
router.push('/login');
}
return Promise.reject(error);
}
);
这样一来,Token过期或者非法时,用户会被自动踢回登录页,体验和交互上比较完整。答辩的时候,如果你能讲清楚JWT的签名原理、为什么服务端不需要存储Session、Token过期怎么办,老师对你的印象分会高很多。
4.2 电影列表、搜索与分页实现
影院首页的电影列表,是用户端最核心的入口。这个功能看起来简单,但其实里面包含了一个前后端分页联动的标准写法。
后端的分页查询写法通常是这样的:
java复制public PageResult<Film> getFilmList(int pageNum, int pageSize, String keyword, String category) {
LambdaQueryWrapper<Film> wrapper = new LambdaQueryWrapper<>();
if (StrUtil.isNotEmpty(keyword)) {
wrapper.like(Film::getTitle, keyword);
}
if (StrUtil.isNotEmpty(category)) {
wrapper.eq(Film::getCategory, category);
}
wrapper.eq(Film::getStatus, 1);
Page<Film> page = filmMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
return new PageResult<>(page.getTotal(), page.getRecords());
}
这里用到了MyBatis-Plus的分页插件。它的好处是,你只需要设置当前页码和每页数量,底层会自动拼LIMIT语句,返回总记录数也很方便。前端拿到数据后,把total给到分页组件,用户点击页码时重新请求对应页的数据。
这个交互链路的重点是:页码和每页条数必须从前端传参,不能写死。如果前后端参数名不一致,最常见的结果是第一页数据正常,点击第二页发现数据还是第一页的,或者直接空页面。调试的时候先看Network面板里请求参数是否带上了pageNum和pageSize。
前端列表页的关键代码会是这样:
html复制<el-pagination
@current-change="handlePageChange"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
layout="prev, pager, next"
/>
这样的分页组件一放,交互就完整了。
4.3 场次排片与选座下单:事务与锁
如果问这套系统里哪个模块最能体现"含金量",我一定投给选座下单。这个模块涉及前端座位交互、后端座位状态校验、订单创建等多个环节,最容易出Bug,也最容易在答辩时被追问。
前端部分,座位的状态通常有三种:可选、已选、已售。页面上渲染一个8x10的座位网格,用户点击座位时改变颜色,再把座位ID收集起来。
html复制<div
v-for="row in rows"
:key="row.id"
class="seat"
:class="{ selected: isSelected(row.id), sold: isSold(row.id) }"
@click="toggleSeat(row)"
>
后端接收的是用户选择的座位ID列表,以及场次ID。关键代码逻辑分为几步:
- 根据场次ID查出该场次的所有已售座位。
- 把用户提交的座位列表和已售座位做交集,如果存在交集,说明座位已被别人占走,直接返回"座位已被购买"。
- 如果没有交集,创建订单记录,同时把座位状态改成已售。
- 整个过程中,第2步和第3步必须是原子操作,否则在高并发场景下会超卖。
为了保证原子性,源码里通常用数据库事务来处理:
java复制@Transactional
public Order createOrder(OrderCreateRequest request) {
// 查询已售座位,加行锁 / 悲观锁
List<Seat> soldSeats = seatMapper.selectForUpdate(sessionId);
Set<String> soldSet = soldSeats.stream().map(Seat::getSeatCode).collect(Collectors.toSet());
for (String seatCode : request.getSeatCodes()) {
if (soldSet.contains(seatCode)) {
throw new BizException("座位已被选, 请重新选择");
}
}
// 生成订单...
// 批量更新座位状态...
}
selectForUpdate就是加了一把数据库行级悲观锁,保证同一时刻只有一个人能查到这个座位的真实状态。虽然这套系统作为毕设通常不会真正面对高并发,但把这个锁的机制写在代码里、讲在嘴里,就已经是超出平均水平的理解了。
5. 管理后台核心逻辑与数据可视化
5.1 电影与场次管理
管理后台是很多同学忽略的重头戏,因为大家做毕设时都急着把用户端页面做得花里胡哨,忽略了管理员功能。但实际上,答辩老师最关心的恰恰是后台数据怎么维护。没有后台管理功能,用户端的数据就是死的。
这套系统的管理后台覆盖了:
- 电影管理:新增电影、编辑电影信息、上传海报、上下架电影。
- 场次管理:为某部电影安排放映时间、选择影厅、设置票价。
- 订单管理:查看所有用户的订单,按状态筛选订单。
- 影厅管理:维护影厅信息,比如行数、列数、座位总数。
电影管理这块本质是一张CRUD表,但要注意海报上传的实现方式。因为前端是Vue,后端是SpringBoot,文件上传的接口一般会写在SpringBoot里,用MultipartFile接收文件,保存到本地目录或OSS,然后把URL存进数据库。如果你是在本地部署,最稳妥的做法是把海报图片放到一个后端可以访问的静态资源目录下,然后通过http://localhost:8080/images/xxx.jpg访问。
这里有个小坑:SpringBoot的静态资源默认只映射classpath:/static/下的文件。如果你把图片传到别的地方,访问会404。遇到这种情况,要么把文件放到src/main/resources/static/images/下面,要么自己写一个WebMvcConfigurer来做路径映射。我通常是加一个映射配置,这样图片路径更灵活,重启服务器也不会丢图片。
5.2 订单统计常用查询方式
管理后台里如果有一个数据统计页面,这个项目的完成度会显得高一个档次。统计逻辑通常包括:
- 每日订单量、每日票房收入。
- 电影票房排行榜。
- 按场次统计上座率。
这类统计在后端写SQL更高效,下面是一个按电影统计票房的例子:
sql复制SELECT f.title, COUNT(o.id) AS order_count, SUM(o.amount) AS total_amount
FROM film f
LEFT JOIN session s ON f.id = s.film_id
LEFT JOIN `order` o ON s.id = o.session_id
WHERE o.status = 1 AND o.pay_status = 1
GROUP BY f.id
ORDER BY total_amount DESC
LIMIT 10;
然后把查询结果封装成图表数据,前端用ECharts绘制柱状图或折线图。这里前端只需要把后端接口返回的数组原样丢给ECharts就行,工作量大头在后端SQL和联调。如果项目里没有统计页,你可以自己加一个,这属于"低成本高收益"的功能扩展。
5.3 前端路由权限控制
权限控制这部分我一直觉得很容易被忽视。很多学生做的前台和后台都在同一个Vue项目里,但后台页面只有管理员能用。如果你不控制路由,用户直接手动输入/admin路径,就能进后台了。
这套系统的路由权限控制方式是在Vue Router里加一个前置守卫:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token');
if (to.path.startsWith('/admin')) {
const role = localStorage.getItem('role');
if (!token || role !== 'ADMIN') {
next('/login');
} else {
next();
}
} else {
if (to.path === '/login' && token) {
next('/');
} else {
next();
}
}
});
注意,这里只是前端的显示层控制,真正的安全边界在后端。后端每个管理员接口都必须校验Token里的角色是否为ADMIN,否则用户用POSTMAN之类工具伪造请求一样能访问。前端路由守卫只是改善体验,防止普通用户看到后台界面,这点面试或答辩时一定要讲明白,因为很多人只做了前端控制,后端完全不设防,这是大忌。
6. 常见问题排查实录与避坑指南
6.1 数据库连接失败与驱动冲突
这个问题出现频率最高,而且报错信息五花八门。我整理一下常见的三件套:
报错一:Access denied for user 'root'@'localhost'
这个最简单,就是用户名或者密码写错了。去application.yml里仔细对比数据库账号密码,注意别带着空格。
报错二:Public Key Retrieval is not allowed
这个在MySQL 8.0 + 新版驱动下经常出现。解决方案是在JDBC URL后面加参数:
code复制allowPublicKeyRetrieval=true&useSSL=false
报错三:ClassNotFoundException: com.mysql.jdbc.Driver
这是驱动类名写错了。MySQL 8.0的驱动类名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver。如果你看到的代码里写的是旧类名,直接改成新的。
6.2 前端跨域问题与代理失效
如果你发现前端页面能打开,但所有接口都报CORS或ERR_CONNECTION_REFUSED,大概率是代理没起作用。
排查方法很简单:
- 打开浏览器的开发者工具,切到Network面板。
- 随便点一个请求,看Request URL里的路径是
http://localhost:3000/api/xxx还是http://localhost:8080/api/xxx。 - 如果是8080,说明代理没生效,检查
vue.config.js是否放在项目根目录,检查代理配置里的/api前缀是否和实际请求路径一致。
还有一个容易踩坑的地方:后端接口路径带不带/api前缀。有的项目后端Controller直接写@RequestMapping("/api/user"),有的写@RequestMapping("/user"),前端代理里/api的匹配规则也会不一样。代理配置的路径一定要和实际请求路径对得上,不然代理就是空设。
6.3 Token过期与登录状态丢失
做联调测试时,经常遇到一种情况:后端启动了一段时间后,前端操作突然报401。查来查去发现是Token过期了。
JWT默认有效期一般是2小时。这个时间对于演示足够,但对于前后端联调开发就不太够用。如果你不想频繁重新登录,可以把有效期改长一点,比如24小时,或者保持默认但前端在401时自动跳转登录页。
但有一点必须注意:修改Token有效期属于安全策略调整,演示归演示,如果项目要正式上线,建议还是搭配刷新Token机制,不要单纯把过期时间拉到很长。
6.4 座位状态不同步问题
我自己在测试时就遇到过:用户A下单了一个座位,但用户B刷新页面后仍然能看到这个座位是可选的。如果点选并下单,后端会返回"座位已被购买",用户体验很差。
这个问题的根源在于:前端页面的座位状态是进入页面时一次性加载的,不会实时刷新。解决思路有几个:
- 页面轮询,每隔几秒刷新一次座位状态。
- 下单失败后弹窗提示并重新刷新座位数据。
- 用WebSocket做实时推送(成本较高,毕设不推荐)。
对于毕设而言,第二种方案最实用。用户下单失败后,前端主动重新请求座位状态,把已售座位更新成灰色,体验立刻好了不少。
7. 基于源码二次开发与赛事升级建议
7.1 低成本高加分的新功能方向
如果你想把这套系统作为一个"有亮点"的毕设或者面试项目,光靠源码本身的功能是不够的,因为大家都是同一套源码,答辩老师可能都已经看过好几遍了。你需要做一些差异化改造。
根据我个人的经验,下面这几个方向性价比最高:
方向一:优惠券模块
增加一个优惠券表,用户下单选座时可以使用优惠券抵扣。这个功能牵扯到优惠券发放、领取、使用、过期判断,很适合展示你对业务流程的理解。
方向二:电影评论和评分
用户可以在电影详情页写评论、打评分。这个功能在边界上比较简单,无非是评论表和电影表之间多一个一对多关系,但在展示系统丰富度上很有效。
方向三:简单的票房统计大屏
在管理后台增加一个数据可视化页,用ECharts展示近七天的票房走势、电影热度排行、场次上座率。这个提交给答辩老师的视觉冲击力很强,而且你只需要在后端多写几个统计SQL,前端用现成的图表库渲染。
方向四:会员等级和积分系统
用户购票后获得积分,不同积分对应不同会员等级,不同等级享受不同折扣。这个功能能体现你对"规则引擎"的理解,虽然在系统里可能只是一个简单的if-else,但讲出来的时候可以上升到"策略模式"。
7.2 二次开发时最容易翻车的点
加功能的时候,有几点容易翻车,我给你打个预防针。
第一,数据库加表要注意字段命名规范。 不要随意用中文或者拼音缩写当字段名,要和现有表的结构保持一致。比如现有表主键都是id,新增的关联字段应该叫user_id、film_id,不要叫uid、fid。
第二,后端新增接口要统一返回结构。 如果项目里已经有一个Result类(一般包含code、message、data),你新增接口就必须用同样的结构返回,否则前端拿到数据的判断逻辑会乱掉。
第三,前端代码尽量在新页面里改,不要大面积动原有页面。 原版源码的页面组件可能耦合了很多逻辑,如果你在不熟悉的情况下大改样式和业务,很容易把原有的订单流程搞挂。稳妥的做法是先新增页面,再慢慢替换原页面。
7.3 答辩讲解路线建议
项目做出来只是第一步,答辩时候讲出来又是一门学问。根据我自己看过不少答辩现场的经验,我建议你这样组织讲解顺序:
- 先讲背景和需求:为什么影院需要这套系统?解决了哪些人工操作的痛点?不要上来就念功能列表,而是用一个"用户想看电影"的场景串联整个流程。
- 再讲技术选型:为什么要用前后端分离?为什么SpringBoot和Vue?数据库为什么选MySQL?讲讲优缺点。
- 接着讲核心模块:不要平铺直叙把所有功能都讲一遍,挑两三个核心模块深入讲。首选"选座下单"和"JWT鉴权",这两个模块最能体现技术深度。
- 最后讲一个自己加的功能:哪怕是简单做一个评论模块,也要讲清楚你遇到了什么问题、怎么设计表、怎么联调。
8. 我个人踩过的坑与最终建议
8.1 三个印象最深的细节坑
这套系统我在本机完整跑通之后,又试着部署到服务器上,期间踩过几个印象很深的坑。
第一个坑是服务器上MySQL的字符集。本地MySQL默认utf8mb4,但一些服务器默认是latin1,导入SQL后中文全部变成乱码。解决办法是建库时强制指定字符集,或者修改my.cnf里的character-set-server=utf8mb4。
第二个坑是前端项目打包后的静态资源路径。本地开发用npm run serve没问题,但部署到服务器上需要用npm run build生成dist目录。如果你把dist扔到Nginx下,而Nginx配置里没有处理history路由的回退,刷新页面就会404。需要在Nginx配置里加:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
这个坑不踩一次,你永远不知道为什么本地好好的,部署到线上就白屏。
第三个坑和Tomcat上传文件大小有关。默认SpringBoot的spring.servlet.multipart.max-file-size是1MB,如果你上传的海报图片超过1MB,接口直接报错。我改成了10MB才够用。
8.2 学习时最值得反复读的源码类
我说句实话,能写出这套系统的作者,基本功是很扎实的。如果你只是把它当成一个应付毕业设计的工具,跑通就交差,那就太浪费了。我建议你在跑通之后,花一个下午的时间专门读这几处代码:
JwtUtils里的Token生成和解析逻辑。- 选座下单里事务和锁的写法。
- 前端
request.js里的Axios封装和拦截器。 - 管理后台的权限控制逻辑。
读完之后你会发现,这些代码几乎是可以在任何Java后端项目里复用的。等你拿到下一个项目,甚至入职之后写业务代码,脑子里有这几段代码打底,上手的速度会快很多。
8.3 选一个适合自己的改造路线
最后给你一个建议:拿到这套源码之后,先不要急着加功能,一定要先完成"跑通-读代码-画流程图"这三步,再动手改。
跑通用半天,读代码用一天,画流程图用一个晚上。前期工作做扎实了,后面新增功能的时候才会有底气。如果你跳过这些直接改,大概率会陷入"改一处坏一处"的尴尬局面。
这套系统的定位很明确,就是一个不折不扣的教学型项目,适合毕业设计、课程设计和个人学习。我把它完整跑通、二次改造、部署上线的经验都写在这里了,希望你能少走我走过的弯路。如果你在搭建过程中遇到别的坑,欢迎在下面留言,我知道的基本都会说清楚。
