
每到学期末课程设计和期末作业就成了许多同学的头等大事。面对一个需要从零开始、功能完整的Web系统开发任务如何在有限的时间内高效地搭建一个技术栈主流、结构清晰、功能可用的项目是大家共同的痛点。本文将手把手带你在1小时内快速构建一个基于SpringBoot、Vue3和MySQL的“健身管理系统”。这个项目不仅覆盖了前后端分离、增删改查CRUD等核心技能点还提供了完整的源码和清晰的实现思路非常适合作为课设、期末作业或入门全栈开发的实战案例。无论你是Java后端新手还是刚接触Vue3的前端同学都能跟着本文一步步完成。1. 项目背景与核心概念在开始敲代码之前我们先明确一下这个项目要做什么以及为什么要选择这些技术栈。1.1 项目需求健身管理系统想象一个真实的健身房管理场景管理员需要对会员、教练、课程、器械等信息进行管理。我们的系统将实现以下核心功能会员管理对健身房会员进行增、删、改、查。教练管理管理教练的基本信息和所属课程。课程管理发布和管理健身课程并关联教练。器械管理记录健身房器械的信息和使用状态。这四大模块完美涵盖了Web系统中最基础的增删改查CRUD操作是学习后端开发和数据库交互的绝佳练手项目。1.2 技术选型与优势我们采用目前企业级开发中非常流行的“前后端分离”架构。后端SpringBoot作为服务提供者负责业务逻辑处理、数据存取和API接口暴露。SpringBoot以其“约定大于配置”的理念能让我们快速搭建起一个稳健的后端服务。前端Vue3作为用户交互界面负责数据的展示和用户操作的收集。Vue3以其组合式API、更好的TypeScript支持和更高的性能是目前前端开发的主流选择。数据库MySQL关系型数据库用于持久化存储我们的业务数据。项目构建与依赖管理后端使用Maven前端使用Vite或Webpack它们能帮助我们高效地管理项目依赖和构建过程。这种架构的优势在于前后端职责清晰可以并行开发接口定义好后前端可以Mock数据先行开发后端则专注于API实现非常适合团队协作和现代Web开发。2. 环境准备与版本说明工欲善其事必先利其器。请确保你的开发环境已安装以下工具。版本号以本文撰写时的稳定版为例如果你的环境不同大部分配置思路是通用的。2.1 后端开发环境JDK版本 8 或 11推荐11。SpringBoot 2.x 对JDK 8兼容性好SpringBoot 3.x 则需要JDK 17。本文示例基于SpringBoot 2.7.x。IDEIntelliJ IDEA社区版或旗舰版或 Eclipse需安装Spring插件。IDEA对SpringBoot支持更友好。Maven版本 3.6用于管理项目依赖和构建。MySQL版本 5.7 或 8.0。需要提前安装并启动MySQL服务。2.2 前端开发环境Node.js版本 16推荐18 LTS。npm包管理器会随之安装。IDEVisual Studio CodeVSCode是前端开发的首选轻量且插件丰富。2.3 辅助工具Postman 或 Apifox用于测试后端API接口。Git用于版本控制和获取本文的示例源码如果需要。你可以通过以下命令快速检查环境# 检查Java和Maven java -version mvn -v # 检查Node.js和npm node -v npm -v # 检查MySQL客户端确保服务已启动 mysql --version3. 后端开发SpringBoot项目搭建与核心实现我们将从零开始创建SpringBoot项目并实现会员管理的全套RESTful API。3.1 创建SpringBoot项目使用Spring Initializrhttps://start.spring.io/或IDEA内置的Spring Initializr快速创建项目。Project: MavenLanguage: JavaSpring Boot: 2.7.18 (选择一个稳定的2.x版本)Group: com.example (根据你的习惯修改)Artifact: fitness-systemDependencies: 添加Spring Web,Spring Data JPA,MySQL Driver,Lombok。点击生成并下载然后用IDE打开项目。pom.xml文件应包含类似依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies3.2 配置数据库连接在src/main/resources/application.properties文件中配置MySQL连接信息。请将your_password和your_database替换为你自己的。# 应用端口 server.port8080 # 数据库配置 spring.datasource.urljdbc:mysql://localhost:3306/fitness_db?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # JPA配置 spring.jpa.database-platformorg.hibernate.dialect.MySQL8Dialect spring.jpa.hibernate.ddl-autoupdate # 首次启动用create后续用update或validate spring.jpa.show-sqltrue # 开发时显示SQL生产环境关闭 spring.jpa.properties.hibernate.format_sqltrue在MySQL中创建数据库CREATE DATABASE IF NOT EXISTS fitness_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;3.3 创建实体类Entity实体类对应数据库中的表。我们以Member会员实体为例。// 文件路径src/main/java/com/example/fitnesssystem/entity/Member.java package com.example.fitnesssystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name member) // 指定表名 Data // Lombok注解自动生成getter, setter, toString等方法 public class Member { Id GeneratedValue(strategy GenerationType.IDENTITY) // 主键自增 private Long id; Column(nullable false, length 50) private String name; // 会员姓名 Column(unique true, nullable false, length 20) private String phone; // 手机号唯一 Column(nullable false) private String gender; // 性别 Column(name birth_date) private LocalDateTime birthDate; // 出生日期 Column(name membership_type, length 20) private String membershipType; // 会员卡类型月卡、季卡、年卡 Column(name join_date) private LocalDateTime joinDate LocalDateTime.now(); // 入会时间默认当前时间 Column(columnDefinition int default 1) private Integer status 1; // 状态 1:有效 0:失效 }说明Data是Lombok的注解极大简化了JavaBean的代码。GeneratedValue指定主键生成策略为数据库自增。3.4 创建数据访问层RepositorySpring Data JPA 提供了强大的数据访问抽象我们只需定义一个接口。// 文件路径src/main/java/com/example/fitnesssystem/repository/MemberRepository.java package com.example.fitnesssystem.repository; import com.example.fitnesssystem.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; Repository public interface MemberRepository extends JpaRepositoryMember, Long { // 可以根据方法名自动生成查询例如根据手机号查找 Member findByPhone(String phone); // 查询所有状态为有效的会员 ListMember findByStatus(Integer status); }说明继承JpaRepository实体类, 主键类型后该接口就自动拥有了基本的CRUD方法save, findById, findAll, deleteById等。findByPhone是自定义查询JPA会根据方法名自动解析成SQL。3.5 创建业务逻辑层ServiceService层处理核心业务逻辑是Controller和Repository之间的桥梁。// 文件路径src/main/java/com/example/fitnesssystem/service/MemberService.java package com.example.fitnesssystem.service; import com.example.fitnesssystem.entity.Member; import com.example.fitnesssystem.repository.MemberRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; Service public class MemberService { Autowired private MemberRepository memberRepository; // 新增或更新会员 public Member saveMember(Member member) { // 简单的业务逻辑检查手机号是否已存在更新时除外 if(member.getId() null) { // 新增 Member existing memberRepository.findByPhone(member.getPhone()); if(existing ! null) { throw new RuntimeException(手机号已存在); } } return memberRepository.save(member); } // 根据ID查询会员 public Member getMemberById(Long id) { OptionalMember optional memberRepository.findById(id); return optional.orElseThrow(() - new RuntimeException(会员不存在ID: id)); } // 查询所有会员 public ListMember getAllMembers() { return memberRepository.findAll(); } // 删除会员逻辑删除将状态改为0 public void deleteMember(Long id) { Member member getMemberById(id); member.setStatus(0); memberRepository.save(member); // 更新状态而非物理删除 } // 查询有效会员 public ListMember getActiveMembers() { return memberRepository.findByStatus(1); } }说明这里演示了逻辑删除即通过修改状态字段来标记删除而不是真的从数据库移除记录。这在生产环境中更常见可以保留数据轨迹。3.6 创建控制器层ControllerController层接收HTTP请求调用Service并返回JSON响应。// 文件路径src/main/java/com/example/fitnesssystem/controller/MemberController.java package com.example.fitnesssystem.controller; import com.example.fitnesssystem.entity.Member; import com.example.fitnesssystem.service.MemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/members) // 统一API路径前缀 public class MemberController { Autowired private MemberService memberService; // 创建会员 POST /api/members PostMapping public Member createMember(RequestBody Member member) { return memberService.saveMember(member); } // 获取所有会员 GET /api/members GetMapping public ListMember getAllMembers() { return memberService.getAllMembers(); } // 根据ID获取会员 GET /api/members/{id} GetMapping(/{id}) public Member getMemberById(PathVariable Long id) { return memberService.getMemberById(id); } // 更新会员信息 PUT /api/members/{id} PutMapping(/{id}) public Member updateMember(PathVariable Long id, RequestBody Member memberDetails) { Member member memberService.getMemberById(id); // 更新字段实际项目中可使用BeanUtils或MapStruct member.setName(memberDetails.getName()); member.setPhone(memberDetails.getPhone()); member.setGender(memberDetails.getGender()); member.setBirthDate(memberDetails.getBirthDate()); member.setMembershipType(memberDetails.getMembershipType()); return memberService.saveMember(member); } // 删除会员 DELETE /api/members/{id} DeleteMapping(/{id}) public String deleteMember(PathVariable Long id) { memberService.deleteMember(id); return 会员删除成功状态已置为无效; } }说明RestController表明这是一个返回JSON数据的控制器。RequestMapping定义了根路径。我们实现了标准的RESTful风格API。3.7 解决跨域问题由于前端运行在localhost:5173(Vite默认端口)后端在localhost:8080浏览器会因同源策略阻止请求。我们需要在后端配置CORS。// 文件路径src/main/java/com/example/fitnesssystem/config/WebConfig.java package com.example.fitnesssystem.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 允许跨域的路径 .allowedOrigins(http://localhost:5173) // 允许的前端地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) // 允许的请求方法 .allowedHeaders(*) // 允许的请求头 .allowCredentials(true); // 允许携带凭证如Cookie } }3.8 启动与测试后端找到主启动类FitnessSystemApplication运行其中的main方法。看到控制台输出Started FitnessSystemApplication且无报错说明启动成功。打开Postman测试APIPOSThttp://localhost:8080/api/membersBody (raw JSON):{ name: 张三, phone: 13800138000, gender: 男, birthDate: 1990-01-01T00:00:00, membershipType: 年卡 }GEThttp://localhost:8080/api/members查看列表。PUThttp://localhost:8080/api/members/1更新数据。DELETEhttp://localhost:8080/api/members/1逻辑删除。4. 前端开发Vue3项目搭建与页面实现后端API准备就绪后我们开始构建前端用户界面。4.1 创建Vue3项目使用Vite快速创建Vue3项目它比传统的Vue CLI更快。# 在合适的目录下执行 npm create vuelatest fitness-frontend # 根据提示选择配置TypeScript? No, JSX? No, Router? Yes, Pinia? Yes, ESLint? Yes cd fitness-frontend npm install安装项目所需的UI组件库和HTTP客户端这里以Element Plus和Axios为例npm install element-plus axios npm install --save-dev element-plus/icons-vue # 图标库4.2 配置Axios和全局样式在src目录下创建utils/request.js文件封装Axios实例。// 文件路径src/utils/request.js import axios from axios; // 创建axios实例 const service axios.create({ baseURL: http://localhost:8080/api, // 后端API基础地址 timeout: 10000 // 请求超时时间 }); // 请求拦截器 service.interceptors.request.use( config { // 在发送请求之前做些什么例如添加token // const token localStorage.getItem(token); // if (token) { // config.headers[Authorization] Bearer token; // } return config; }, error { // 对请求错误做些什么 console.error(Request Error:, error); return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( response { // 对响应数据做点什么 return response.data; }, error { // 对响应错误做点什么 console.error(Response Error:, error.response?.data || error.message); // 可以在此处统一处理错误例如提示用户 ElMessage.error(error.response?.data?.message || 网络请求失败); return Promise.reject(error); } ); export default service;修改src/main.js引入Element Plus和Axios实例。// 文件路径src/main.js import { createApp } from vue import App from ./App.vue import router from ./router import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(router) app.use(ElementPlus) app.mount(#app)4.3 实现会员管理页面我们创建一个会员列表页包含查询、新增、编辑、删除功能。 首先在src/views目录下创建MemberView.vue。!-- 文件路径src/views/MemberView.vue -- template div classmember-container h2会员管理/h2 el-row :gutter20 classmb-20 el-col :span6 el-input v-modelsearchKeyword placeholder请输入姓名或手机号 keyup.enterhandleSearch template #append el-button :iconSearch clickhandleSearch / /template /el-input /el-col el-col :span18 classtext-right el-button typeprimary :iconPlus clickhandleAdd新增会员/el-button /el-col /el-row el-table :datamemberList border stripe stylewidth: 100% el-table-column propid labelID width80 / el-table-column propname label姓名 / el-table-column propphone label手机号 / el-table-column propgender label性别 width80 template #defaultscope el-tag :typescope.row.gender 男 ? primary : danger {{ scope.row.gender }} /el-tag /template /el-table-column el-table-column propmembershipType label会员类型 / el-table-column propjoinDate label入会时间 :formatterformatDate / el-table-column propstatus label状态 width100 template #defaultscope el-tag :typescope.row.status 1 ? success : info {{ scope.row.status 1 ? 有效 : 失效 }} /el-tag /template /el-table-column el-table-column label操作 width200 fixedright template #defaultscope el-button sizesmall :iconEdit clickhandleEdit(scope.row)编辑/el-button el-button sizesmall :iconDelete typedanger clickhandleDelete(scope.row.id)删除/el-button /template /el-table-column /el-table !-- 新增/编辑对话框 -- el-dialog v-modeldialogVisible :titledialogTitle width500px el-form :modelform :rulesrules refformRef label-width80px el-form-item label姓名 propname el-input v-modelform.name placeholder请输入姓名 / /el-form-item el-form-item label手机号 propphone el-input v-modelform.phone placeholder请输入手机号 / /el-form-item el-form-item label性别 propgender el-radio-group v-modelform.gender el-radio label男男/el-radio el-radio label女女/el-radio /el-radio-group /el-form-item el-form-item label出生日期 propbirthDate el-date-picker v-modelform.birthDate typedate placeholder选择日期 value-formatYYYY-MM-DD / /el-form-item el-form-item label会员类型 propmembershipType el-select v-modelform.membershipType placeholder请选择 el-option label月卡 value月卡 / el-option label季卡 value季卡 / el-option label年卡 value年卡 / /el-select /el-form-item /el-form template #footer span classdialog-footer el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/el-button /span /template /el-dialog /div /template script setup import { ref, reactive, onMounted } from vue import { Search, Plus, Edit, Delete } from element-plus/icons-vue import { ElMessage, ElMessageBox } from element-plus import request from /utils/request // 数据与状态 const memberList ref([]) const searchKeyword ref() const dialogVisible ref(false) const dialogTitle ref(新增会员) const formRef ref() const form reactive({ id: null, name: , phone: , gender: 男, birthDate: , membershipType: 月卡 }) // 表单验证规则 const rules reactive({ name: [{ required: true, message: 请输入姓名, trigger: blur }], phone: [ { required: true, message: 请输入手机号, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确, trigger: blur } ], gender: [{ required: true, message: 请选择性别, trigger: change }] }) // 生命周期钩子 onMounted(() { fetchMemberList() }) // 方法 const fetchMemberList async () { try { const res await request.get(/members) memberList.value res } catch (error) { console.error(获取会员列表失败:, error) } } const handleSearch () { // 简单的前端过滤实际项目中可向后端传递搜索参数 if (!searchKeyword.value) { fetchMemberList() return } const keyword searchKeyword.value.toLowerCase() memberList.value memberList.value.filter(member member.name.toLowerCase().includes(keyword) || member.phone.includes(keyword) ) } const handleAdd () { dialogTitle.value 新增会员 Object.keys(form).forEach(key { if (key gender) form[key] 男 else if (key membershipType) form[key] 月卡 else form[key] }) form.id null dialogVisible.value true } const handleEdit (row) { dialogTitle.value 编辑会员 Object.assign(form, row) // 转换日期格式确保与日期选择器兼容 if (form.birthDate) { form.birthDate form.birthDate.split(T)[0] } dialogVisible.value true } const handleDelete (id) { ElMessageBox.confirm(确定要删除此会员吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(async () { try { await request.delete(/members/${id}) ElMessage.success(删除成功) fetchMemberList() // 刷新列表 } catch (error) { ElMessage.error(删除失败) } }).catch(() {}) } const submitForm async () { // 表单验证 await formRef.value.validate() try { const url form.id ? /members/${form.id} : /members const method form.id ? put : post await request({ url, method, data: form }) ElMessage.success(form.id ? 更新成功 : 新增成功) dialogVisible.value false fetchMemberList() // 刷新列表 } catch (error) { ElMessage.error(error.message || 操作失败) } } const formatDate (row, column, cellValue) { if (!cellValue) return const date new Date(cellValue) return date.toLocaleString(zh-CN) } /script style scoped .member-container { padding: 20px; } .mb-20 { margin-bottom: 20px; } .text-right { text-align: right; } /style4.4 配置路由修改src/router/index.js将会员管理页面添加到路由中。// 文件路径src/router/index.js import { createRouter, createWebHistory } from vue-router import MemberView from ../views/MemberView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, redirect: /members // 默认重定向到会员页 }, { path: /members, name: members, component: MemberView } // 后续可以在这里添加教练、课程、器械等页面的路由 ] }) export default router4.5 修改主页面布局简单修改src/App.vue添加一个导航菜单。!-- 文件路径src/App.vue -- template el-container el-aside width200px el-menu router default-active/members classel-menu-vertical-demo el-menu-item index/members el-iconUser //el-icon span会员管理/span /el-menu-item !-- 可以继续添加其他菜单项 -- !-- el-menu-item index/coaches el-iconAvatar //el-icon span教练管理/span /el-menu-item -- /el-menu /el-aside el-main router-view / /el-main /el-container /template script setup import { User } from element-plus/icons-vue /script style #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; height: 100vh; } .el-container { height: 100%; } /style4.6 启动前端项目在项目根目录下运行npm run dev访问控制台输出的地址通常是http://localhost:5173你应该能看到一个带有侧边栏导航的页面点击“会员管理”即可对会员数据进行增删改查操作。5. 常见问题与排查思路在搭建和运行过程中你可能会遇到以下问题问题现象常见原因解决思路后端启动失败端口被占用8080端口已被其他程序如Tomcat使用1. 在application.properties中修改server.port。2. 命令行执行netstat -ano | findstr :8080找到进程并结束。后端连接MySQL失败1. MySQL服务未启动。2. 数据库连接信息URL、用户名、密码错误。3. 时区配置问题。1. 检查MySQL服务状态并启动。2. 仔细核对application.properties中的配置。3. 在JDBC URL中添加serverTimezoneAsia/Shanghai。前端访问后端API报跨域错误CORS后端未配置CORS或配置不正确。1. 确保后端已添加WebConfig配置类。2. 检查allowedOrigins中的前端地址和端口是否正确。前端页面空白控制台报错1. 依赖未安装。2. 组件引入错误。3. 路由配置错误。1. 运行npm install重新安装依赖。2. 检查浏览器控制台错误信息定位到具体文件行。3. 检查router/index.js和main.js的导入路径。新增/修改数据后列表不刷新前端未在操作成功后重新调用获取列表的API。在submitForm和handleDelete方法成功回调中调用fetchMemberList()。Element Plus组件样式丢失未正确引入CSS文件。在main.js中确认已导入import element-plus/dist/index.css。6. 项目扩展与最佳实践完成基础功能后你可以从以下几个方面深化项目这会让你的课程设计或作业更加出彩。6.1 功能扩展建议完善其他模块参照会员管理实现教练Coach、课程Course、器械Equipment的完整CRUD功能。关联查询例如在课程管理中可以选择关联的教练使用下拉框选择教练ID并在查询课程列表时显示教练姓名需要联表查询或业务层拼接。用户登录与权限增加用户表实现简单的登录/注册功能并使用JWT进行鉴权。不同的用户角色如管理员、普通员工拥有不同的操作权限。数据统计与图表在仪表盘页面使用ECharts等库展示会员增长趋势、课程预约统计等图表。文件上传实现会员头像、教练资质证书的上传功能。6.2 后端工程化建议统一响应封装创建一个通用的Result类统一API返回格式如code,message,data。public class ResultT { private Integer code; private String message; private T data; // 构造方法、成功/失败静态方法等 }全局异常处理使用ControllerAdvice和ExceptionHandler捕获并处理各类异常返回友好的错误信息而不是堆栈跟踪。参数校验在Controller的入参上使用Validated注解配合JSR-303校验注解如NotBlank,Pattern避免无效数据进入业务层。使用DTO不要直接使用Entity作为API的请求/响应体。创建独立的DTOData Transfer Object类用于控制暴露的字段和进行数据转换。分页查询当数据量变大时列表查询必须支持分页。Spring Data JPA 的Pageable和Page接口可以很方便地实现。6.3 前端工程化建议状态管理对于跨组件共享的状态如用户登录信息使用Pinia进行集中管理。API接口统一管理将所有的后端API请求函数封装在src/api目录下的独立文件中例如member.js使页面组件更专注于视图和交互。环境变量使用.env.development和.env.production文件管理不同环境的后端API地址。组件化将重复使用的UI片段如日期格式化、图片预览抽离成可复用的组件。路由守卫使用Vue Router的导航守卫在页面跳转前进行权限检查和登录状态验证。6.4 部署上线简易步骤后端打包在SpringBoot项目根目录执行mvn clean package会在target目录生成fitness-system-0.0.1-SNAPSHOT.jar。运行后端在服务器上使用java -jar fitness-system-0.0.1-SNAPSHOT.jar运行。可使用nohup或配置为系统服务保持后台运行。前端构建在前端项目根目录执行npm run build生成静态文件在dist目录。部署前端将dist目录下的所有文件放到Nginx或Apache的Web服务器目录下。并配置Nginx将API请求代理到后端服务解决跨域和生产环境部署。# Nginx 配置示例片段 server { listen 80; server_name your-domain.com; # 你的域名或IP location / { root /path/to/your/fitness-frontend/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } location /api/ { proxy_pass http://localhost:8080; # 代理到后端服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }通过以上步骤你已经成功搭建了一个具备前后端分离、增删改查核心功能的SpringBootVue3健身管理系统。这个项目骨架清晰代码规范涵盖了课程设计所需的大部分技术要点。你可以在此基础上自由发挥添加更多功能模块和复杂业务逻辑。