Java健身房管理系统实战:Spring Boot+Vue全栈开发指南

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度

很多同学在做Java课程设计或毕业设计时,常常面临选题困难、技术栈不熟、代码逻辑混乱、文档无从下手等问题。一个结构清晰、功能完整、技术栈主流的实战项目,不仅能帮你顺利通过考核,更能让你在简历上增添一个亮眼的实战经验。

本文将为你完整拆解一个基于Java的健身房管理系统项目。这个项目非常适合作为课程设计、毕业设计或自学练手项目。我们将从项目背景、技术选型、环境搭建、数据库设计、前后端核心代码实现,到最终部署测试,一步步带你走完全流程。文章末尾会提供完整的源码、笔记和资料文档获取方式,确保你能“拿来即用”,并理解其背后的设计思想。

1. 项目背景与核心价值

健身房管理系统是一个典型的B/S架构信息管理系统。它旨在解决传统健身房在会员管理、课程预约、教练排班、器材维护、财务统计等方面依赖人工操作带来的效率低下、易出错、数据不透明等问题。

核心价值体现在:

  1. 提升管理效率:将会员信息、课程、教练、财务等数据电子化,实现快速查询、统计和更新。
  2. 优化用户体验:会员可通过线上平台自助预约课程、查看消费记录,提升服务满意度。
  3. 数据驱动决策:系统可生成各类报表(如会员增长、课程热度、收入分析),为管理者提供决策支持。
  4. 技术学习价值:项目涵盖了Java Web开发的主流技术栈(Spring Boot, MyBatis, Vue等),是学习企业级应用开发的优秀案例。

技术栈选型分析:

  • 后端:采用 Spring Boot + Spring MVC + MyBatis-Plus (SSM框架的现代化演进)。Spring Boot简化了配置和部署;MyBatis-Plus在MyBatis基础上提供了强大的CRUD增强功能,极大提升开发效率。
  • 前端:采用 Vue.js + Element UI。Vue.js是渐进式前端框架,易于上手且生态丰富;Element UI提供了美观且功能完备的UI组件,能快速构建管理后台界面。
  • 数据库:MySQL 5.7/8.0。关系型数据库,开源且稳定,是中小型项目的首选。
  • 项目管理:Maven。用于项目构建、依赖管理。
  • 其他:Redis(可选,用于缓存会话或热点数据)、JWT(用于API接口无状态认证)。

这个技术组合在当前的Java就业市场中非常普遍,掌握它意味着你具备了开发大多数企业级后台管理系统的基础能力。

2. 开发环境准备与项目初始化

在开始编码前,我们需要搭建统一的开发环境。以下版本为推荐版本,实际操作时请确保环境变量配置正确。

2.1 环境与工具清单

工具/环境推荐版本作用说明
JDK1.8 或 11 (LTS版本)Java开发工具包,编译和运行基础。
IDEIntelliJ IDEA (社区版或旗舰版)集成开发环境,提高编码效率。
MySQL5.7 或 8.0项目数据库。
Maven3.6+项目构建与依赖管理工具。
Node.js14.x 或 16.x运行前端Vue项目所需环境。
Git最新版代码版本管理工具(可选,但推荐)。

环境验证命令:打开终端或命令行工具,依次执行以下命令,确认安装成功。

java -version mvn -v node -v npm -v mysql --version

2.2 创建Spring Boot后端项目

我们使用Spring Initializr来快速生成项目骨架。可以通过IDEA内置的Spring Initializr或访问 start.spring.io 网站生成。

项目元数据:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.7.x (一个稳定的版本)
  • Group:com.gym(可根据自己习惯修改)
  • Artifact:gym-management-system
  • Packaging: Jar
  • Java: 8 或 11

依赖选择:在Dependencies中添加以下起步依赖:

  • Spring Web(构建Web应用,包含MVC)
  • MyBatis Framework(或直接使用MyBatis-Plus,需后续手动添加依赖)
  • MySQL Driver(数据库连接)
  • Lombok(简化Java Bean编写,推荐)

生成并下载项目压缩包,解压后用IDEA打开。

2.3 初始化数据库

使用MySQL客户端(如命令行、Navicat、MySQL Workbench)连接数据库,并执行以下SQL语句创建数据库和用户。

-- 创建数据库,字符集推荐使用utf8mb4以支持完整Unicode(如emoji) CREATE DATABASE IF NOT EXISTS `gym_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE `gym_db`; -- 创建一个专门用于此项目的数据库用户(安全最佳实践) CREATE USER 'gym_user'@'%' IDENTIFIED BY 'Gym123456!'; -- 生产环境请使用强密码 GRANT ALL PRIVILEGES ON gym_db.* TO 'gym_user'@'%'; FLUSH PRIVILEGES;

注意@‘%’允许从任何主机连接,适用于开发。生产环境应限制为特定IP。请务必修改成你自己的密码。

3. 项目架构设计与核心模块拆解

在动手编码前,清晰的架构设计能避免后期混乱。本项目采用经典的分层架构。

3.1 系统分层架构

gym-management-system (后端) ├── src/main/java/com/gym │ ├── config/ # 配置类(数据源、MyBatis-Plus、拦截器、WebMvc等) │ ├── controller/ # 控制层,接收请求,调用服务,返回结果 │ ├── entity/ # 实体层,与数据库表一一对应的Java类 │ ├── mapper/ # 数据访问层(DAO),MyBatis的Mapper接口 │ ├── service/ # 业务逻辑层接口 │ │ └── impl/ # 业务逻辑层实现类 │ ├── dto/ # 数据传输对象,用于前后端交互,常不同于Entity │ ├── vo/ # 视图对象,用于封装返回给前端的数据 │ ├── common/ # 通用类(常量、枚举、统一返回结果、异常等) │ └── GymManagementSystemApplication.java # 启动类 ├── src/main/resources │ ├── application.yml # 主配置文件 │ ├── mapper/ # MyBatis的XML映射文件(如果使用XML方式) │ └── static/ & templates/ # 静态资源(可放置前端构建产物) └── pom.xml # Maven依赖管理文件

3.2 数据库表结构设计(核心表示例)

根据健身房业务,我们设计以下几张核心表:

1. 会员表 (member)

CREATE TABLE `member` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `member_number` varchar(32) NOT NULL COMMENT '会员卡号', `name` varchar(64) NOT NULL COMMENT '姓名', `gender` tinyint(1) DEFAULT '0' COMMENT '性别:0-未知,1-男,2-女', `phone` varchar(11) NOT NULL COMMENT '手机号', `birthday` date DEFAULT NULL COMMENT '生日', `card_type` varchar(20) DEFAULT NULL COMMENT '卡类型(月卡、季卡、年卡)', `card_price` decimal(10,2) DEFAULT NULL COMMENT '办卡金额', `valid_start_date` date DEFAULT NULL COMMENT '有效期开始', `valid_end_date` date DEFAULT NULL COMMENT '有效期结束', `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-冻结,1-正常', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_member_number` (`member_number`), UNIQUE KEY `uk_phone` (`phone`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员信息表';

2. 教练表 (coach)

CREATE TABLE `coach` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `photo` varchar(255) DEFAULT NULL COMMENT '头像', `gender` tinyint(1) DEFAULT NULL, `phone` varchar(11) NOT NULL, `speciality` varchar(255) DEFAULT NULL COMMENT '专长(如:瑜伽、拳击)', `level` varchar(20) DEFAULT NULL COMMENT '教练等级', `hire_date` date DEFAULT NULL COMMENT '入职日期', `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-离职,1-在职', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教练信息表';

3. 课程表 (course)

CREATE TABLE `course` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL COMMENT '课程名称', `coach_id` bigint(20) NOT NULL COMMENT '教练ID', `course_date` date NOT NULL COMMENT '上课日期', `start_time` time NOT NULL COMMENT '开始时间', `end_time` time NOT NULL COMMENT '结束时间', `max_participants` int(11) DEFAULT NULL COMMENT '最大参与人数', `current_participants` int(11) DEFAULT '0' COMMENT '当前报名人数', `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-取消,1-正常,2-已满', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_coach_date` (`coach_id`,`course_date`), CONSTRAINT `fk_course_coach` FOREIGN KEY (`coach_id`) REFERENCES `coach` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';

4. 课程预约表 (course_booking)

CREATE TABLE `course_booking` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `course_id` bigint(20) NOT NULL, `member_id` bigint(20) NOT NULL, `booking_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '预约时间', `status` tinyint(1) DEFAULT '1' COMMENT '状态:1-已预约,2-已签到,3-已取消', PRIMARY KEY (`id`), UNIQUE KEY `uk_course_member` (`course_id`,`member_id`), -- 防止重复预约 KEY `idx_member` (`member_id`), CONSTRAINT `fk_booking_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`), CONSTRAINT `fk_booking_member` FOREIGN KEY (`member_id`) REFERENCES `member` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程预约表';

(其他表如:器材表equipment、消费记录表consumption_record、管理员表admin_user等可根据需求补充)

4. 后端核心代码实现

我们以“会员管理”模块为例,展示从Entity到Controller的完整代码流程。

4.1 引入MyBatis-Plus依赖

pom.xml中,将Spring Initializr生成的mybatis-spring-boot-starter依赖替换或添加为MyBatis-Plus的依赖。

<!-- 在dependencies部分添加 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> <!-- 请使用当时最新稳定版 --> </dependency>

4.2 配置数据源与MyBatis-Plus

src/main/resources/application.yml中配置:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/gym_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: gym_user password: Gym123456! # 请替换为你的密码 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,开发环境使用 global-config: db-config: id-type: auto # 主键策略,数据库自增 logic-delete-field: deleted # 全局逻辑删除字段名(如果表中有此字段) logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值 mapper-locations: classpath:mapper/*.xml # XML映射文件位置

4.3 创建实体类(Entity)

对应member表,在entity包下创建Member.java

package com.gym.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @Data @TableName("member") // 指定对应表名 public class Member { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String memberNumber; private String name; private Integer gender; // 使用Integer便于接收null private String phone; private LocalDate birthday; private String cardType; private BigDecimal cardPrice; private LocalDate validStartDate; private LocalDate validEndDate; private Integer status; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; // 逻辑删除字段(如果表中有) // @TableLogic // private Integer deleted; }

4.4 创建Mapper接口

mapper包下创建MemberMapper.java。继承MyBatis-Plus的BaseMapper,即可获得基础的CRUD方法。

package com.gym.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.gym.entity.Member; import org.apache.ibatis.annotations.Mapper; @Mapper // 重要:让Spring管理此接口 public interface MemberMapper extends BaseMapper<Member> { // 无需编写XML,简单的CRUD已由BaseMapper提供。 // 如需复杂查询,可在此定义方法,并在resources/mapper下编写对应的XML。 }

4.5 创建Service层

接口service/MemberService.java

package com.gym.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.gym.entity.Member; import com.baomidou.mybatisplus.extension.service.IService; public interface MemberService extends IService<Member> { // 可以在此定义业务逻辑方法,例如分页条件查询 Page<Member> getMemberPage(Page<Member> page, String keyword, Integer status); }

实现类service/impl/MemberServiceImpl.java

package com.gym.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gym.entity.Member; import com.gym.mapper.MemberMapper; import com.gym.service.MemberService; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @Service public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService { @Override public Page<Member> getMemberPage(Page<Member> page, String keyword, Integer status) { LambdaQueryWrapper<Member> wrapper = new LambdaQueryWrapper<>(); // 根据关键词模糊查询姓名或手机号 if (StringUtils.isNotBlank(keyword)) { wrapper.like(Member::getName, keyword).or().like(Member::getPhone, keyword); } // 根据状态查询 if (status != null) { wrapper.eq(Member::getStatus, status); } // 按创建时间倒序 wrapper.orderByDesc(Member::getCreateTime); return this.page(page, wrapper); } }

4.6 创建Controller层

controller包下创建MemberController.java,处理HTTP请求。

package com.gym.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.gym.common.R; import com.gym.entity.Member; import com.gym.service.MemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/member") public class MemberController { @Autowired private MemberService memberService; /** * 新增会员 */ @PostMapping public R<String> save(@RequestBody Member member) { // 简单校验 if (member == null || member.getPhone() == null) { return R.error("参数错误"); } // 业务逻辑:检查手机号是否已存在等(应在Service层做) boolean saved = memberService.save(member); return saved ? R.success("新增成功") : R.error("新增失败"); } /** * 分页查询会员列表 */ @GetMapping("/page") public R<Page<Member>> page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer status) { Page<Member> page = new Page<>(pageNum, pageSize); Page<Member> resultPage = memberService.getMemberPage(page, keyword, status); return R.success(resultPage); } /** * 根据ID获取会员详情 */ @GetMapping("/{id}") public R<Member> getById(@PathVariable Long id) { Member member = memberService.getById(id); return member != null ? R.success(member) : R.error("会员不存在"); } /** * 更新会员信息 */ @PutMapping public R<String> update(@RequestBody Member member) { if (member.getId() == null) { return R.error("ID不能为空"); } boolean updated = memberService.updateById(member); return updated ? R.success("更新成功") : R.error("更新失败"); } /** * 删除会员(逻辑删除,如果表有deleted字段) */ @DeleteMapping("/{id}") public R<String> delete(@PathVariable Long id) { // memberService.removeById(id); // 物理删除 // 逻辑删除示例(需要实体类有@TableLogic注解) boolean removed = memberService.removeById(id); return removed ? R.success("删除成功") : R.error("删除失败"); } }

4.7 统一返回结果封装

common包下创建R.java,用于统一API响应格式。

package com.gym.common; import lombok.Data; import java.io.Serializable; @Data public class R<T> implements Serializable { private Integer code; // 状态码,如 200成功,500失败 private String msg; // 提示信息 private T data; // 返回数据 public static <T> R<T> success(T data) { R<T> r = new R<>(); r.setCode(200); r.setMsg("success"); r.setData(data); return r; } public static <T> R<T> success(String msg) { R<T> r = new R<>(); r.setCode(200); r.setMsg(msg); return r; } public static <T> R<T> error(String msg) { R<T> r = new R<>(); r.setCode(500); r.setMsg(msg); return r; } // 可以定义更多状态码,如 400参数错误,401未授权等 public static <T> R<T> fail(Integer code, String msg) { R<T> r = new R<>(); r.setCode(code); r.setMsg(msg); return r; } }

至此,一个完整的会员管理后端API模块就搭建好了。启动Spring Boot应用,可以使用Postman或Swagger测试接口。

5. 前端Vue.js界面实现(核心示例)

前端项目通常独立于后端。我们使用Vue CLI创建一个新项目,并集成Element UI和Axios。

5.1 创建Vue项目并安装依赖

# 全局安装Vue CLI(如果未安装) npm install -g @vue/cli # 创建项目 vue create gym-frontend # 选择默认配置或手动选择(推荐选择Vue 2 或 Vue 3) cd gym-frontend # 安装Element UI (以Vue 2为例) vue add element # 或手动安装 # npm i element-ui -S # 安装axios用于HTTP请求 npm install axios -S # 安装vue-router和vuex(如需) npm install vue-router vuex -S

5.2 会员列表页面组件

src/views/member/MemberList.vue中:

<template> <div class="member-container"> <el-card> <!-- 搜索和操作栏 --> <div style="margin-bottom: 20px;"> <el-row :gutter="20"> <el-col :span="6"> <el-input v-model="searchForm.keyword" placeholder="请输入姓名或手机号" clearable @clear="handleSearch" @keyup.enter="handleSearch"> <el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button> </el-input> </el-col> <el-col :span="4"> <el-select v-model="searchForm.status" placeholder="状态" clearable @change="handleSearch"> <el-option label="正常" :value="1"></el-option> <el-option label="冻结" :value="0"></el-option> </el-select> </el-col> <el-col :span="4" :offset="10"> <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增会员</el-button> </el-col> </el-row> </div> <!-- 会员表格 --> <el-table :data="tableData" border stripe style="width: 100%" v-loading="loading"> <el-table-column prop="memberNumber" label="会员卡号" width="150"></el-table-column> <el-table-column prop="name" label="姓名" width="120"></el-table-column> <el-table-column prop="gender" label="性别" width="80"> <template slot-scope="scope"> {{ scope.row.gender === 1 ? '男' : (scope.row.gender === 2 ? '女' : '未知') }} </template> </el-table-column> <el-table-column prop="phone" label="手机号" width="150"></el-table-column> <el-table-column prop="cardType" label="卡类型" width="100"></el-table-column> <el-table-column prop="validEndDate" label="有效期至" width="120"></el-table-column> <el-table-column prop="status" label="状态" width="80"> <template slot-scope="scope"> <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'"> {{ scope.row.status === 1 ? '正常' : '冻结' }} </el-tag> </template> </el-table-column> <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column> <el-table-column label="操作" width="200" fixed="right"> <template slot-scope="scope"> <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button> <el-button size="mini" type="info" @click="handleViewDetail(scope.row)">详情</el-button> </template> </el-table-column> </el-table> <!-- 分页组件 --> <div style="margin-top: 20px; text-align: center;"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pagination.current" :page-sizes="[10, 20, 50, 100]" :page-size="pagination.size" layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"> </el-pagination> </div> </el-card> <!-- 新增/编辑对话框 --> <member-form-dialog :visible.sync="dialogVisible" :form-data="currentMember" :dialog-type="dialogType" @refresh="fetchMemberList"> </member-form-dialog> </div> </template> <script> import { getMemberPage, deleteMember } from '@/api/member'; import MemberFormDialog from './components/MemberFormDialog.vue'; export default { name: 'MemberList', components: { MemberFormDialog }, data() { return { tableData: [], loading: false, searchForm: { keyword: '', status: null }, pagination: { current: 1, size: 10, total: 0 }, dialogVisible: false, dialogType: 'add', // 'add' or 'edit' currentMember: {} }; }, created() { this.fetchMemberList(); }, methods: { async fetchMemberList() { this.loading = true; try { const params = { pageNum: this.pagination.current, pageSize: this.pagination.size, ...this.searchForm }; const res = await getMemberPage(params); if (res && res.code === 200) { this.tableData = res.data.records; this.pagination.total = res.data.total; } else { this.$message.error(res.msg || '获取数据失败'); } } catch (error) { console.error(error); this.$message.error('网络请求失败'); } finally { this.loading = false; } }, handleSearch() { this.pagination.current = 1; // 搜索时回到第一页 this.fetchMemberList(); }, handleSizeChange(val) { this.pagination.size = val; this.fetchMemberList(); }, handleCurrentChange(val) { this.pagination.current = val; this.fetchMemberList(); }, handleAdd() { this.dialogType = 'add'; this.currentMember = {}; this.dialogVisible = true; }, handleEdit(row) { this.dialogType = 'edit'; this.currentMember = { ...row }; // 浅拷贝,避免直接修改表格数据 this.dialogVisible = true; }, async handleDelete(row) { try { await this.$confirm(`确定要删除会员【${row.name}】吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }); const res = await deleteMember(row.id); if (res && res.code === 200) { this.$message.success('删除成功'); this.fetchMemberList(); // 刷新列表 } else { this.$message.error(res.msg || '删除失败'); } } catch (error) { if (error !== 'cancel') { this.$message.error('删除操作失败'); } } }, handleViewDetail(row) { // 跳转到详情页或打开详情弹窗 this.$router.push(`/member/detail/${row.id}`); } } }; </script> <style scoped> .member-container { padding: 20px; } </style>

5.3 API请求封装

src/api/member.js中:

import request from '@/utils/request'; // 假设已封装了基于axios的request工具 export function getMemberPage(params) { return request({ url: '/member/page', method: 'get', params }); } export function getMemberById(id) { return request({ url: `/member/${id}`, method: 'get' }); } export function addMember(data) { return request({ url: '/member', method: 'post', data }); } export function updateMember(data) { return request({ url: '/member', method: 'put', data }); } export function deleteMember(id) { return request({ url: `/member/${id}`, method: 'delete' }); }

5.4 配置跨域与代理

在Vue项目的vue.config.js中配置开发环境代理,解决前端开发时的跨域问题。

module.exports = { devServer: { port: 8081, // 前端开发服务器端口 proxy: { '/api': { // 代理前缀,可根据后端实际情况调整 target: 'http://localhost:8080', // 后端API地址 changeOrigin: true, pathRewrite: { '^/api': '' // 如果后端接口没有前缀,可以重写掉/api } } } } };

这样,前端访问/api/member/page就会被代理到http://localhost:8080/member/page

6. 系统集成与功能扩展

完成核心的CRUD后,一个完整的系统还需要以下模块和功能:

  1. 用户认证与授权:实现管理员登录(可使用Spring Security + JWT),不同角色(如超级管理员、普通员工)拥有不同权限。
  2. 课程管理模块:教练排课、课程发布、课程状态管理。
  3. 预约模块:会员查看课程、预约课程、取消预约、签到功能。
  4. 器材管理模块:器材信息录入、借用归还记录、维修状态管理。
  5. 财务统计模块:会员办卡收入、私教课收入等统计报表,可使用ECharts进行可视化展示。
  6. 仪表盘:首页展示关键数据概览,如会员总数、今日预约数、即将到期会员等。
  7. 数据导出:将会员列表、消费记录等导出为Excel文件。
  8. 日志记录:记录关键操作日志,便于审计。

7. 项目部署与运行

7.1 后端打包与运行

# 在后端项目根目录下 mvn clean package -DskipTests # 生成的jar包在target目录下 java -jar target/gym-management-system-0.0.1-SNAPSHOT.jar

7.2 前端构建与部署

# 在前端项目根目录下 npm run build # 构建产物在dist目录下,可以将此目录下的文件复制到后端项目的src/main/resources/static目录下, # 然后重启后端服务,即可通过 http://localhost:8080 访问完整应用。 # 或者,将dist目录部署到独立的Nginx服务器。

8. 常见问题与解决方案

问题现象可能原因解决方案
后端启动失败,报DataSource相关错误1. 数据库连接URL、用户名、密码错误。
2. MySQL服务未启动。
3. 数据库驱动版本不匹配。
1. 检查application.yml配置。
2. 启动MySQL服务。
3. 确认pom.xml中MySQL驱动版本与数据库版本兼容。
前端访问后端API出现4041. 后端服务未启动。
2. 请求URL路径错误。
3. 控制器@RequestMapping注解路径不匹配。
1. 确认后端服务在指定端口运行。
2. 使用浏览器或Postman直接测试后端接口。
3. 检查前端axios请求的baseURL和具体路径。
前端页面样式错乱或Element UI组件不显示1. Element UI未正确引入或注册。
2. 项目CSS样式冲突。
1. 检查main.js中是否正确引入和Vue.use(ElementUI)
2. 检查组件标签名和属性是否正确。
插入数据时,create_time等字段为nullMyBatis-Plus的自动填充功能未生效。1. 确保实体类字段有@TableField(fill = FieldFill.INSERT)注解。
2. 创建一个元数据处理器(MetaObjectHandler)并注册到Spring。
分页查询失效,返回所有数据MyBatis-Plus分页插件未配置。在配置类(如MybatisPlusConfig)中添加分页插件PaginationInterceptor(MP 3.x)或MybatisPlusInterceptor(MP 3.4+)。
Vue项目npm install报错1. Node.js版本不兼容。
2. 网络问题。
3.package-lock.json冲突。
1. 使用nvm切换Node.js版本(如14.x或16.x)。
2. 配置npm镜像源(如淘宝镜像)。
3. 删除node_modulespackage-lock.json,重新npm install

9. 项目优化与最佳实践

  1. 接口安全
    • 所有敏感接口(增删改)必须进行权限校验。
    • 使用HTTPS协议传输数据。
    • 对用户输入进行严格的校验和过滤,防止SQL注入和XSS攻击。
  2. 代码规范
    • 遵循阿里巴巴Java开发手册。
    • 使用Swagger或Knife4j自动生成API文档。
    • 前后端定义统一的接口响应格式(如本文的R<T>类)。
  3. 性能优化
    • 对于频繁查询且变化不大的数据(如健身房基本信息、教练列表),使用Redis进行缓存。
    • 数据库表建立合适的索引(如会员表的phonestatus字段)。
    • 对大列表查询做好分页,避免一次性加载过多数据。
  4. 可维护性
    • 业务逻辑尽量放在Service层,Controller保持轻薄。
    • 使用全局异常处理(@ControllerAdvice)统一处理异常,返回友好的错误信息。
    • 使用日志框架(如SLF4J + Logback)记录关键操作和错误信息。
  5. 部署与监控
    • 使用Docker容器化部署,保证环境一致性。
    • 配置Spring Boot Actuator进行应用健康监控。
    • 生产环境务必关闭Swagger、druid监控页面的外网访问,或设置强密码。

通过以上步骤,你不仅能够完成一个功能完善的健身房管理系统,更能深入理解一个典型Java Web应用从设计到部署的全过程。这个项目涵盖了当前企业开发中的主流技术,将其作为你的课程设计或毕业设计,无疑会是一个高质量的作品。

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度