SpringBoot+Vue前后端分离瑜伽馆管理系统实战解析

发布时间:2026/9/10 8:47:18
SpringBoot+Vue前后端分离瑜伽馆管理系统实战解析 前后端分离这套东西在圈子里聊了好几年了但从我自己带项目的体感来说真正能跑通全流程的案例还是不多。很多人卡在“后端接口写好了前端不知道该怎么调”或者“前端页面写完了一对接就跨域报错”这种尴尬的节点上。今天分享的这个瑜伽馆管理系统就是一个非常典型的SpringBootVueMyBatisMySQL组合案例业务上覆盖了会员、课程、预约、排课这些真实场馆管理场景技术上把前后端分离从开发到部署的全链路都串起来了。哪怕你不是做瑜伽馆这个行业的把它当成一个标准的Java全栈实战项目来参考也完全值得花时间过一遍。先给第一次接触这类项目的朋友说清楚这玩意儿到底能干什么。简单讲它就是一个给瑜伽馆老板或者店长用的管理系统功能上对标我们常见的会员管理系统办卡、约课、上课打卡、课程排期、员工信息管理这些日常运营动作都能在系统里完成。前台是Vue写的单页应用负责页面展示和用户交互后台是SpringBoot提供的接口服务负责业务逻辑和数据存储MySQL就是最终落数据的地方。整个项目不仅给了完整源码连数据库脚本、接口文档、部署教程都带了基本上你拿到手之后按着步骤搭完环境就能在本地把所有功能跑起来。这个项目最适合三类人看第一是正在做Java全栈课程设计或者毕业设计的同学业务场景完整、技术栈主流直接拿来改改就能用第二是自学SpringBoot和Vue但是一直缺一个完整串联项目的人这个项目能帮你把零散的知识点拼成完整的地图第三是想了解前后端分离项目从开发到部署完整流程的初级开发你可以在里面看到接口怎么约定、跨域怎么处理、前端后端怎么联调以及最后怎么打包上线。我下面会把整个项目从设计思路到实际部署一条线讲完重点放在那些文档里没写、但实操中一定会遇到的坑上。1. 项目整体设计与思路拆解1.1 为什么选择前后端分离架构在开始敲代码之前得先想明白一个问题为什么非要做前后端分离而不是像十年前那样用JSP或者Thymeleaf把页面直接写在服务端里答案其实很实在。前几年我做过一个中小型健身房的管理系统当时用的是Freemarker模板渲染业务规模小的时候一切正常但一旦页面上要加交互效果、要实时刷新数据、要对接微信端或者小程序模板渲染就变得非常痛苦——前端要等着后端一起联调改个样式还得重新部署服务。后来把项目重构为前后端分离整个团队就舒服多了。具体到这个瑜伽馆系统前后端分离带来的好处是实打实的前端只关心页面渲染和数据交互后端只关心业务逻辑和接口输出两边通过HTTP接口和JSON数据通信各干各的活。而且Vue开发时自带热更新改完代码浏览器立刻刷新开发效率比模板引擎高太多。还有一点很实际瑜伽馆这种场景未来大概率要扩展小程序端或者手机H5端如果一开始就是前后端分离那小程序端可以直接复用后端的接口前端页面重写一套就行不用动后端代码。这种扩展性是模板渲染架构很难做到的。1.2 瑜伽馆业务的三个核心模块这个系统的业务建模是围绕瑜伽馆的典型经营场景来设计的。我拆开来看核心就三大块。第一个是会员管理。瑜伽馆的会员不是简单地存一个姓名和手机号就完事了它有一套自己的计费逻辑有按月的普通会员有按次数的次卡会员还有私教课包。每一种不同计费方式在数据库表结构和代码逻辑上的处理方式都不一样。月卡会员主要看有效期过期就要续费次卡会员要记剩余次数每上一次课扣一次私教课包则可能要跟具体的教练绑定。这块是整个系统的地基会员数据不对后面所有业务都会跟着错。第二个是课程与排课管理。瑜伽馆的课程种类很多——哈他瑜伽、流瑜伽、阴瑜伽、空中瑜伽、理疗私教等等。每一种课程有自己适合的教练、固定的时长、最多容纳的人数。排课就是把这些课程安排到一周的每一天、每一个时段比如周二晚上7点有流瑜伽课周五上午10点有空中瑜伽课。排课出来之后会员才能去预约。这个模块对于馆方来说是运营的核心怎么排、排什么课、谁来上直接影响门店的收入和会员的满意度。第三个是预约与打卡管理。会员约了一节周二的流瑜伽课系统需要记录这个预约到上课当天会员到店后需要验证预约信息并打卡确认。这里还有一个容易被人忽略的细节如果会员约了课但没来也就是“爽约”是扣次卡次数还是不做任何惩罚不同场馆规则不一样。这个系统在业务设计上留有处理的余地这也是我比较认可的一点。1.3 数据库设计的几个关键决策数据库设计这块直接决定了后面写代码是省力还是费劲。这个系统的表设计有几个地方想得很清楚。用户和角色的设计采用的是经典的RBAC模型用户表、角色表、权限表、用户角色关联表、角色权限关联表五张表一套带走。后面做登录和权限控制的时候非常清晰不用在代码里写死逻辑。有些项目图省事在用户表里加个role字段类型为字符串比如admin,teacher,member这种设计能跑但非常不优雅一旦要扩展权限点就会非常痛苦。会员信息和用户信息是分开设计的。用户表里存的是账号、密码、手机号这类登录凭证信息会员表里存的是会员等级、卡类型、剩余次数、有效期这些业务属性。为什么要分开因为一个用户不一定永远是会员会员可能过期过期了可以续费但用户账号还在。如果混在一张表里会员过期就不知道该怎么处理用户数据了。分开设计的好处是用户的生命周期和会员的生命周期可以独立管理。课程、排课、预约这三张表的关系是这个系统里最微妙的地方。课程表定义的是“有什么课”比如“哈他瑜伽”这个名字、时长、适合的人群排课表定义的是“什么时候在哪儿上什么课”比如“周二晚上7点在1号教室由李教练上哈他瑜伽”预约表定义的是“谁约了哪一堂排课”。这三者的层级关系理清了后面写查询和业务逻辑就顺了。特别是预约表一定要同时存排课ID和会员ID并且做唯一约束防止同一个会员重复预约同一节课。2. 技术选型深挖为什么是SpringBootVueMyBatis2.1 SpringBoot到底解决了什么问题很多新手刚接触SpringBoot的时候会有一个困惑这东西和之前的SSM架构SpringSpringMVCMyBatis到底差在哪里我用一个最直观的对比来回答。SSM时代你得先配置web.xml、配置Spring容器、配置SpringMVC的DispatcherServlet、配置数据源、配置事务管理器光这些配置文件加起来就有几百行。而且每个项目都要重复来一遍换个环境还经常因为某些细节配错了跑不起来。SpringBoot做的核心事情就是“约定大于配置”它把那些繁琐的配置统统内置了你只需要引入对应的starter依赖项目就能自动完成配置。比如你想在项目里用MySQL引入一个mybatis-spring-boot-starter和mysql-connector-java再把数据库连接信息写到application.yml里就完事了。不需要手动创建DataSource的Bean不需要写SqlSessionFactory的配置类SpringBoot会自动帮你搞定。这就是为什么现在企业里新项目基本都选SpringBoot开发效率差太多了。这个瑜伽馆系统在SpringBoot的运用上也没整那些花活儿就是老老实实按分层架构来Controller层接收参数和返回结果Service层写业务逻辑Mapper层操作数据库。这种情况下SpringBoot的自动配置、依赖注入、事务管理等能力正好能发挥最大的作用不复杂也不绕。2.2 MyBatis和MyBatis-Plus的选择思路先统一一个认知MyBatis本身是一个持久层框架它的核心优势是让你自己写SQL把SQL语句和Java方法的映射关系通过XML文件或者注解来管理。熟悉SQL的人用MyBatis会非常顺手因为数据的查询逻辑完全在自己掌控之中不像JPA那样需要理解它那一套方法命名规则和自动生成的SQL逻辑。这个项目用的是原生MyBatis我觉得是有意为之的。对于学习型项目来说用原生MyBatis反而能让你把SQL写明白。因为你在XML里写的每一条SQL都是显式的比如查询会员列表、判断某个时段是否已经有排课、统计某节课的预约人数这些业务逻辑对应的SQL你都能看得一清二楚不容易出现“用MyBatis-Plus写对了方法但不知道底层执行了什么SQL”的情况。当然如果你是在实际企业项目里做开发我一般建议用MyBatis-Plus因为单表CRUD真的不需要手写SQL它内置的Wrapper机制帮你省掉很多模板代码。但对于这个瑜伽馆项目牵扯到多表联查的场景不少原生MyBatis在XML里写关联查询更加清晰可控。等你在项目里把原生SQL练熟了再切到MyBatis-Plus会非常有感觉——你就知道它到底帮你省了哪些事。2.3 Vue这层为什么选2而不是3现在Vue 3已经是主流了Composition API、TypeScript、Vite这些都很好用。但这个项目用的是Vue 2我猜测有几个现实层面的考虑。第一是生态成熟度。Vue 2的Element UI、Vue Router、Vuex这些配套库都发展了很多年文档齐全、案例丰富、遇到的问题网上基本都有答案。对于学习者来说这意味着你踩到坑的时候有大把的解决方案可以查。Vue 3虽然也好但生态里的一些库版本更迭过程中偶尔会有兼容问题新手容易被卡住。第二是入门门槛。Vue 2的Options API对新手更友好——data写数据、methods写方法、computed写计算属性、watch写监听结构非常清晰一看就懂。Vue 3的Composition API功能更强大但上手理解的曲线要陡一些。如果你是第一次接触Vue从Vue 2入手会更顺畅。第三也是最重要的Vue 2项目的开发思想和Vue 3是打通的。你在Vue 2里学到的组件化、路由、状态管理、生命周期、异步请求这些核心概念迁移到Vue 3完全没有障碍。所以不要太纠结于版本先跟着项目把Vue的整个工作流跑通后面再花个两三天去了解Vue 3的差异点知识迁移非常自然。2.4 MySQL表结构设计的关键点拆解这个系统的数据库脚本我建议拿到手之后先别急着execute而是花点时间把每一张表过一遍。表结构设计是一个项目最底层的逻辑你把这个搞清楚了再去看代码就是降维打击。我挑一张典型的表来说。预约表appointment/booking表的设计核心字段至少包括主键ID、排课ID、会员ID、预约状态、创建时间。这里有一个我特别想强调的点预约状态为什么要单独设一个字段因为业务上“已预约”“已到店”“已爽约”“已取消”这几个状态需要区分。如果你不设计状态字段想要判断一个会员到底有没有来上课就得去关联打卡表查非常麻烦。加一个状态字段用不同的值映射不同的业务状态查询的时候一个WHERE status ?就搞定了。再比如会员表的设计。会员表里通常会有一个“卡类型”字段和一个“剩余次数”字段。卡类型可以用数字标识比如1代表月卡、2代表次卡、3代表私教课包然后用代码里的枚举去解析它。但是这里要注意一个细节如果只是存一个数字时间久了后面接手的开发者可能就忘了1到底是什么意思所以要么在上面的常量类里写清楚注释要么在表字段的comment里写得明明白白。这个项目的数据库脚本里注释写得比较详细这一点我点个赞——写数据字典是一个很好的习惯可惜很多项目都不重视。还有一个表设计上的小细节时间字段统一用datetime类型不要用varchar存时间字符串。为什么因为datetime类型在MySQL里可以直接用日期函数进行比较和计算比如要查“今天有哪些课”一个WHERE date(start_time) CURDATE()就搞定了。你要是用varchar存这种查询就没法走索引数据量上来之后性能会非常差。3. 核心功能实操细节与代码导读3.1 前后端接口怎么约定和对接前后端分离项目能不能开发顺畅很大程度上取决于接口约定是否清晰。我在做这个项目的时候坚持一个原则接口返回的数据结构必须统一。后端约定所有接口返回的JSON格式都是这样的{ code: 200, message: success, data: { } }code是业务状态码200代表成功其他值代表不同的失败原因比如401表示未登录、403表示无权限、500表示服务器内部错误。message是给前端展示的提示信息data是真正的业务数据。这个约定看起来简单但实际在项目中带来的好处特别大。前端可以封装一个统一的请求工具类在拦截器里统一处理状态码不需要每个接口都写一遍成功和失败的判断逻辑。举一个前端封装的例子用axios的话大概是这样import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动附带token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理code service.interceptors.response.use(response { const res response.data if (res.code ! 200) { if (res.code 401) { // 未登录跳转到登录页 router.push(/login) } return Promise.reject(new Error(res.message)) } return res.data }) export default service这段代码非常重要它解决了两个问题一是所有请求自动携带token不用每个接口单独写二是后端返回的code不是200时可以在统一的地方处理错误提示和登录失效跳转不用每个页面都重复写逻辑。再看后端Controller层的写法拿登录接口举例前端把用户名和密码通过POST请求发给/api/user/login后端接收后调用Service层去数据库里查用户校验密码然后生成一个token返回给前端。token的生成方式很多可以用JWT也可以用UUID配合Redis存session。这个项目用的是JWT方案好处是无状态——后端不需要存session前端每次请求带上token后端解析token就能知道当前是哪个用户。3.2 会员预约与排课冲突校验的实现逻辑预约功能是这个系统里业务逻辑最密集的一个模块牵扯到的细节非常多我给你拆解开来说。先想一个业务场景一个普通会员想预约周三晚上7点的流瑜伽课这节排课对应的课程最多容纳12人。系统在接收到预约请求的时候至少要判断三件事这个会员是否登录并且有预约权限这门课是否还有剩余名额这个会员是否已经预约过同一个时段的课程了。三个条件都满足才能预约成功。先说剩余名额的判断。最简单可靠的办法是在排课表course_schedule表里设计一个max_count字段和一个current_count字段每次有人预约成功就在一个事务里执行UPDATE course_schedule SET current_count current_count 1 WHERE id ? AND current_count max_count。这个SQL写法有一个好处——它把“判断是否有名额”和“更新名额”合并成了一条原子操作在高并发场景下不会出现超卖问题。如果这次UPDATE影响的行数是0就说明已经没有名额了直接返回“本次课程名额已满”就可以。再说重复预约的判断。这个需要用会员ID和排课ID去预约表里查如果查出已经有记录且状态不是“已取消”就说明已经约过了不能再约。同时预约表的这两个字段要建一个唯一索引双保险防止并发场景下的重复插入。还有一个容易漏掉的细节如果同一名会员预约了两节同一时段、但不同教室的课这种算不算冲突严格来说应该算但这需要额外的业务判断逻辑——查一下该会员已预约的排课记录里有没有时间段跟新课重叠的排课。这个查询写起来稍微复杂一点但逻辑上是完备的。有些初级项目不做这个判断就会导致会员同时约了两节课的极端情况。整个预约流程为了保证数据一致性建议在Service方法上加上Transactional事务注解。一旦最后一步执行失败前面加了的当前count要回滚已插入的预约记录也要回滚不能出现“预约记录写了但还没扣名额”这种中间状态。3.3 权限控制与登录状态的实现思路前后端分离项目做权限控制跟传统的Session方案有一个关键区别传统的Session方案天然具备“服务端状态”登录之后服务端记住了你是谁但前后端分离强调无状态服务端不保留会话信息怎么识别“你是你”常见的方案是用token。用户登录成功后后端根据用户ID、角色等信息生成一个token返回给前端。前端把token存在localStorage或者sessionStorage里每次发起HTTP请求都在请求头里带上这个token。后端通过拦截器或者过滤器解析请求头里的token解析成功就放行解析失败就返回401。这个系统的做法是定义了一个拦截器在SpringBoot里就是实现HandlerInterceptor接口在preHandle方法里读取请求头中的token调用工具类解析把解析出来的用户信息放到ThreadLocal或者HttpServletRequest的属性里方便后面的Controller方法取用。对于登录接口本身和注册接口这一类不需要认证的接口要用配置方式放行不要拦截。从角色权限的角度来看后端在拦截器里只做了“是否登录”的判断那管理员和普通会员的权限怎么区分这个要结合RBAC模型去做。管理员可以访问“课程管理”“会员列表”“排课管理”这些管理类接口普通会员只能访问“我的预约”“我要约课”这类型接口。实现方式主要有两种一种是在接口方法上加自定义注解比如RequirePermission(admin)由AOP统一校验另一种是在拦截器里根据请求URL路径去判断。小项目用简单的路径判断就够了比如/admin/**开头的接口要求管理员权限/member/**开头的接口要求登录用户权限即可。灵活度高代码也不复杂。3.4 Vue端核心页面与接口调用的串联前端这块我挑几个关键的页面来说说它的实现逻辑。登录页是整个前端系统最先被访问的入口。用户在登录页输入账号密码点击登录按钮后前端调用登录接口成功后把token和用户信息保存下来同时根据角色跳转到不同的首页——管理员跳转到管理后台会员跳转到课程列表页。这里有一个常见的坑刷新页面后Vuex里的用户状态会丢失因为Vuex是内存存储。解决方案是在App.vue的created生命周期里读取localStorage中保存的用户信息重新填充到Vuex里。否则一刷新页面页面就以为用户没登录了这是很多新手最容易踩的坑。课程列表页的逻辑也很有代表性。页面加载时请求后端接口拿课程列表数据展示课程名称、课程时间、教练、剩余名额等信息。Vue 2里这个过程很清晰created钩子里调用methods里的fetchCourseList方法该方法通过axios请求接口拿到数据后赋值到data里的courseList字段模板里用v-for把列表渲染出来。如果需要“只展示今天可预约的课程”这种筛选一种做法是后端接口接收日期参数做过滤另一种是前端在拿到全量数据后用computed做前端过滤。推荐前者因为数据量和并发上来之后前端过滤的效率远不如后端SQL过滤。同理在分页需求上建议后端做真分页而不是一次性返回所有数据让前端自己分。管理端的页面上课程管理、会员管理、排课管理这些表格类的页面本质上都是“表格表单弹窗删除确认”的组合。拿课程管理举例管理员打开课程管理页面看到课程列表可以点击新增课程打开一个表单弹窗填完提交之后刷新列表可以点击编辑回填当前数据修改后提交可以点击删除弹出一个确认框确认后删除并刷新列表。这类CRUD操作在Vue里写起来很有套路Element UI的el-table加el-dialog组合就能搞定重点在于每次操作之后要重新请求列表数据保证页面显示跟数据库实时同步。还有一些页面需要做条件查询比如按课程名称模糊搜索、按教练筛选这些搜索条件是通过请求参数传给后端的后端在SQL里用if动态拼接查询条件来实现。4. 从源码到部署本地环境搭建与全流程实操4.1 环境准备JDK、Node、MySQL的版本选择和安装实战项目最大的拦路虎不是代码本身而是环境。我先说版本问题这是最容易踩坑的地方。后端如果用的是SpringBoot 2.x那JDK选择8或者11都可以。但如果你下载的是SpringBoot 3.x那就必须用JDK 17以上因为SpringBoot 3是基于Jakarta EE的底层改了。这个项目用的JDK版本在文档里应该有说明一般配套SpringBoot 2.x的就是JDK 8——这也是目前国内大多数中小型公司的主流配置。JDK安装没什么技术含量但要记住设置JAVA_HOME环境变量并且把%JAVA_HOME%\bin加入Path。装完在命令行里敲java -version验证一下就说明环境变量的配置没有生效。前端需要安装Node.js。这里有一个非常重要的点不要无脑装最新版NodeVue 2项目用Node 14或者Node 16是最稳的因为高版本的Node可能导致老项目的依赖安装报错。装完Node之后npm会自带可以用npm -v验证。考虑到国内网络环境建议把npm的镜像源切换成淘宝镜像命令是npm config set registry https://registry.npmmirror.com否则下载依赖的时候会等到怀疑人生。MySQL的版本建议用5.7或者8.0这两个版本是目前国内使用最广泛的。安装过程中要注意MySQL 8.0默认的认证插件是caching_sha2_password而某些老版本的数据库连接驱动不支持这个认证方式会出现连接报错的情况。如果你用的是MySQL 8.0建议在创建数据库用户的时候使用mysql_native_password认证或者确保你引入的mysql-connector-java版本在8.0以上。4.2 数据库初始化与配置拿到项目源码后数据库这块一般会有两种交付形式一种是SQL脚本文件你需要手动导入另一种是项目启动时会自动执行初始化脚本前提是你在配置里开启了spring.sql.init相关选项。这个项目给的是SQL脚本我们手动导入。首先打开MySQL命令行或者用Navicat、MySQL Workbench这些图形化工具创建一个新的数据库CREATE DATABASE IF NOT EXISTS yoga_studio DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后切换到该数据库导入项目里提供的yoga_studio.sql文件mysql -u root -p yoga_studio yoga_studio.sql导入完成之后用SHOW TABLES;看看表是否都建出来了。这一步能提前排查SQL脚本和当前MySQL版本是否兼容如果某条建表语句报错大概率是数据类型或索引语法不兼容。接着修改后端的数据库连接配置位置在src/main/resources/application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/yoga_studio?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你自己的数据库密码在url参数里useUnicodetruecharacterEncodingutf8是为了解决中文乱码问题serverTimezoneAsia/Shanghai是为了避免MySQL连接时的时区报错。这两个参数是从老项目里继承过来最容易出问题的地方别省略。4.3 后端启动从IDE到命令行如果你用的是IntelliJ IDEA直接用IDEA打开后端项目等待Maven下载完依赖后运行主启动类通常是一个带有SpringBootApplication注解的类里的main方法。看到控制台输出Started Application in ...并且没有任何异常就说明后端启动成功了。如果你更习惯用命令行操作可以用下面这种方式cd backend mvn clean package -DskipTests java -jar target/yoga-system-0.0.1-SNAPSHOT.jar第一次执行mvn clean package的时候Maven会从中央仓库下载大量依赖包速度取决于你的网络状况和镜像源配置。如果你的Maven下载很慢建议在settings.xml里配置阿里云镜像这一步能帮你节省大把时间。后端启动成功之后可以用浏览器直接访问一些不需要登录的接口验证一下比如http://localhost:8080/api/course/list如果返回了课程的JSON数据说明后端服务、数据库连接、接口路由都没问题。4.4 前端启动依赖安装与本地开发服务前端项目的启动流程相对固定。进入前端目录先安装依赖cd frontend npm install这里我提前打一个预防针npm install非常容易报错。最经典的错误是node-sass安装失败因为node-sass是C模块需要本地编译环境Node版本不匹配的时候必挂。如果你是Vue 2项目且用到了node-sass建议检查一下它要求的Node版本实在不行可以把node-sass换成dart-sass也就是sass包API基本兼容安装过程要友好得多。依赖安装完成之后启动开发服务器npm run serve默认情况下Vue CLI项目的开发服务器跑在http://localhost:8081。打开浏览器访问这个地址应该能看到前端页面。这里通常会出现一个所有前后端分离新手都会遇到的问题跨域请求被浏览器拦截。因为你的前端在8081端口后端在8080端口两个端口不同浏览器同源策略就会拦截请求响应。解决办法在开发阶段最常用的有两个。第一个是在前端配置代理转发以Vue CLI为例在vue.config.js里加一段配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }这段配置的意思是当页面发起/api开头的请求时开发服务器自动把它转发到http://localhost:8080。这样浏览器端看到的请求还是同源的就不会被拦截了。注意这里pathRewrite的问题——如果你的前端请求路径是/api/user/login而后端Controller的路由是/user/login那你需要做路径重写如果后端接口本来就以/api开头就取消pathRewrite。第二种做法是在后端加CORS全局配置加一个配置类实现WebMvcConfigurer接口重写addCorsMappings方法允许所有来源访问。这种方案在开发阶段方便但在生产环境要小心因为跨域配置太宽泛会带来安全隐患。4.5 生产环境打包前端构建与后端部署开发完成之后最终是要部署到服务器上的。前后端分离项目在部署阶段有一个经典的设计取舍到底是把前端打包出来的静态文件扔到后端的resources/static目录里然后用同一个端口访问还是让前端和后端分开部署、用Nginx做反向代理很多课程项目为了省事直接把前端npm run build生成的dist目录复制到后端的静态资源目录里这样整个项目成了一个单体应用不用处理跨域。但这种做法的坏处也很明显前端和后端无法独立发布每次改个前端页面都要重新打后端包。我个人的建议是使用Nginx方案这样才能真正发挥前后端分离架构的优势。前端构建产物就一行命令npm run build执行完会在frontend/dist目录下生成一堆静态文件包括index.html、js目录、css目录等。把这些文件上传到服务器的某个目录比如/opt/yoga/frontend。后端打包依然用Mavenmvn clean package -DskipTests生成的JAR包上传到服务器的/opt/yoga目录下。用nohup java -jar yoga-system.jar log.txt 21 命令后台启动。然后在服务器上安装Nginx配置一个server块核心配置是这两层server { listen 80; server_name your_domain_or_ip; location / { root /opt/yoga/frontend; index index.html; 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模式必须的配置否则你直接访问某个子路由比如/admin的时候Nginx找不到对应的静态文件会返回404。第二location /api/的代理转发把前端请求的/api前缀透传给了后端接口也就是说后端接口路径本身是要带/api的。如果你的后端接口不带/api前缀那这里就需要加rewrite命令做路径剥离。配置好Nginx之后nginx -t检查语法然后nginx -s reload重载配置整个系统就算正式上线了。5. 常见问题排查与避坑实录5.1 SpringBoot版本太高导致的各种诡异问题先聊一个我在SpringBoot项目里遇到频率极高的问题版本太高反而引发一系列兼容性异常。身边不少同学跟着网上的教程做项目习惯性地去Spring Initializr下载最新版本的SpringBoot然后一旦项目里用的是老版本的MyBatis-Starter或者其他第三方依赖就会出现各种莫名其妙的报错。比如ClassNotFoundException、NoSuchMethodException、Failed to configure a DataSource等等。这种问题的排查思路很清晰先查看你项目pom.xml里SpringBoot的父版本号然后去查它对应依赖的兼容版本。SpringBoot 2.7.x就老老实实配MyBatis-Starter 2.x别去用还处于预览版的MyBatis-Starter 3.x。成熟稳定、使用人数最多这本身就是选型时的重要指标。如果你的项目报错实在找不到原因不妨先检查一下是不是版本兼容的问题——把SpringBoot的版本降一档问题往往就消失了。5.2 MyBatis配置文件里的常见坑MyBatis踩坑主要集中在两个地方。第一个是XML文件里的SQL占位符和业务参数对不上。比如你写了一个WHERE id #{id}但是Mapper接口的方法参数名不叫id程序运行时会直接报Parameter id not found的错误。解决办法是给方法参数加Param(id)注解或者在XML里使用#{param1}这样的位置引用方式。我个人推荐前者因为参数名写清楚之后代码的可读性会好很多。第二个常见坑是动态SQL标签写错了位置或者用错了标签。举一个很经典的例子你写了一个带条件查询的SQLselect idselectList resultTypecom.example.entity.Course SELECT * FROM course where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if /where /selectwhere标签会智能地去掉第一个多余的AND但如果你不用where而是自己写WHERE 11再加if判断那建议你直接用where因为它内置了“自动去掉第一个AND/OR”的逻辑简洁得多也不会出现SQL拼接出错的问题。还有一个几乎人人都会踩的坑MySQL查询结果映射成Java对象时驼峰命名和下划线命名对不上。比如数据库字段是create_timeJava属性是createTime默认情况下MyBatis是映射不上的查询出来的对象这个字段就是null。解决方法是开启驼峰映射配置mybatis: configuration: map-underscore-to-camel-case: true这一个配置能帮你解决掉90%的字段映射问题强烈建议写上。5.3 Vue前端接口联调阶段的高频报错前后端联调阶段前端控制台最常见的报错就是403和404。403通常是权限问题。最常见的原因是你请求的接口需要登录权限但前端没有在后端请求中带上token或者token已经过期了。排查思路是打开浏览器开发者工具切到Network选项卡点击那个报错的请求看看请求头里有没有Authorization字段。如果没有问题就出在前端的请求拦截器没有生效如果有但后端还是返回403那可能是token解析失败——比如密钥不一致、token过期。404则要仔细区分。如果是请求路径404先看后端控制台有没有对应的请求日志如果连日志都没有说明前端的请求路径和后端RequestMapping注解里的路径对不上重点检查接口前缀是否一致。比如后端是/api/user/login前端请求的是/user/login就差了那一个/api。这类问题在开发阶段最典型的触发点就是配了代理转发和没配代理转发时前端请求的路径写得不一致。还有一个很隐蔽的问题Vue打包之后页面加载正常但访问子路由刷新后404。这个就是我在部署部分提到过的try_files配置问题。如果你没用Nginx直接把dist目录挂在某个静态服务器下这个坑一定躲不掉。5.4 MySQL连接和中文乱码问题排查MySQL相关的报错里最高频的两个是连接超时和中文乱码。连接超时一般是URL里没有加serverTimezone参数导致的——报错信息通常包含The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这种乱码一样的提示其实就是服务器的时区值不被JDBC识别。解决办法就是在数据库连接URL后面加上serverTimezoneAsia/Shanghai同时注意你的数据库驱动版本MySQL 8.0以上需要用com.mysql.cj.jdbc.Driver老版本驱动类名是com.mysql.jdbc.Driver用错了也会报错。中文乱码问题核心在三个方面。第一数据库本身要设置成utf8mb4字符集第二数据库连接的URL要加useUnicodetruecharacterEncodingutf8第三前端页面的meta charsetUTF-8和接口返回时的Content-Type要带charsetUTF-8。这三个环节任何一个断了就会出现中文乱码。但一般开发阶段遇到的乱码90%都是连接URL没配置编码导致的优先检查这一项。还有一个经常被忽略的细节在使用MyBatis时如果你往MySQL里批量插入数据一定要在连接URL后面加上allowMultiQueriestrue参数否则批量插入的SQL执行时会直接报语法错误而且报错信息比较隐蔽不容易联想到这个参数上。6. 项目二次开发与能力延伸建议跑完整套流程之后如果想把这次实战的价值最大化我建议在现有基础上做一些二次开发的小练习不需要多复杂但每一项都能帮你把知识钉得更牢。第一个建议给系统加上“课程评分”功能。会员上完课后可以对这节课和教练打分课程表或者教练信息那里展示平均分。这个功能会牵扯到新增评分表、编写评分接口、在课程详情页展示评分数据前后端都要动是一个非常好的全栈练习。而且它有一个很有意思的点——平均分是聚合数据可以训练你用SQL写AVG函数的熟练度。第二个建议把前端的列表页加上真正的分页功能。现在很多课程项目列表页是把所有数据都查出来一次性渲染到表格里但真实项目中数据量一大这种写法就会把页面卡死。用MyBatis的PageHelper插件或者手写LIMIT加分页参数把分页做完对理解“前后端如何配合处理分页”特别有帮助。第三个建议完善会员的生日提醒功能。给会员表加一个birthday字段然后在后端写一个定时任务每天早上检查当天过生日的会员给运营人员推送一条消息。这个功能会用到SpringBoot的Scheduled注解还能练习一下Quartz定时任务的基础用法。你不用真的接入短信或者微信推送只要在控制台里打一条日志流程跑通就算达成目标了。另外说一下这个项目可以怎么魔改成其他行业。瑜伽馆管理系统的核心表结构是“会员-课程-预约-排课”这个骨架换到健身房、舞蹈工作室、美容美发店都完全成立。你只需要改掉课程名称、服务项目这些业务字段整个系统就能适配一个新的行业。这也是我为什么一直建议大家抽时间读一遍完整项目源码的原因——你掌握的是一套可复用的业务模型而不是死记硬背某个行业的功能清单。7. 写在最后的实操总结项目完整跑通一遍之后我强烈建议你做一个动作不要急着开新项目而是把源码从头到尾读一遍。别只写代码一边读一边在注释里写自己的理解——这个Controller为什么要写这个校验逻辑这个Service方法为什么事务要这么设计这个Mapper的SQL为什么要这么写而不那么写。你自己动手写过一遍之后再读感受会完全不一样。我在实际带新人的过程中见过太多次这样的现象项目能跑通但问他“这个预约名额是怎么避免超卖的”或者“token过期之后前端是怎么处理跳转的”就说不清楚了。能用代码和不会用代码中间隔着的就是对这些细节的深度理解。这个项目最大的价值不是帮你交一份课程作业或者完成一次实训而是让你真正理解一个前后端分离系统从设计到上线的完整闭环。最后再分享一个小技巧如果你在读源码的过程中发现自己对某一块的理解特别吃力比如MyBatis的动态SQL或者Vue的响应式原理不用急着死磕。先把它记下来然后去找三到五个同类的实战项目在练习过程中反复对照等到量积累够了那些当初怎么都想不通的概念会在某一天突然贯通。学习编程本质上就是一个“大量输入、反复实践、持续复盘”的过程这个瑜伽馆管理系统就是你的第一块跳板。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询