Spring Boot + Vue全栈实战:构建剧本杀门店预约与管理系统
2026/9/9 17:11:51 网站建设 项目流程

1. 项目概述与选题思路

做这个项目的起因是我常去的那家剧本杀店,老板手上有三个本子靠Excel排期,周末场次全靠群聊拼车,等座的玩家在门口干着急。我问他为什么不上一套管理系统,他说市面上的门店收银系统偏零售,剧本杀这种“按场次卖座位”的玩法套不进去。这个需求缺口正好适合拿来做一套前后端分离的完整项目,技术栈也成熟稳定——后端Spring Boot,前端Vue,数据库MySQL,认证走JWT。

这个项目适合三类人看:一是想学Spring Boot + Vue全栈开发、需要一个完整业务链路练手的初学者;二是准备把这个题目作为毕业设计或课程设计,需要从需求分析做到部署上线的在校生;三是确实在经营剧本杀店、想低成本搞一套门店管理工具的小老板。不管你是哪类,这个系统覆盖的知识点都挺全:权限管理、主子表CRUD、文件上传、订单防并发、前台展示与后台管理分离。

系统整体上解决三个核心问题:第一,剧本、场次、订单这三类核心数据的统一管理;第二,玩家在线预约座位,门店人员(店长、DM)审批和排班;第三,经营数据的简单统计——每天卖了多少场、哪些本子最受欢迎、哪个时间段上座率最高。这个逻辑听着不复杂,但真正从零写起来,前后端加起来大概要二十几张表、三四十个接口,能把整个流程跑顺,对Spring Boot和Vue的理解会上一个台阶。

我在设计阶段就明确了一个原则:不追求功能炫技,但每个模块都要解决一个真实痛点。比如玩家端不要做复杂的会员积分体系,但一定要有“正在拼车的场次展示”和“一键预约”的功能,因为这两个是门店老板实际使用频率最高的入口。围绕这个思路,技术选型就顺理成章了。

2. 技术选型与项目架构拆解

2.1 为什么选Spring Boot + Vue这套组合

现在做管理系统,前后端分离基本是默认方案。Spring Boot胜在生态成熟、社区资料多、坑少,适合绝大多数Java开发者上手;Vue则因为轻量、上手曲线缓、组件化开发效率高,在前端三巨头里最适合单人或小团队快速交付。组合在一起,后端专注提供接口,前端专注页面交互,两边可以并行开发,联调成本也不高。

有一个容易被忽略的点是,Spring Boot的自动配置机制和Vue的脚手架工具能很大程度降低项目初始化成本。后端一个spring-boot-starter-web就能把内嵌Tomcat、JSON序列化、参数校验全部搞定;前端vite或vue-cli初始化项目后,vue-router配路由、pinia或vuex管状态、axios发请求,都是在同一个开发生态里顺手的事。对做毕设或练手的同学来说,你不需要花大量时间在环境搭建上,可以把精力集中在业务逻辑和数据处理上。

我在实际开发中选择的技术组合是:Spring Boot 2.7 + MyBatis-Plus 3.5 + MySQL 8.0 + Redis(做缓存和防重)+ Sa-Token或Spring Security(权限控制,二选一)。如果你是新手,我更推荐Sa-Token,配置简单、中文文档友好,几行代码就能实现登录认证和角色权限控制;如果项目要求里明确写了Spring Security,那也别慌,套路是一样的,就是过滤器链和认证管理器的配置稍微绕一些。前端选了Vue 3 + Element Plus + Axios + Pinia,这套组合在社区里的最佳实践很多,遇到问题基本都能搜到现成方案。

2.2 业务模块与角色权限设计

权限设计是管理系统的地基,剧本杀店的业务场景天然适合做多角色权限。我把系统分为四个角色,每个角色看到的功能菜单和能调用的接口是隔离的。

第一个是系统管理员,管的是账号和全局配置——哪个店员能登录、剧本分类怎么维护、平台参数怎么设。第二个是店长,管的是核心经营数据——剧本上架下架、场次发布、订单查看和统计。第三个是DM(主持人),管的是执行层面的东西——查看自己分配的场次、确认开本状态、提交场次总结。第四个是玩家/前台,玩家在小程序或H5里浏览场次、预约座位、查看订单;前台可以代客下单。

角色之间是树形上下级关系,所以接口权限不能只靠前端隐藏菜单来完成。后端的做法是,在JWT令牌里写入角色标识,Spring Security的拦截器在进行接口访问校验时,通过注解(比如@PreAuthorize("hasAuthority('ROLE_STORE_MANAGER')"))来判断当前用户是否有权限访问这个接口。前端再根据登录用户的角色动态生成路由表,只渲染当前角色有权限的菜单。这样前后端双重校验,才算是比较完整的权限闭环。

这里有个经验要分享:不要在一开始就把权限设计做得特别复杂,比如搞部门、岗位、数据权限那一套。剧本杀店场景下,角色权限是固定且清晰的,直接写死资源-角色映射关系即可,等你以后业务复杂度上来了再重构也不迟。过度设计是新手最容易踩的坑,既浪费时间又增加了出bug的概率。

2.3 项目目录结构与后端分层规范

前后端项目结构一开始就要定好规范,不然代码越写越乱。我习惯把项目分成backend和frontend两个根目录,后端用Maven管理,前端用npm管理。后端代码采用经典的三层架构——controller层接收请求和参数校验,service层处理业务逻辑,mapper层操作数据库。

这里重点说一下后端包结构的划分,直接抄作业即可。com.example.dms下分controller、service(impl)、mapper、entity、dto、vo、config、common、utils这几个子包。entity对应数据库表,dto是接收前端参数的传输对象,vo是返回给前端的数据封装。前期多花十分钟建好这个目录层级,后面写几十个接口的时候就会庆幸当初没偷懒。

前端目录也一样,src下面按api、assets、components、router、store、utils、views组织。pages下再按角色模块建子目录,比如admin、store、dm、player,每个模块内部的页面组件就近管理。这样前后端结构清晰,一个人开发也好,两人协作也好,找文件都很快。项目的可维护性,往往不是靠后期重构来的,而是靠一开始的目录规范打底。

3. 数据库设计与核心业务流程

3.1 核心数据表设计与关联关系

数据库设计是整个系统的地基,表结构不合理,后面写接口全是泪。剧本杀门店管理系统的核心表,我按业务域分成了四组。第一组是用户域:用户表、角色表、用户角色关联表。第二组是商品域:剧本表、剧本分类表、剧本场次表、房间表。第三组是交易域:订单表、订单明细表。第四组是运营域:场次评论表、操作日志表。

这里挑几张关键表聊一下设计思路。剧本表(script)包含的基本字段有主键id、剧本名称、题材类型、人数范围(minimum_players和maximum_players)、时长(分钟)、难度等级、封面图URL、剧本简介、价格(人均单价)。场次表(session)是连接剧本和玩家的枢纽,包含的字段有剧本ID、房间ID、DM用户ID、开始时间、结束时间、当前已拼人数、状态(招募中/已满员/已开始/已结束)。

订单表(order)我用了order_info单独建表,字段包括场次ID、购买用户ID、购买数量、总金额、订单状态、创建时间。之所以要把订单和场次拆开,是因为一个场次可能对应多个订单——比如8人本,A玩家订了2个座位,B玩家订了3个座位,这两个订单都要记录,同时场次表的已拼人数要累加。

在设计索引时,我给session表加了(start_time, status)组合索引,因为业务上最常见的查询是“某天哪些场次还能预约”;给order_info加了(user_id, create_time)组合索引,支撑个人订单列表查询。很多新手容易忽略索引设计,实际数据量一上来,全表扫描会直接把接口拖垮。对毕设场景来说数据量不大,但这套思路还是要养成的。

3.2 场次预约与防并发设计

预约功能是这个系统里含金量最高的模块,它涉及一个并发问题:8人本只剩2个座位,两个玩家同时下单,怎么保证不会超卖?这是面试官最喜欢问的点,也是实际开发中真正需要处理的问题。

最基础的方案是,在座位扣减时使用乐观锁。给session表加一个version字段,每次下单时先查询当前拼团人数,UPDATE语句写成这样:

UPDATE session SET joined_players = joined_players + #{count}, version = version + 1 WHERE id = #{sessionId} AND version = #{oldVersion}

affected rows大于0说明更新成功,等于0说明版本号冲突,需要回滚提示“手慢了,座位被抢了”。这是比较稳妥的数据库层面防并发方案。

但我在实际项目中叠加了一层Redis缓存来减轻数据库压力。场次详情是高频读接口,很多玩家进来会反复刷新场次列表,每次都查MySQL显然不划算。做法是把正在招募中的场次列表和可预订数量缓存在Redis里,以场次ID为key,下单时用Redis的decr操作预扣数量,预扣成功后再异步写订单。这种二级缓存的方案,即使遇到秒杀场景也能顶住压力。

还有一个细节:下单时要校验这个场次的状态是不是“招募中”,以及当前用户是否重复下单,防止玩家手滑点了两次。这些业务校验最好都放在事务里,配合数据库唯一索引(user_id + session_id)形成兜底,确保数据一致性。

3.3 完整下单链路梳理

我把整个下单流程走一遍,方便你理解各模块是如何协作的。玩家在前端选择场次,点击“开始预约”,前端弹出确认弹窗,显示场次信息(剧本名、时间、房间、剩余座位数)和需要预约的人数。点击确认后,前端调用后端POST /api/order/create接口,提交的JSON里包含sessionId和buyCount。

后端收到请求后,先解析JWT获取当前登录用户ID,然后校验场次是否存在、是否处于可预约状态、剩余座位是否充足、当前用户是否已经预约过。校验通过后,进入座位扣减环节——Redis先预扣,再更新MySQL场次表的已拼人数,同时插入订单记录。订单状态初始为待支付(我这里简化了支付流程,线下支付后前台确认即可),整个操作加@Transactional事务注解包裹。

如果全部成功,返回订单ID给前端,玩家端“我的预约”列表就能看到这个订单了。如果任何一个环节失败,统一抛出业务异常,全局异常处理器捕获后返回友好错误提示。整个链路走下来,你会发现业务的闭环在于各模块之间的数据流转,而不仅仅是某个接口写得好不好。

4. 前端核心实现与Vue组件化开发

4.1 Vue Router动态路由与权限控制

前端部分最重要的一个工程细节是动态路由。不同角色登录后看到的菜单不一样,玩家看到的是“浏览剧本”“我的预约”,店长看到的是“剧本管理”“场次管理”“订单管理”。如果用静态路由表写死所有页面,用户其实可以通过URL地址直接访问没有权限的页面,这在体验和安全性上都不合适。

我的做法是,在router/index.js中把不需要登录就能访问的公共路由(登录页、首页)单独配置,业务页面组件全部采用动态import写法实现懒加载:

{ path: '/order/list', name: 'OrderList', component: () => import('@/views/order/OrderList.vue'), meta: { title: '订单管理', roles: ['ROLE_STORE_MANAGER'] } }

登录成功后,前端拿着后端返回的角色信息,遍历完整的菜单路由表,过滤出当前角色有权限的路由,通过router.addRoute方法动态挂载。这样即使有人手动输入URL,因为对应路由还没有被添加,也只会进入404页面。

在具体实现中有个容易踩的坑:刷新页面时路由会重置,因为Vuex/Pinia里的状态会丢失,需要对登录态做持久化。我的方案是把用户信息存一份在localStorage里,页面刷新后在路由守卫(beforeEach)中重新从localStorage读取用户信息,调用router.addRoute重新注册动态路由。这一步忘了的话,所有人刷新后都会跳到登录页或者404,属于高频问题,要特别留意。

4.2 Axios封装与统一错误处理

前端请求后端的第一个门面是Axios。所有请求都通过axios实例发出去,所以非常适合在这里做统一处理。我在utils/request.js里创建axios实例,设置baseURL为'/api',设置了请求超时时间(10秒),请求拦截器里从localStorage获取token并加到请求头中。

响应拦截器的核心逻辑是:后端返回的结构统一为{ code, message, data },code为200时认为是成功,取出data返回给页面;code为401时说明token过期或未登录,跳转到登录页并清除本地数据;code为其他值时,用Element Plus的Message提示错误信息。

这里分享一个经验:后端返回的状态码和HTTP状态码不要混着用。建议HTTP状态码保持200,业务成功与否用body里的code字段区分。因为Tomcat默认会对4xx、5xx状态码做日志记录,测试人员看到一堆404、500日志容易误以为出故障了。用业务状态码的方式,日志更干净,排查问题时也更容易定位。

4.3 后台管理界面的核心页面拆解

后台管理的核心页面可以拆成四屏:剧本管理、场次管理、订单列表、经营看板。剧本管理页面是标准的CRUD表格,使用Element Plus的el-table加el-pagination,顶部是搜索表单(按剧本名称、题材类型过滤),右侧是新增按钮。编辑和新增共用一个Dialog弹窗,里面用el-form做表单校验,封面图用el-upload上传组件,选择图片后走后端文件上传接口,返回URL赋值给表单字段。

场次管理页面相对复杂一些,因为新增场次时要选择剧本、选择房间、选择DM、设置开始时间和预计结束时间。这里有个联动逻辑:选择了剧本后,前端根据剧本的人数范围和时长自动带出“人数上限”和“预计时长”两个字段。新增场次后,场次列表按时间轴展示当天的所有场次,不同状态用不同颜色标签标识。

订单管理页面是店长最常用的页面,实现上就是一个订单列表加上筛选条件(按日期、订单状态、剧本名称),点击订单行可以展开查看详情。经营看板页面用ECharts画柱状图和饼图,展示近7天销售额、各题材剧本售卖占比、各时间段上座率。前端通过调用后端统计接口拿到聚合数据,交给ECharts渲染。如果后端是新手,统计SQL可能会写得比较痛苦,我建议直接上MyBatis-Plus的queryWrapper配合groupBy,或者写原生SQL时用if标签做动态拼接。

5. 系统亮点功能与实现细节

5.1 文件上传与图片管理方案

剧本封面、房间照片这些都是图片资源,怎么管理是个实际需求。方案有很多种——上传到本地服务器磁盘、上传到阿里云OSS、上传到七牛云。对非专业的毕设项目来说,我推荐先把文件存到本机指定目录,数据库里只存访问路径。这样不仅在开发环境运行简单,部署到Linux服务器也容易理解。

实现方式是后端写一个FileUploadController,接收MultipartFile,校验文件大小(不超过5MB)和类型(jpg、png),生成UUID文件名,调用transferTo方法保存到配置文件指定的目录。访问静态资源时,在application.yml里配置资源映射:

spring: mvc: static-path-pattern: /upload/** resources: static-locations: file:D:/upload/

Windows上的路径要注意反斜杠的转义,Linux上直接配置绝对路径即可。在实际部署时,我用Nginx把所有/upload路径的请求直接映射到物理目录,这样静态资源的访问就不经过Tomcat,效率更高。

5.2 基于Spring Boot Actuator的轻量监控

项目做完之后,我发现排查线上问题还是有点被动的。系统运行是否健康、最近有没有报错,这些信息需要主动去查。于是我在项目里集成了Spring Boot Actuator,这个Spring Boot自带的功能模块,可以暴露一系列运维接口,比如health(服务健康状态)、metrics(JVM内存、线程信息)、loggers(动态调整日志级别)。

开启方式很简单,pom.xml加spring-boot-starter-actuator依赖,application.yml里配置暴露端点。我实际开放了health和info两个端点,通过Micrometer对接了简单指标上报。对毕设答辩来说,能讲清楚“集成了Actuator做服务健康监控”绝对是个加分项。而且面试时Spring Boot actuator也是高频考点,项目中真实用过和只看过博客,聊起来的感觉完全不一样。

有一点必须提醒:Actuator端点在生产环境暴露是有安全风险的,必须配合Spring Security做访问认证,至少不应该把shutdown这类危险端点暴露出来。我在配置里显式设置了只暴露health、info、metrics三个安全端点,并加了访问路径前缀,这个细节如果你能在答辩或面试中主动说出来,会显得考虑问题非常全面。

5.3 数据库自动备份与恢复策略

数据是门店经营的核心资产,万一数据库出问题没有备份,老板真的要哭。我在项目中添加了一个简单的自动备份机制,利用Spring的定时任务Schedule定时执行mysqldump命令。配置文件里设置备份目录和备份频率,每天凌晨三点自动备份一次,保留最近七天的备份文件。

核心实现是在config包下开启@EnableScheduling,写一个DatabaseBackupTask类,方法上标注@Scheduled(cron = "0 0 3 * * ?"),方法内部用ProcessBuilder调用本机的mysqldump命令,把备份文件输出到指定目录。这个功能虽然实现起来不复杂,但实用性很强,也是期末答辩时一个很好的落地亮点。

这里要专门提醒一下:mysqldump一般是MySQL安装目录下的可执行文件,在Java里调用时记得用绝对路径,或者在application.yml里配置命令路径,否则很容易出现A fatal error: mysqldump command not found这个经典错误。我在本地开发环境中就踩过这个坑。

6. 开发环境配置与项目部署上线

6.1 Java、Maven、Node.js环境搭建要点

先把环境这件事说透。安装Java JDK时,最容易出问题的是环境变量配置,JAVA_HOME指向JDK安装目录,PATH里加上%JAVA_HOME%\bin,很多人漏了这一步,或者配置完没重新打开终端,导致java -version报错。JDK版本我推荐8或11,Spring Boot 2.7两者都支持,生产环境也更稳定。

Maven的settings.xml建议配一个国内镜像仓库,阿里云镜像就行,否则首次拉取依赖会等到怀疑人生。Node.js环境更简单,装上之后npm自带了,注意npm的registry也建议改为国内镜像(npmmirror),能解决大部分前端依赖安装慢或失败的问题。

用IDEA打开Spring Boot项目后,右键pom.xml选择Maven的reload即可拉取依赖。前端项目用命令行cd到frontend目录,依次执行npm install和npm run dev。有一点容易踩坑:如果IDE的终端用的还是旧版本的node或npm,需要检查node -v版本是否和项目要求一致,Vue 3要求Node 14.18及以上。

6.2 Nginx反向代理与前后端联调上网

本地开发时,前端Vite dev server跑在端口5173,后端Spring Boot跑在8080,跨域问题不可避免。最简单的解决方案是利用Vite的代理功能,在vite.config.js中配置:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端所有/api开头的请求都会被代理到后端,不涉及跨域,后端也无需额外配置CORS。这个方法干净利落,我强烈推荐。

生产部署时,前后端分开部署,Nginx做反向代理。前端Vue项目执行npm run build打出的dist目录上传到服务器某个目录,nginx配置大致是这样的:80端口监听,root指向dist目录,location /api/下面的请求反向代理到本机8080端口的Java进程。这样浏览器访问Nginx拿静态资源,动态接口由Nginx转发给后端,整个链路就很清晰了。

这里有一个常见的坑是history模式路由刷新404问题。Vue Router如果用history模式,刷新页面时Nginx会去磁盘找对应的路径,找不到就404了。解决办法是在Nginx配置里加fallback:

location / { try_files $uri $uri/ /index.html; }

这行配置的含义是,找不到对应文件时统一返回index.html,让前端路由接管页面渲染。如果你用的是hash模式就没有这个问题,但URL里会多一个#号,观感稍差。

6.3 Maven打包与Linux服务器部署实操

后端部署的流程是,在IDEA的Maven面板里执行package命令,会生成一个target/xxx.jar文件。Linux服务器上如果装了Java环境,直接执行java -jar xxx.jar就能启动,但为了让进程在后台稳定运行,我推荐用nohup方式:

nohup java -jar /opt/script-server/script-server.jar --server.port=8080 > /opt/script-server/logs/app.log 2>&1 &

日志输出到文件,配合tail -f logs/app.log实时查看启动情况。如果Spring Boot启动失败,先看日志,绝大部分问题都能在启动日志里找到明确原因。

MySQL的连接地址在部署时要改一下,不能用localhost了,改成服务器的内网IP或真实IP。数据库初始化方面,我用了Spring Boot的SQL初始化功能,把schema.sql放在classpath下,配置spring.sql.init.mode=always,首次启动时会执行建表SQL。如果是已有数据,用mysqldump导出一份,登录服务器后执行source命令导入即可。

整体的上线流程梳理一下:服务器装JDK、MySQL、Nginx,前端打包上传dist目录到/usr/share/nginx/html,后端打包上传jar包到/app目录,Nginx配置好反向代理,启动Java进程,浏览器访问服务器IP就能看到系统首页了。从开发到部署的完整闭环,就这么跑通了。

7. 常见问题与排查技巧实录

7.1 跨域问题与前后端联调典型场景

前后端分开跑,跨域是最容易出现的问题。配置了Vite代理还报CORS错误,先看看报错信息是浏览器层面的拦截还是后端抛出的异常。有时候前端直接在浏览器地址栏访问后端地址,以为能通,结果因为端口不一致出现了跨域。排查思路是:先看浏览器Network面板,确认请求有没有发出去,如果发出去了再看响应头里有没有CORS相关字段,如果没有,就是后端没配置或者代理没生效。

7.2 数据库连接与SQL执行问题速查

我在开发中整理了一个排查小表格,分享给你。

问题表现可能原因解决方案
Failed to configure a DataSource未配置数据源或yml配置错误检查application.yml的url、username、password,确认MySQL已启动
Access denied for user数据库用户名或密码错误用命令行工具测试连接,确认授权
Unknown database库名填错先在MySQL客户端创建对应名称的数据库
Table not found实体类和表名不一致检查@TableName注解或建表SQL
Duplicate entry for key唯一索引冲突检查业务是否重复插入,或清理测试数据
中文乱码连接字符集不一致jdbcUrl后加useUnicode=true&characterEncoding=utf8

7.3 权限验证与路由拦截的坑

权限这块最容易出现的问题是:明明登录成功了,但访问某个接口一直返回401。常见原因是JWT生成和解析时用的签名密钥不一致,导致解析出来的token无效。排查方法是把token打印出来,在JWT工具类的解析方法里看具体报错,是过期还是签名不一致,一目了然。

还有一类问题是前端动态路由加载了但页面还是空白,控制台报No match for path,这种基本是addRoute的顺序问题。Vue Router注册完动态路由后需要调用next({ ...to, replace: true })重新触发一次导航,否则当前这次路由匹配还是按照旧的静态路由表来解析,就会找不到路径。

7.4 数据统计页面的SQL性能优化心得

经营看板页面的统计SQL,一开始我是让前端传日期范围,然后三个图表分别请求三个接口,每个接口都查一遍数据库。数据量小还好,数据量一大性能明显下降。后来我优化成一次性查出聚合数据,在Java内存里拆分成三个消费方需要的数据结构。基础的统计SQL别整太复杂,尽量用简单的group by加聚合函数,复杂的指标在代码里二次计算,这样维护性和性能都更好。

还有一个值得注意的点:日期范围跨度大的统计,如果没有索引,全表扫描会很慢。应对办法是把订单表中的下单时间字段加了索引;如果数据量特别大,还可以考虑按月做分区存储。对当前场景来说,索引已经足够,但知道有这样一个演进方向,在面试时能聊出深度。

8. 经验总结与扩展建议

最后分享一些个人体会。这个项目做完,我最深的感受是:一个管理系统能不能真正用起来,关键不在于功能堆得多满,而在于每个核心业务流程是否闭环、交互是否符合实际使用习惯。比如玩家预约剧本这个事,门店老板真实需求是减少沟通成本,而不是看华丽的动画效果,那前端做得简洁清晰就好;但订单和场次的数据必须实时准确,因为门店要靠它做调度。

后续扩展空间也比较大。第一是引入工作流审批,门店的场次上新、请假调休可以走多个环节审批;第二是接支付,剧本杀门店线下支付居多,但线上订金是趋势,微信/支付宝下单支付可以与订单流程打通;第三是接一个小程序端,用uni-app或Vue 3重新包一层,客人在微信里就能完成预约,会比H5体验更好;第四是把经营分析做成周报/月报自动发送,减少门店人员每天盯后台统计数据的精力。

如果你正在做类似的项目,我的建议是别急着上来就写代码,先花一天画清楚用户角色和核心业务表格;写后端接口时多留意参数校验和异常处理,这会让联调顺畅很多;前端页面每个流程自己先走一遍,以玩家身份真实预约一次,以店长身份审核一次,以系统管理员身份配置一次,跑通了再考虑加新功能。这些习惯,比任何单项技术都值得养成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询