SpringBoot+Vue3+DeepSeek前后端分离健身房管理系统实战

SpringBoot+Vue3+DeepSeek前后端分离健身房管理系统实战 把 SpringBoot、Vue3、DeepSeek 这三个词放在同一个项目里最吸引人的地方不是“最新技术全部用上”而是它能构成一条完整的前后端分离开发链路后端处理业务和数据前端负责交互和展示再通过 DeepSeek API 补一个带有真实业务价值的 AI 教练功能。用健身房管理系统来做载体是因为业务边界足够清楚会员、教练、课程、预约、订单这些实体之间的关系非常自然做成毕业设计、课程设计或秋招简历项目都不会显得空洞。我的建议是别把“2小时搭建”当成目标把“能讲清楚每一步为什么这么设计”当成验收标准。这个项目真正值得投入的原因不是源码本身多复杂而是你可以在一个可控范围内把后端接口、前端页面、数据库建模、第三方 API 接入、接口联调、部署上线完整走一遍。下面按我实际搭建时踩过的顺序把它拆开讲一遍。1. 这个项目到底适合谁为什么很多人做完依然拿不到加分项1.1 “健身房管理系统”这个选题好在哪很多人做毕设或课设时习惯性选图书管理系统、购物商城、班级管理系统。这些选题不是不行而是太常见面试官一眼就能猜到项目里有什么很难留下印象。健身房管理系统的好处在于业务场景足够具体不用花大量时间向别人解释需求。数据模型清晰会员、教练、课程、预约、订单之间关联自然。功能有梯度既能做基础 CRUD也能做预约冲突检测、续卡、统计报表还能接入 AI 教练。演示效果好前端有表格、日历、聊天窗口视觉上不枯燥。更关键的是这个系统可以承载“前后端分离”这个核心概念。前端用 Vue3 管理页面和状态后端用 SpringBoot 提供接口数据库存业务数据DeepSeek 负责智能对话能力。整条链路是完整的不是某个单点功能。1.2 哪些人适合做哪些人容易被源码坑适合做的人有三类有一定的 Java 和 SQL 基础想通过一个完整项目把 SpringBoot、MyBatis、MySQL 串起来的人。想练 Vue3 和前后端联调的前端开发者。需要交毕设或课设并且希望项目有“AI 亮点”的学生。不太建议一类人直接上手完全没有接触过 SpringBoot 和 Vue 的零基础同学。不是说不能学而是直接拿源码跑遇到环境报错、依赖冲突、版本问题时很容易卡在第一步。网上很多源码项目给的是 SpringBoot 3.x JDK 17 的组合如果本机还是 JDK 8一启动就报错。很多人觉得是源码有问题实际上多半是版本不匹配。有些人还会直接用若依这类脚手架改项目这也没问题但要注意如果面试官深入问“这个权限拦截是怎么实现的”“这个代码生成器生成的代码你改过吗”答不上来反而减分。我更建议的做法是源码项目只用来参考核心模块自己写一遍。建议先把“单条业务流”跑通再批量加接口。链路通了一百个接口都是重复劳动链路不通十个接口就够你排查一晚上。2. 动手之前先把环境、版本和目录设计定下来2.1 环境准备和版本组合开始写代码之前先确认环境。常见环境要求如下工具作用建议JDK运行 SpringBoot 后端17SpringBoot 3.x 必须 JDK 17 以上Maven管理后端依赖和打包3.6MySQL存储业务数据8.x 或 5.7Node.js构建 Vue3 前端18npm / yarn安装前端依赖随 Node.js 一起安装IDEA 或 VSCode开发工具按习惯选择这里最容易踩的坑就是 JDK 和 SpringBoot 版本组合。假设项目的 pom.xml 里 spring-boot-starter-parent 是 3.x那么 JDK 必须是 17 以上。如果还在用 JDK 8要么换 JDK要么把 SpringBoot 降到 2.7.x。很多教程是老版本照抄之后发现启动失败先看版本不要先怀疑代码。MySQL 连接时还要注意时区配置。经典报错The server time zone value ... is unrecognized多数是因为连接串里没有加serverTimezoneAsia/Shanghai。2.2 前后端分离的目录结构项目结构不需要一开始就完美但要有一个清晰的拆分思路。我的常用结构是这样的gym-project/ ├── gym-backend/ │ ├── src/main/java/com/example/gym/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # 数据访问层 │ │ ├── entity/ # 数据库实体 │ │ ├── config/ # 配置类、拦截器 │ │ └── common/ # 统一返回、异常处理、工具类 │ └── src/main/resources/ │ ├── application.yml │ ├── mapper/ # MyBatis XML 文件 │ └── db/init.sql # 建库建表脚本 └── gym-frontend/ ├── src/ │ ├── api/ # 接口封装 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia 状态管理 │ ├── views/ # 页面组件 │ └── components/ # 公共组件 └── package.json分开目录的好处是前端可以独立启动、独立打包后端只管接口不需要关心页面长什么样。这也正好对应“前后端分离”的核心理念面试时可以展开讲。2.3 统一接口返回比想象的更重要前后端联调时最怕的就是每个接口返回格式都不一致。今天这个返回{ code: 0, data: {...} }明天那个返回{ success: true, result: {...} }前端处理起来会很痛苦。所以后端需要定义一个统一返回类例如public class ResultT { private int code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(int code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }前端 axios 拦截器只需要处理三种情况2xx 正常、401 未登录、其他错误。不要把错误判断散落在每个页面里。3. 后端怎么搭从数据库表到会员接口把“能跑”变成“能讲”3.1 数据库先建模再写代码很多人习惯先写实体类再让工具生成数据库表。这种方式快但容易忽略字段含义和业务规则。我建议先手动建表把关系理清楚。健身房管理系统核心表可以这样设计表名说明关键字段member会员表id, name, phone, gender, card_type, card_expire, statustrainer教练表id, name, specialty, introcourse课程表id, name, type, duration, capacity, trainer_id, start_timeappointment预约记录表id, member_id, course_id, appoint_time, statuspayment_order续卡/订单表id, member_id, amount, pay_type, status, create_timeai_chatAI 对话记录表id, member_id, user_message, assistant_reply, create_time建表脚本统一放到db/init.sql里不要只在本地数据库里建一次就完事。这样项目换到另一台机器时可以快速复现。课程表和教练表用trainer_id关联预约表和会员表、课程表用member_id、course_id关联。外键在学习阶段可以加但也要能说清楚为什么有些场景不建议依赖外键比如高并发写入时外键会带来额外校验开销分库分表后外键约束基本不可用。3.2 SpringBoot 工程和核心依赖创建 SpringBoot 项目时最简单的办法是用 Spring Initializr。也可以手动创建一个 Maven 工程再引入依赖。核心依赖大概包含这些spring-boot-starter-web mybatis-plus-boot-starter mysql-connector-j lombok spring-boot-starter-validation jjwt 或 java-jwt springdoc-openapi这里不写具体版本号因为版本迭代太快照抄老版本很容易出现兼容性问题。建议创建项目时先确认当前官方稳定版再根据官方文档生成依赖。application.yml的配置也要在最开始就规划好server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/gym_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true deepseek: api-key: ${DEEPSEEK_API_KEY} base-url: https://api.deepseek.com model: deepseek-chat timeout: 30注意api-key不要直接写在 yml 里也不要把真实密钥提交到 GitHub。用环境变量DEEPSEEK_API_KEY前缀部署时单独配置。这一点在简历里也可以写说明你有基本的工程安全意识。3.3 登录鉴权JWT 怎么做健身房管理系统需要一个简单的登录注册能力。密码不能明文存要用 BCrypt 加密。登录成功之后后端生成一个 JWT前端后续请求都带上这个 token。JWT 的核心逻辑是登录时校验用户名和密码。校验通过后生成 token里面包含用户 ID、用户名、过期时间。前端把 token 存在 localStorage 或 Pinia 中。后端拦截器解析 token判断是否有效。工具类可以简化成下面这种结构public class JwtUtil { private static final String SECRET your-secret-key; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; public static String createToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }然后写一个拦截器在访问/api/member/**、/api/appointment/**等接口之前先从请求头取Authorization: Bearer xxx解析失败直接返回 401。问“为什么用 JWT 而不是 Session”时不要只说“无状态”。可以展开前后端分离之后前端部署在 Nginx后端接口可能是独立端口Session 做跨域需要额外处理 Cookie。JWT 把用户信息放在 token 里后端不存会话适合接口调用。但缺点也要说清楚token 一旦签发在过期前很难主动作废所以密钥管理和过期时间比较重要。3.4 会员管理接口从 Controller 到 SQL会员管理是最经典的一组 CRUD 接口也是检验后端链路是否完整的最小样例。我一般先写“分页查询”RestController RequestMapping(/api/member) public class MemberController { GetMapping(/page) public ResultPageMember page( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) String keyword) { return Result.success(memberService.pageQuery(page, size, keyword)); } }Service 里调用 MyBatis-Plus 的 Page或者写 XML 里的 select 语句都可以。重点不是选哪种技术而是把链路打通前端页面点击查询请求到达 ControllerController 调用 ServiceService 调用 MapperMapper 执行 SQL结果一层层返回。先跑通一个完整接口比一口气写十个接口更有价值。因为一个接口链路通了说明数据库连接、MyBatis 映射、JSON 序列化、统一返回、前端调用这五层都正常。后面加接口只是重复劳动。4. 前端怎么搭Vue3 页面别急着写完先定 API 封装和路由4.1 初始化项目和安装依赖前端用 Vite 创建 Vue3 项目命令写出来很简单npm create vitelatest gym-frontend -- --template vue cd gym-frontend npm install然后再安装项目需要的依赖npm install element-plus axios vue-router piniaElement Plus 提供现成的表格、表单、弹窗、消息提示适合快速搭管理后台。Pinia 用来管理用户状态比如登录后的用户信息和 token。在main.js里注册 Element Plus、router、pinia顺序没有严格要求但建议保持一致import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount(#app)4.2 axios 封装把 token 和错误提示统一处理前端每个页面都直接调axios.get不是不行但会非常散乱。更稳妥的做法是封装一个request.js统一处理 baseURL、token 注入、响应错误。import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { // 统一错误提示 return Promise.reject(new Error(res.message || error)) } return res }, error { if (error.response error.response.status 401) { // token 失效跳回登录页 } return Promise.reject(error) } ) export default request这里有一个容易忽略的点开发环境下/api这个前缀最好通过 Vite proxy 转发到后端而不是直接写死http://localhost:8080。否则还要处理跨域 CORS。生产环境则通过 Nginx 把/api转发到后端服务前端静态资源和后端接口在同一个域名下跨域问题自然少很多。4.3 路由设计登录、主布局、功能页面路由建议分成两部分登录页和主布局页。主布局里放顶部导航、侧边菜单、内容区。const routes [ { path: /login, component: () import(/views/LoginView.vue) }, { path: /, component: () import(/layout/MainLayout.vue), redirect: /dashboard, meta: { requiresAuth: true }, children: [ { path: dashboard, component: () import(/views/DashboardView.vue) }, { path: member, component: () import(/views/MemberView.vue) }, { path: course, component: () import(/views/CourseView.vue) }, { path: appointment, component: () import(/views/AppointmentView.vue) }, { path: ai-coach, component: () import(/views/AiCoachView.vue) } ] } ]路由守卫里判断是否登录router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })这一步能让你在面试时讲“前端路由权限控制”而不是只有后端拦截器。虽然这只是最简单的判断但至少证明了你有完整的权限意识。4.4 页面不能只做“静态展示”很多 Vue3 项目做完页面看起来很多但全是静态写死的数据没有对接接口。这样面试时被问“你这个表格数据哪来的”回答不上来就很尴尬。真正的页面开发应该按这个逻辑来页面加载时调用后端接口获取数据。表格展示接口返回的列表。搜索和分页触发重新请求。新增和编辑用弹窗表单。删除要加确认提示。所有请求都要有 loading 状态。会员管理页面可以做成左边搜索栏右边表格顶部新增按钮底部有分页。课程预约页面可以做成选择课程、选择会员、提交预约如果课程已满则提示错误。AI 教练页面可以做成聊天窗口输入问题调用后端 AI 接口返回结果显示在气泡里。这样一套做完前端部分基本就立体了不是套壳。5. DeepSeek 接入把 AI 功能做成简历里的亮点而不是噱头5.1 先想清楚 AI 功能放在哪接入 AI 最忌讳的是“为了 AI 而 AI”。比如登录页挂个聊天框问什么答什么和系统业务完全没有关系。这样面试官只会觉得你是调了一个 API 玩了一下。健身房管理系统里AI 功能可以放在“AI 教练助手”这个位置。会员输入自己的情况比如“我每周只能锻炼三次目标是减脂能帮我安排一周训练计划吗”后端拼接好上下文调用 DeepSeek返回一份训练计划然后把对话记录存到ai_chat表。为了让回复更可控可以设计一段 System Prompt你是健身房的 AI 教练助手。你熟悉力量训练、有氧训练、饮食建议和会员安全注意事项。 请根据会员提供的身体情况、训练频率和目标生成简单可执行的训练计划。 如果会员提到伤病或不适请建议其咨询线下专业教练。这样 AI 不是乱聊而是围绕业务场景服务。在简历里写“基于 DeepSeek API 实现 AI 教练对话支持训练计划生成和多轮上下文管理”比“接入了大模型”有说服力得多。5.2 为什么后端转发而不是前端直连很多人会直接在前端调用 DeepSeek 的接口这样确实代码少但问题也很明显API Key 暴露在前端代码里别人抓包就能看到直接盗用。无法统一控制调用频率用户连续点击会消耗大量额度。没有业务日志出问题不知道怎么排查。不方便记录对话内容后续无法分析。所以正确做法是前端把用户消息发给后端后端带上 API Key 调用 DeepSeek拿到结果后再返回给前端。这样前端只负责展示后端统一管控。5.3 调用 DeepSeek API 的请求流程和示例DeepSeek 官方接口格式在兼容 OpenAI 协议时请求体通常包含model、messages、temperature等字段。具体 URL 和模型名要以官方文档为准。下面是一个通用调用思路使用 Spring 的RestTemplateHttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(deepSeekProperties.getApiKey()); MapString, Object body new HashMap(); body.put(model, deepSeekProperties.getModel()); ListMapString, Object messages new ArrayList(); messages.add(Map.of(role, system, content, 你是健身房AI教练助手)); messages.add(Map.of(role, user, content, userMessage)); body.put(messages, messages); body.put(temperature, 0.7); HttpEntityMapString, Object requestEntity new HttpEntity(body, headers); ResponseEntityDeepSeekResponse response restTemplate.postForEntity( deepSeekProperties.getBaseUrl() /chat/completions, requestEntity, DeepSeekResponse.class);后端拿到响应后从返回结构里取出生成内容再和用户消息一起保存到数据库最终返回给前端。这里要注意DeepSeek 的返回结构字段名是什么必须根据官方文档确认不要凭记忆写死。不同版本、不同兼容模式可能字段命名有差异。5.4 超时、失败与上下文AI 接口最影响体验的往往是“慢”。普通接口几十毫秒返回AI 接口可能几秒甚至十几秒。所以要做几件小事设置连接超时和读取超时建议 30 秒左右避免线程长时间挂起。前端请求时显示 loading让用户知道系统正在生成而不是卡死。捕获超时异常返回“AI 暂时不可用请稍后再试”不能直接把堆栈抛给前端。后端的 DeepSeek 调用要用 try-catch 包一层失败时记录日志并给前端一个有提示的降级响应。多轮对话还有一个容易踩的坑如果把所有历史消息都发给模型消息会越来越多token 超过限制后直接报错。我一般会保留最近 6 轮对话更早的只保留一句摘要或者直接丢弃。这个取舍可以在面试时说清楚说明你考虑过上下文长度问题。6. 联调、验证清单和常见报错跑通之后才算完成6.1 联调顺序前后端分别开发完之后要回到“完整业务流”这个层面做联调。不要只在接口文档里调通而是要模拟真实用户操作。我的联调顺序是先执行db/init.sql确保数据库里有干净的表结构和测试数据。启动后端确认 8080 端口正常。启动前端 Vite 开发服务检查/api代理是否指向localhost:8080。用浏览器访问前端登录页用测试账号登录。跑一条完整业务流登录 - 新增会员 - 新增课程 - 会员预约课程 - 使用 AI 教练生成训练计划 - 查看对话记录。这一步最容易发现的问题就是“接口单独调是好的页面一操作就报错”。大部分原因是前端传参格式和后端接收不一致比如后端要 JSON前端用表单格式提交后端字段叫memberName前端传的是name。6.2 接口文档怎么加接口文档不是给老师看的摆设是给自己和合作者用的。如果项目是单人完成至少也要把接口文档导出来方便答辩时展示。有两种常见方式后端集成 springdoc-openapi启动后访问/swagger-ui.html或/v3/api-docs。用 Apifox 或 Postman 管理接口导入后端接口后分组整理。我更推荐 springdoc-openapi因为它从代码注解自动生成文档不需要额外维护一份 Markdown。给每个 Controller 加Tag给接口加Operation文档质量会好很多。6.3 一次完整验证清单联调结束后可以按下面的清单逐项验证验证项操作预期结果常见排查点登录提交手机号和密码返回 token跳转首页数据库有无账号、密码加密是否一致会员分页请求/api/member/page返回分页数据表名和实体是否一致新增课程填表单并提交列表多一条记录字段校验、数据库字段是否完整预约冲突预约已满的课程提示课程已满后端预约校验逻辑是否生效AI 对话输入“安排一周训练计划”返回内容并保存记录API Key、超时、网络、请求格式每项验证通过后再把对应模块的代码顺手整理一下。这样最后交付的不仅是可运行的系统还有清晰的验证记录。6.4 常见报错和排查顺序项目跑不起来的报错其实就那么几类关键是排查顺序要正确。第一步先看现象是启动失败、接口 404、还是页面无响应。第二步看日志后端控制台输出前端浏览器 Network 面板的状态码和响应体。第三步看配置端口、数据库连接、代理地址。第四步看依赖JDK 版本、SpringBoot 版本是否匹配。常见的几个具体问题后端启动报数据库连接失败先看 MySQL 是否启动、账号密码是否正确、serverTimezone是否配置。前端请求 404先看 Vue 的 proxy 配置再看后端 Controller 的RequestMapping路径。请求返回 401先看 token 是否带上了再检查 JWT 密钥是否一致过期时间是否太短。跨域报错开发环境优先用 Vite proxy不要每个接口都去写CrossOrigin。端口被占用Windows 用netstat -ano | findstr 8080Mac/Linux 用lsof -i :8080找到进程。遇到报错先看日志再改参数。很多问题不是代码逻辑不对而是路径、权限、版本、配置这些前置条件没满足。7. 简历和答辩怎么包装别把项目写成功能列表7.1 简历项目描述怎么写简历上写项目不能只列“会员管理、课程管理、预约管理”这种纯功能清单。要写出技术链路和你的设计思路。可以参考这个写法健身房管理系统SpringBoot Vue3 DeepSeek - 独立完成后端接口与前端页面开发实现前后端分离包含会员管理、课程预约、订单和 AI 教练对话模块。 - 后端使用 SpringBoot MyBatis-Plus JWT统一返回结构使用拦截器校验 token密码 BCrypt 加密存储。 - 前端使用 Vue3 Element Plus Pinia Vue Router封装 axios实现登录态管理和路由守卫。 - 集成 DeepSeek API 实现 AI 教练对话支持多轮上下文管理、超时降级和对话记录持久化。 - 通过 Nginx 部署前端静态资源后端接口独立运行数据库脚本和接口文档齐全。注意不要写“2小时完成”也不要写“性能达到每秒几万并发”。这些经不起追问。7.2 面试官高频问题这个项目能延伸出来的面试题不少常见的有前后端分离解决了什么问题可以答职责拆分、独立部署、并行开发、接口化。JWT 和 Session 有什么区别可以答无状态、跨域友好、但吊销困难。密码为什么不存明文可以答数据库泄露风险BCrypt 加盐哈希。项目里哪里最复杂可以答预约冲突校验、AI 上下文管理、权限拦截。DeepSeek 返回慢怎么办可以答超时设置、前端 loading、后端异步化、失败降级。会员数据量大了怎么优化可以答分页、索引、Redis 缓存、慢查询日志。线上部署怎么做可以答前端 dist 放 Nginx后端 jar 独立运行MySQL 单独部署。回答时不要背概念要结合项目里的实际选择。比如说到 Redis 缓存就说“可以用 Redis 缓存课程列表降低数据库压力但这个项目目前没做是后续扩展方向”这样反而显得真实。7.3 答辩和现场演示的暗坑现场演示是最容易翻车的一环。提前准备下面几件事准备好测试账号不要现场注册。测试数据要干净不要留一堆乱造的数据。AI 功能要准备一