基于Spring Boot和Vue3的体育馆管理系统【代码讲解文档】
基于Spring Boot和Vue3的体育馆管理系统 - 代码讲解文档
一、项目概述

1.1 项目简介
本项目是一个基于Spring Boot和Vue3技术栈开发的体育馆管理系统,采用前后端分离架构,实现了体育馆日常运营管理的数字化和信息化。系统支持多角色用户(管理员、会员、教练、前台员工)协同工作,涵盖课程管理、场地预约、收银结算、健康记录等核心业务功能。
1.2 技术架构
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 后端框架 | Spring Boot 2.2.2 | 快速开发的Java框架 |
| ORM框架 | MyBatis Plus 2.3 | 数据持久层框架 |
| 数据库 | MySQL 5.7 | 关系型数据库 |
| 安全框架 | Shiro 1.3.2 | 用户认证与授权 |
| 前端框架 | Vue 3 + Vue Router | 渐进式JavaScript框架 |
| 前端UI | Element Plus | Vue3组件库 |
| 工具库 | Hutool 4.0.12 | Java工具类库 |
| JSON处理 | FastJSON 1.2.8 | JSON序列化工具 |
| 图表库 | ECharts 5.4.1 | 数据可视化 |
1.3 项目目录结构
项目根目录/
├── server_code/ # 后端代码
│ ├── src/main/java/com/cl/ # Java源码
│ │ ├── annotation/ # 自定义注解
│ │ ├── aspect/ # AOP切面
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器层(24个)
│ │ ├── dao/ # 数据访问层
│ │ ├── entity/ # 实体类
│ │ ├── interceptor/ # 拦截器
│ │ ├── service/ # 业务服务层
│ │ └── utils/ # 工具类
│ ├── src/main/resources/ # 配置文件
│ │ ├── mapper/ # MyBatis映射文件
│ │ └── application.yml # 应用配置
│ └── sql/ # 数据库脚本
│ └── cl05925562.sql # 数据库初始化脚本
├── manage_code/ # 管理端前端代码
│ ├── src/views/ # 页面组件(20+模块)
│ ├── src/router/ # 路由配置
│ ├── src/store/ # 状态管理
│ └── src/utils/ # 工具函数
└── client_code/ # 客户端前端代码
├── src/views/pages/ # 页面组件
├── src/router/ # 路由配置
└── src/utils/ # 工具函数
二、数据库设计
2.1 数据库概览
数据库名称:cl05925562,共包含22张数据表,覆盖用户管理、业务数据、系统配置三大类别。
2.2 核心数据表结构
2.2.1 用户相关表(5张)
(1)会员表(huiyuan)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | bigint(20) | PK, AUTO_INCREMENT | 主键ID |
| addtime | timestamp | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| huiyuanzhanghao | varchar(200) | NOT NULL, UNIQUE | 会员账号 |
| mima | varchar(200) | NOT NULL | 密码 |
| huiyuanxingming | varchar(200) | NOT NULL | 会员姓名 |
| touxiang | longtext | - | 头像图片路径 |
| xingbie | varchar(200) | - | 性别 |
| lianxidianhua | varchar(200) | - | 联系电话 |
| jinjilianxiren | varchar(200) | - | 紧急联系人 |
| max_password_wrong | int(11) | DEFAULT 5 | 最大密码输错次数 |
| is_locked | int(11) | DEFAULT 0 | 用户锁定状态(0:正常,1:锁定) |
设计亮点:
- 账号唯一索引保证数据唯一性
- max_password_wrong字段支持密码错误计数
- is_locked字段实现账号自动锁定功能
- 紧急联系人字段提升安全保障
(2)教练表(jiaolian)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| jiaolianzhanghao | varchar(200) | 教练账号(唯一) |
| mima | varchar(200) | 密码 |
| jiaolianxingming | varchar(200) | 教练姓名 |
| touxiang | longtext | 头像图片 |
| xingbie | varchar(200) | 性别 |
| lianxidianhua | varchar(200) | 联系电话 |
(3)前台员工表(qiantaiyuangong)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| yuangongzhanghao | varchar(200) | 员工账号(唯一) |
| mima | varchar(200) | 密码 |
| yuangongxingming | varchar(200) | 员工姓名 |
| touxiang | longtext | 头像 |
| xingbie | varchar(200) | 性别 |
| lianxidianhua | varchar(200) | 联系电话 |
(4)管理员表(users)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| username | varchar(200) | 用户名 |
| password | varchar(200) | 密码 |
| role | varchar(200) | 角色(默认"管理员") |
(5)Token表(token)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| userid | bigint | 用户ID |
| username | varchar(100) | 用户名 |
| tablename | varchar(100) | 表名(huiyuan/jiaolian等) |
| role | varchar(100) | 角色 |
| token | varchar(200) | Token值 |
| addtime | timestamp | 新增时间 |
| expiratedtime | timestamp | 过期时间 |
2.2.2 课程管理表(4张)
(1)课程信息表(kechengxinxi)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| kechengmingcheng | varchar(200) | 课程名称 |
| kechengfengmian | longtext | 课程封面图片(多张) |
| keshi | varchar(200) | 课时 |
| shangkeshijian | datetime | 上课时间 |
| shangkedidian | varchar(200) | 上课地点 |
| jiaolianzhanghao | varchar(200) | 教练账号(外键关联) |
| jiaolianxingming | varchar(200) | 教练姓名 |
| discuss_number | int | 评论数 |
(2)课程预约表(kechengyuyue)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| kechengmingcheng | varchar(200) | 课程名称 |
| kechengfengmian | longtext | 课程封面 |
| keshi | varchar(200) | 课时 |
| shangkeshijian | datetime | 上课时间 |
| shangkedidian | varchar(200) | 上课地点 |
| jiaolianzhanghao | varchar(200) | 教练账号 |
| jiaolianxingming | varchar(200) | 教练姓名 |
| huiyuanzhanghao | varchar(200) | 会员账号(预约人) |
| huiyuanxingming | varchar(200) | 会员姓名 |
| crossuserid | bigint | 跨表用户ID |
| crossrefid | bigint | 跨表主键ID |
设计说明:课程预约表冗余存储了课程和会员的核心信息,便于快速查询和展示,无需频繁关联查询。
(3)取消预约表(quxiaoyuyue)
结构同课程预约表,用于记录会员取消的预约信息,便于统计分析。
(4)课程表(kechengbiao)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| jiaolianzhanghao | varchar(200) | 教练账号 |
| jiaolianxingming | varchar(200) | 教练姓名 |
| riqi | varchar(200) | 日期(星期几) |
| kechengyi | varchar(200) | 课程一 |
| kechenger | varchar(200) | 课程二 |
| kechengsan | varchar(200) | 课程三 |
| kechengsi | varchar(200) | 课程四 |
| kechengwu | varchar(200) | 课程五 |
2.2.3 场地管理表(2张)
(1)场地信息表(changdixinxi)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| changdimingcheng | varchar(200) | 场地名称 |
| changditupian | longtext | 场地图片(多张) |
| changdimianji | varchar(200) | 场地面积 |
| rongnarenshu | varchar(200) | 容纳人数 |
| kaifangshijian | varchar(200) | 开放时间 |
| changdisheshi | longtext | 场地设施描述 |
| changdixiangqing | longtext | 场地详情介绍 |
(2)场地预约表(changdiyuyue)
结构类似课程预约表,记录场地名称、会员信息、跨表关联ID等。
2.2.4 财务管理表(1张)
收银结算表(shouyinjiesuan)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| huiyuanfei | double | 会员费 |
| kechengfei | double | 课程费 |
| changdifei | double | 场地费 |
| zongfeiyong | double | 总费用 |
| huiyuanzhanghao | varchar(200) | 会员账号 |
| huiyuanxingming | varchar(200) | 会员姓名 |
| ispay | varchar(200) | 是否支付(默认"未支付") |
2.2.5 其他功能表(10张)
(1)健康记录表(jiankangjilu)
| 字段名 | 类型 | 说明 |
|---|---|---|
| biaoti | varchar(200) | 标题 |
| jianshenjindu | varchar(200) | 健身进度 |
| tizhongbianhua | varchar(200) | 体重变化 |
| zongjie | longtext | 总结 |
| jilushijian | datetime | 记录时间 |
| huiyuanzhanghao | varchar(200) | 会员账号 |
(2)教学资源表(jiaoxueziyuan)
| 字段名 | 类型 | 说明 |
|---|---|---|
| ziyuanmingcheng | varchar(200) | 资源名称 |
| fengmian | longtext | 封面图片 |
| ziyuanjianjie | longtext | 资源简介 |
| ziyuanxiazai | longtext | 资源下载链接 |
| shipin | longtext | 视频链接 |
| fabushijian | datetime | 发布时间 |
| jiaolianzhanghao | varchar(200) | 发布教练账号 |
| storeup_number | int | 收藏数 |
(3)聊天消息表(chat_message)
| 字段名 | 类型 | 说明 |
|---|---|---|
| uid | bigint | 发送用户ID |
| fid | bigint | 接收好友ID |
| content | varchar(200) | 消息内容 |
| format | int | 格式(1:文字,2:图片) |
| is_read | int | 已读状态(0:未读,1:已读) |
(4)好友表(chat_friend)
| 字段名 | 类型 | 说明 |
|---|---|---|
| uid | bigint | 用户ID |
| fid | bigint | 好友ID |
| name | varchar(200) | 名称 |
| picture | longtext | 图片 |
| type | int | 类型(0:好友申请,1:好友,2:消息) |
(5)课程评论表(discusskechengxinxi)
| 字段名 | 类型 | 说明 |
|---|---|---|
| refid | bigint | 关联课程ID |
| userid | bigint | 评论用户ID |
| avatarurl | longtext | 头像 |
| nickname | varchar(200) | 用户名 |
| content | longtext | 评论内容 |
| reply | longtext | 回复内容 |
(6)收藏表(storeup)
| 字段名 | 类型 | 说明 |
|---|---|---|
| refid | bigint | 关联资源ID |
| tablename | varchar(200) | 表名 |
| name | varchar(200) | 名称 |
| picture | longtext | 图片 |
| type | varchar(200) | 类型(1:收藏) |
| userid | bigint | 用户ID |
(7)操作日志表(syslog)
| 字段名 | 类型 | 说明 |
|---|---|---|
| username | varchar(200) | 用户名 |
| operation | varchar(200) | 操作类型 |
| method | varchar(200) | 请求方法 |
| params | longtext | 请求参数 |
| time | bigint | 执行时长(毫秒) |
| ip | varchar(200) | IP地址 |
(8)问题解答表(chat)
| 字段名 | 类型 | 说明 |
|---|---|---|
| adminid | bigint | 管理员ID |
| ask | longtext | 提问内容 |
| reply | longtext | 回复内容 |
| isreply | int | 是否回复 |
| userid | bigint | 用户ID |
三、后端代码详解
3.1 项目配置
3.1.1 Maven依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<groupId>com.cl</groupId>
<artifactId>cl05925562</artifactId>
<name>springboot-schema</name>
<properties>
<java.version>1.8</java.version>
<fastjson.version>1.2.8</fastjson.version>
</properties>
<dependencies>
<!-- Spring Boot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus ORM框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<!-- MySQL数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Shiro安全框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 百度AI SDK(人脸识别等) -->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.4.1</version>
</dependency>
<!-- Hutool工具类库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.0.12</version>
</dependency>
<!-- FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- Apache Commons工具 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- POI(Excel处理) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!-- AOP支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>
3.2 控制器层设计
系统共设计24个Controller控制器,每个业务模块对应一个控制器。
3.2.1 会员控制器(HuiyuanController.java)
核心功能:会员注册、登录、信息管理、统计分析
@RestController
@RequestMapping("/huiyuan")
public class HuiyuanController {
@Autowired
private HuiyuanService huiyuanService;
@Autowired
private TokenService tokenService;
/**
* 会员登录接口
* 实现密码验证和账号锁定机制
* @IgnoreAuth注解表示无需Token验证
*/
@IgnoreAuth
@RequestMapping(value = "/login")
public R login(String username, String password, String captcha,
HttpServletRequest request) {
// 根据账号查询会员
HuiyuanEntity u = huiyuanService.selectOne(
new EntityWrapper<HuiyuanEntity>().eq("huiyuanzhanghao", username));
// 检查账号是否被锁定
if(u != null && u.getIsLocked().intValue() == 1) {
return R.error("账号已锁定,请联系管理员。");
}
// 验证账号密码
if(u == null || !u.getMima().equals(password)) {
if(u != null) {
// 密码错误,累加错误次数
u.setMaxPasswordWrong(u.getMaxPasswordWrong() + 1);
// 连续错误5次,锁定账号
if(u.getMaxPasswordWrong() >= 5) {
u.setIsLocked(1);
}
huiyuanService.updateById(u);
}
return R.error("账号或密码不正确");
}
// 登录成功,生成Token
String token = tokenService.generateToken(
u.getId(), username, "huiyuan", "会员");
return R.ok().put("token", token);
}
/**
* 会员注册接口
*/
@IgnoreAuth
@RequestMapping("/register")
public R register(@RequestBody HuiyuanEntity huiyuan) {
// 检查账号是否已存在
HuiyuanEntity u = huiyuanService.selectOne(
new EntityWrapper<HuiyuanEntity>()
.eq("huiyuanzhanghao", huiyuan.getHuiyuanzhanghao()));
if(u != null) {
return R.error("注册用户已存在");
}
// 使用时间戳生成唯一ID
Long uId = new Date().getTime();
huiyuan.setId(uId);
huiyuanService.insert(huiyuan);
return R.ok();
}
/**
* 分页查询会员列表
* 支持条件筛选和排序
*/
@RequestMapping("/page")
public R page(@RequestParam Map<String, Object> params,
HuiyuanEntity huiyuan, HttpServletRequest request) {
EntityWrapper<HuiyuanEntity> ew = new EntityWrapper<HuiyuanEntity>();
// MPUtil工具类处理查询条件
PageUtils page = huiyuanService.queryPage(params,
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, huiyuan), params), params));
return R.ok().put("data", page);
}
/**
* 获取当前登录会员信息
*/
@RequestMapping("/session")
public R getCurrUser(HttpServletRequest request) {
Long id = (Long)request.getSession().getAttribute("userId");
return R.ok().put("data",
huiyuanService.selectView(new EntityWrapper<HuiyuanEntity>().eq("id", id)));
}
/**
* 密码重置功能
*/
@IgnoreAuth
@RequestMapping(value = "/resetPass")
public R resetPass(String username, HttpServletRequest request) {
HuiyuanEntity u = huiyuanService.selectOne(
new EntityWrapper<HuiyuanEntity>().eq("huiyuanzhanghao", username));
if(u == null) {
return R.error("账号不存在");
}
// 重置密码为默认值
u.setMima("123456");
// 解锁账号
u.setIsLocked(0);
u.setMaxPasswordWrong(0);
huiyuanService.updateById(u);
return R.ok("密码已重置为:123456");
}
/**
* 新增会员(后台)
* @SysLog注解记录操作日志
*/
@RequestMapping("/save")
@SysLog("新增会员")
public R save(@RequestBody HuiyuanEntity huiyuan, HttpServletRequest request) {
// 检查账号唯一性
if(huiyuanService.selectCount(
new EntityWrapper<HuiyuanEntity>()
.eq("huiyuanzhanghao", huiyuan.getHuiyuanzhanghao())) > 0) {
return R.error("会员账号已存在");
}
huiyuan.setId(new Date().getTime());
huiyuanService.insert(huiyuan);
return R.ok();
}
/**
* 修改会员信息
* @Transactional注解支持事务
*/
@RequestMapping("/update")
@Transactional
@SysLog("修改会员")
public R update(@RequestBody HuiyuanEntity huiyuan, HttpServletRequest request) {
huiyuanService.updateById(huiyuan);
return R.ok();
}
/**
* 批量删除会员
*/
@RequestMapping("/delete")
@SysLog("删除会员")
public R delete(@RequestBody Long[] ids) {
huiyuanService.deleteBatchIds(Arrays.asList(ids));
return R.ok();
}
/**
* 按字段统计(如按姓名统计会员数量)
*/
@RequestMapping("/value/{xColumnName}/{yColumnName}")
public R value(@PathVariable("yColumnName") String yColumnName,
@PathVariable("xColumnName") String xColumnName,
HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
params.put("xColumn", xColumnName);
params.put("yColumn", yColumnName);
EntityWrapper<HuiyuanEntity> ew = new EntityWrapper<>();
List<Map<String, Object>> result = huiyuanService.selectValue(params, ew);
// 格式化日期字段
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for(Map<String, Object> m : result) {
for(String k : m.keySet()) {
if(m.get(k) instanceof Date) {
m.put(k, sdf.format((Date)m.get(k)));
}
}
}
return R.ok().put("data", result);
}
/**
* 分组统计(如按性别分组)
*/
@RequestMapping("/group/{columnName}")
public R group(@PathVariable("columnName") String columnName,
HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
params.put("column", columnName);
EntityWrapper<HuiyuanEntity> ew = new EntityWrapper<>();
List<Map<String, Object>> result = huiyuanService.selectGroup(params, ew);
return R.ok().put("data", result);
}
/**
* 统计会员总数
*/
@RequestMapping("/count")
public R count(@RequestParam Map<String, Object> params,
HuiyuanEntity huiyuan, HttpServletRequest request) {
EntityWrapper<HuiyuanEntity> ew = new EntityWrapper<>();
int count = huiyuanService.selectCount(
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, huiyuan), params), params));
return R.ok().put("data", count);
}
}
代码解析:
- @RestController:标识为RESTful API控制器,返回JSON数据
- @IgnoreAuth:自定义注解,标记无需Token验证的公开接口
- 登录安全机制:密码错误计数 + 自动锁定功能
- EntityWrapper:MyBatis Plus条件构造器,灵活构建查询条件
- MPUtil工具类:封装查询条件处理逻辑
- @SysLog:自定义注解,配合AOP切面记录操作日志
- 统计分析:提供按值统计、分组统计、总数统计多种接口
3.2.2 课程预约控制器(KechengyuyueController.java)
@RestController
@RequestMapping("/kechengyuyue")
public class KechengyuyueController {
@Autowired
private KechengyuyueService kechengyuyueService;
/**
* 分页列表查询(后台)
* 根据用户角色自动过滤数据
*/
@RequestMapping("/page")
public R page(@RequestParam Map<String, Object> params,
KechengyuyueEntity kechengyuyue, HttpServletRequest request) {
// 获取当前用户的角色表名
String tableName = request.getSession().getAttribute("tableName").toString();
// 教练只能查看自己课程的预约记录
if(tableName.equals("jiaolian")) {
kechengyuyue.setJiaolianzhanghao(
(String)request.getSession().getAttribute("username"));
}
// 会员只能查看自己的预约记录
if(tableName.equals("huiyuan")) {
kechengyuyue.setHuiyuanzhanghao(
(String)request.getSession().getAttribute("username"));
}
EntityWrapper<KechengyuyueEntity> ew = new EntityWrapper<>();
PageUtils page = kechengyuyueService.queryPage(params,
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, kechengyuyue), params), params));
return R.ok().put("data", page);
}
/**
* 前端列表(无需登录)
*/
@IgnoreAuth
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params,
KechengyuyueEntity kechengyuyue, HttpServletRequest request) {
EntityWrapper<KechengyuyueEntity> ew = new EntityWrapper<>();
PageUtils page = kechengyuyueService.queryPage(params,
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, kechengyuyue), params), params));
return R.ok().put("data", page);
}
/**
* 新增课程预约(后台)
*/
@RequestMapping("/save")
@SysLog("新增课程预约")
public R save(@RequestBody KechengyuyueEntity kechengyuyue,
HttpServletRequest request) {
kechengyuyueService.insert(kechengyuyue);
return R.ok();
}
/**
* 新增课程预约(前端)
*/
@SysLog("新增课程预约")
@RequestMapping("/add")
public R add(@RequestBody KechengyuyueEntity kechengyuyue,
HttpServletRequest request) {
kechengyuyueService.insert(kechengyuyue);
return R.ok();
}
/**
* 课程预约统计(按时间维度)
* 支持日统计、周统计、月统计、年统计
*/
@RequestMapping("/value/{xColumnName}/{yColumnName}/{timeStatType}")
public R valueDay(@PathVariable("yColumnName") String yColumnName,
@PathVariable("xColumnName") String xColumnName,
@PathVariable("timeStatType") String timeStatType,
HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
params.put("xColumn", xColumnName);
params.put("yColumn", yColumnName);
params.put("timeStatType", timeStatType); // 日/周/月/年
EntityWrapper<KechengyuyueEntity> ew = new EntityWrapper<>();
// 根据角色过滤数据
String tableName = request.getSession().getAttribute("tableName").toString();
if(tableName.equals("jiaolian")) {
ew.eq("jiaolianzhanghao",
(String)request.getSession().getAttribute("username"));
}
if(tableName.equals("huiyuan")) {
ew.eq("huiyuanzhanghao",
(String)request.getSession().getAttribute("username"));
}
List<Map<String, Object>> result =
kechengyuyueService.selectTimeStatValue(params, ew);
return R.ok().put("data", result);
}
/**
* 课程预约总数统计
*/
@RequestMapping("/count")
public R count(@RequestParam Map<String, Object> params,
KechengyuyueEntity kechengyuyue, HttpServletRequest request) {
String tableName = request.getSession().getAttribute("tableName").toString();
if(tableName.equals("jiaolian")) {
kechengyuyue.setJiaolianzhanghao(
(String)request.getSession().getAttribute("username"));
}
if(tableName.equals("huiyuan")) {
kechengyuyue.setHuiyuanzhanghao(
(String)request.getSession().getAttribute("username"));
}
EntityWrapper<KechengyuyueEntity> ew = new EntityWrapper<>();
int count = kechengyuyueService.selectCount(
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, kechengyuyue), params), params));
return R.ok().put("data", count);
}
}
代码亮点:
- 基于角色的数据隔离:教练查看自己的课程预约,会员查看自己的预约
- 时间维度统计:支持按日/周/月/年统计预约数据
- Session获取用户信息:通过Session存储用户角色和用户名
3.2.3 收银结算控制器(ShouyinjiesuanController.java)
@RestController
@RequestMapping("/shouyinjiesuan")
public class ShouyinjiesuanController {
@Autowired
private ShouyinjiesuanService shouyinjiesuanService;
/**
* 分页查询收银记录
*/
@RequestMapping("/page")
public R page(@RequestParam Map<String, Object> params,
ShouyinjiesuanEntity shouyinjiesuan, HttpServletRequest request) {
String tableName = request.getSession().getAttribute("tableName").toString();
// 会员只能查看自己的消费记录
if(tableName.equals("huiyuan")) {
shouyinjiesuan.setHuiyuanzhanghao(
(String)request.getSession().getAttribute("username"));
}
EntityWrapper<ShouyinjiesuanEntity> ew = new EntityWrapper<>();
PageUtils page = shouyinjiesuanService.queryPage(params,
MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, shouyinjiesuan), params), params));
return R.ok().put("data", page);
}
/**
* 费用统计(多维度)
* 可统计会员费、课程费、场地费、总费用
*/
@RequestMapping("/value/{xColumnName}/{yColumnName}")
public R value(@PathVariable("yColumnName") String yColumnName,
@PathVariable("xColumnName") String xColumnName,
HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
params.put("xColumn", xColumnName);
params.put("yColumn", yColumnName);
EntityWrapper<ShouyinjiesuanEntity> ew = new EntityWrapper<>();
String tableName = request.getSession().getAttribute("tableName").toString();
if(tableName.equals("huiyuan")) {
ew.eq("huiyuanzhanghao",
(String)request.getSession().getAttribute("username"));
}
List<Map<String, Object>> result = shouyinjiesuanService.selectValue(params, ew);
return R.ok().put("data", result);
}
/**
* 多字段统计
* 同时统计多个费用字段(如会员费+课程费+场地费)
*/
@RequestMapping("/valueMul/{xColumnName}")
public R valueMul(@PathVariable("xColumnName") String xColumnName,
@RequestParam String yColumnNameMul,
HttpServletRequest request) {
String[] yColumnNames = yColumnNameMul.split(",");
Map<String, Object> params = new HashMap<>();
params.put("xColumn", xColumnName);
List<List<Map<String, Object>>> result2 = new ArrayList<>();
EntityWrapper<ShouyinjiesuanEntity> ew = new EntityWrapper<>();
// 根据角色过滤
String tableName = request.getSession().getAttribute("tableName").toString();
if(tableName.equals("huiyuan")) {
ew.eq("huiyuanzhanghao",
(String)request.getSession().getAttribute("username"));
}
// 循环统计每个字段
for(int i = 0; i < yColumnNames.length; i++) {
params.put("yColumn", yColumnNames[i]);
List<Map<String, Object>> result =
shouyinjiesuanService.selectValue(params, ew);
result2.add(result);
}
return R.ok().put("data", result2);
}
}
3.3 实体类设计
3.3.1 课程信息实体类(KechengxinxiEntity.java)
@TableName("kechengxinxi")
public class KechengxinxiEntity<T> implements Serializable {
private static final long serialVersionUID = 1L;
public KechengxinxiEntity() {}
// 支持从其他对象复制属性
public KechengxinxiEntity(T t) {
try {
BeanUtils.copyProperties(this, t);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 主键ID(自增)
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 课程名称
*/
private String kechengmingcheng;
/**
* 课程封面图片(可存储多张图片路径)
*/
private String kechengfengmian;
/**
* 课时
*/
private String keshi;
/**
* 上课时间(日期格式化)
*/
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
@DateTimeFormat
private Date shangkeshijian;
/**
* 上课地点
*/
private String shangkedidian;
/**
* 教练账号(关联jiaolian表)
*/
private String jiaolianzhanghao;
/**
* 教练姓名
*/
private String jiaolianxingming;
/**
* 评论数(统计字段)
*/
private Integer discussNumber;
/**
* 创建时间
*/
@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
@DateTimeFormat
private Date addtime;
// getter和setter方法...
}
设计要点:
@TableName:MyBatis Plus注解,映射数据库表名@TableId(type = IdType.AUTO):指定主键自增策略@JsonFormat:Jackson注解,序列化时格式化日期@DateTimeFormat:Spring注解,接收参数时解析日期Serializable:支持序列化传输- 泛型设计:支持从其他对象复制属性
3.4 自定义注解
3.4.1 忽略认证注解(IgnoreAuth.java)
package com.cl.annotation;
/**
* 忽略验证注解
* 用于标记不需要登录验证的接口
* 如:注册、登录、公开信息浏览等
*/
public @interface IgnoreAuth {
}
3.4.2 系统日志注解(SysLog.java)
package com.cl.annotation;
/**
* 系统日志注解
* 用于标记需要记录操作日志的方法
* 配合SysLogAspect切面使用
*/
public @interface SysLog {
String value() default "";
}
3.5 AOP切面
3.5.1 操作日志切面(SysLogAspect.java)
package com.cl.aspect;
@Aspect
@Component
public class SysLogAspect {
@Autowired
private SyslogService syslogService;
/**
* 定义切点:所有标注了@SysLog的方法
*/
@Pointcut("@annotation(com.cl.annotation.SysLog)")
public void logPointCut() {}
/**
* 环绕通知:记录操作日志
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行目标方法
Object result = point.proceed();
long time = System.currentTimeMillis() - beginTime;
// 保存操作日志
saveSysLog(point, time);
return result;
}
/**
* 保存日志到数据库
*/
private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SyslogEntity syslog = new SyslogEntity();
// 获取@SysLog注解的值
SysLog sysLog = method.getAnnotation(SysLog.class);
if(sysLog != null) {
syslog.setOperation(sysLog.value()); // 操作描述
}
// 获取请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
syslog.setMethod(className + "." + methodName + "()");
// 获取请求参数
Object[] args = joinPoint.getArgs();
if(args != null && args.length > 0) {
String params = JSON.toJSONString(args[0]);
syslog.setParams(params);
}
// 获取用户名(从Session)
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder
.getRequestAttributes()).getRequest();
syslog.setUsername((String)request.getSession().getAttribute("username"));
// 设置IP地址
syslog.setIp(request.getRemoteAddr());
// 设置执行时长
syslog.setTime(time);
syslogService.insert(syslog);
}
}
四、前端代码详解
4.1 管理端前端(manage_code)
4.1.1 路由配置(src/router/index.js)
import { createRouter, createWebHashHistory } from 'vue-router'
// 导入各模块的列表组件
import jiankangjilu from '@/views/jiankangjilu/list'
import jiaolian from '@/views/jiaolian/list'
import shouyinjiesuan from '@/views/shouyinjiesuan/list'
import huiyuan from '@/views/huiyuan/list'
import syslog from '@/views/syslog/list'
import jiaoxueziyuan from '@/views/jiaoxueziyuan/list'
import changdixinxi from '@/views/changdixinxi/list'
import storeup from '@/views/storeup/list'
import discusskechengxinxi from '@/views/discusskechengxinxi/list'
import kechengxinxi from '@/views/kechengxinxi/list'
import users from '@/views/users/list'
import kechengbiao from '@/views/kechengbiao/list'
import quxiaoyuyue from '@/views/quxiaoyuyue/list'
import chat from '@/views/chat/list'
import kechengyuyue from '@/views/kechengyuyue/list'
import config from '@/views/config/list'
import qiantaiyuangong from '@/views/qiantaiyuangong/list'
import changdiyuyue from '@/views/changdiyuyue/list'
// 导入个人中心组件
import usersCenter from '@/views/users/center'
import qiantaiyuangongCenter from '@/views/qiantaiyuangong/center'
import jiaolianCenter from '@/views/jiaolian/center'
export const routes = [
{
path: '/login',
name: 'login',
component: () => import('../views/login.vue') // 懒加载
},
{
path: '/',
name: '首页',
component: () => import('../views/index'),
children: [
{
path: '/',
name: '首页',
component: () => import('../views/HomeView.vue'),
meta: { affix: true } // 固定在标签栏
},
{
path: '/updatepassword',
name: '修改密码',
component: () => import('../views/updatepassword.vue')
},
// 个人中心路由
{ path: '/usersCenter', name: '管理员个人中心', component: usersCenter },
{ path: '/qiantaiyuangongCenter', name: '前台员工个人中心', component: qiantaiyuangongCenter },
{ path: '/jiaolianCenter', name: '教练个人中心', component: jiaolianCenter },
// 业务模块路由
{ path: '/jiankangjilu', name: '健康记录', component: jiankangjilu },
{ path: '/jiaolian', name: '教练', component: jiaolian },
{ path: '/shouyinjiesuan', name: '收银结算', component: shouyinjiesuan },
{ path: '/huiyuan', name: '会员', component: huiyuan },
{ path: '/syslog', name: '操作日志', component: syslog },
{ path: '/jiaoxueziyuan', name: '教学资源', component: jiaoxueziyuan },
{ path: '/changdixinxi', name: '场地信息', component: changdixinxi },
{ path: '/storeup', name: '我的收藏', component: storeup },
{ path: '/discusskechengxinxi', name: '课程信息评论', component: discusskechengxinxi },
{ path: '/kechengxinxi', name: '课程信息', component: kechengxinxi },
{ path: '/users', name: '管理员', component: users },
{ path: '/kechengbiao', name: '课程表', component: kechengbiao },
{ path: '/quxiaoyuyue', name: '取消预约', component: quxiaoyuyue },
{ path: '/chat', name: '问题解答', component: chat },
{ path: '/kechengyuyue', name: '课程预约', component: kechengyuyue },
{ path: '/config', name: '轮播图', component: config },
{ path: '/qiantaiyuangong', name: '前台员工', component: qiantaiyuangong },
{ path: '/changdiyuyue', name: '场地预约', component: changdiyuyue }
]
}
]
const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes
})
export default router
路由设计特点:
- 嵌套路由:children配置实现模块化页面结构
- 路由懒加载:
() => import()动态导入,优化首屏加载 - HashHistory模式:无需服务器配置,适合静态部署
- meta元信息:affix标识固定标签页
4.1.2 页面组件结构
views/
├── HomeView.vue # 首页(统计图表)
├── login.vue # 登录页
├── index.vue # 主框架(侧边栏+标签栏)
├── updatepassword.vue # 修改密码
├── forget.vue # 忘记密码
├── config/ # 轮播图管理
├── huiyuan/ # 会员管理
│ ├── list.vue # 列表页
│ ├── form.vue # 表单页
│ └── center.vue # 个人中心
├── jiaolian/ # 教练管理
├── qiantaiyuangong/ # 前台员工管理
├── kechengxinxi/ # 课程信息管理
├── kechengyuyue/ # 课程预约管理
├── changdixinxi/ # 场地信息管理
├── changdiyuyue/ # 场地预约管理
├── shouyinjiesuan/ # 收银结算管理
├── jiankangjilu/ # 健康记录管理
├── jiaoxueziyuan/ # 教学资源管理
├── kechengbiao/ # 课程表管理
├── quxiaoyuyue/ # 取消预约管理
├── syslog/ # 操作日志管理
├── storeup/ # 收藏管理
├── discusskechengxinxi/ # 课程评论管理
├── chat/ # 问题解答管理
├── chatFriend/ # 好友管理
├── chatMessage/ # 消息管理
├── menu_manage/ # 菜单管理
└── users/ # 管理员管理
4.2 客户端前端(client_code)
4.2.1 路由配置(src/router/index.js)
import { createRouter, createWebHashHistory } from 'vue-router'
import index from '../views'
import home from '../views/pages/home.vue'
import login from '../views/pages/login.vue'
// 导入各模块组件
import huiyuanList from '@/views/pages/huiyuan/list'
import huiyuanDetail from '@/views/pages/huiyuan/formModel'
import huiyuanCenter from '@/views/pages/huiyuan/center'
import kechengxinxiList from '@/views/pages/kechengxinxi/list'
import kechengxinxiDetail from '@/views/pages/kechengxinxi/formModel'
import changdixinxiList from '@/views/pages/changdixinxi/list'
import changdixinxiDetail from '@/views/pages/changdixinxi/formModel'
// ... 更多导入
const routes = [
{
path: '/',
redirect: '/index/home' // 默认跳转首页
},
{
path: '/index',
component: index,
children: [
{ path: 'home', component: home },
{ path: 'huiyuanList', component: huiyuanList },
{ path: 'huiyuanDetail', component: huiyuanDetail },
{ path: 'huiyuanCenter', component: huiyuanCenter },
{ path: 'kechengxinxiList', component: kechengxinxiList },
{ path: 'kechengxinxiDetail', component: kechengxinxiDetail },
{ path: 'changdixinxiList', component: changdixinxiList },
{ path: 'changdixinxiDetail', component: changdixinxiDetail },
{ path: 'storeupList', component: storeupList },
{ path: 'shouyinjiesuanList', component: shouyinjiesuanList },
{ path: 'jiankangjiluList', component: jiankangjiluList },
// ... 更多路由
]
},
{
path: '/login',
component: login
}
]
const router = createRouter({
history: createWebHashHistory(process.env.BASE_URL),
routes
})
export default router
客户端特点:
- 每个模块包含list、detail、add三种页面
- 个人中心页面用于用户信息管理
- 简化的页面结构,面向普通用户
五、核心业务流程
5.1 用户登录流程
用户输入账号密码 → 前端表单验证
↓
发送登录请求 → /huiyuan/login
↓
后端查询会员表 → EntityWrapper条件查询
↓
检查账号锁定状态 → is_locked == 1?
↓ 是 ↓ 否
返回"账号锁定" 验证密码正确性
↓ 错误 ↓ 正确
错误计数+1 生成Token
达到5次锁定 存入数据库
↓ ↓
返回"密码错误" 返回Token+用户信息
↓
前端存储Token
后续请求携带Token
5.2 课程预约流程
会员浏览课程列表 → /kechengxinxi/list
↓
选择课程 → 查看详情 → /kechengxinxi/detail/{id}
↓
点击预约按钮 → 填写预约信息
↓
发送预约请求 → /kechengyuyue/add
↓
后端验证用户身份 → Token验证
↓
创建预约记录 → kechengyuyue表
↓
记录课程信息、会员信息、跨表关联ID
↓
返回预约成功 → 前端显示预约记录
↓
如需取消 → /quxiaoyuyue/add(转入取消预约表)
5.3 场地预约流程
会员浏览场地列表 → /changdixinxi/list
↓
查看场地详情 → 场地面积、设施、开放时间
↓
选择场地进行预约 → /changdiyuyue/add
↓
后端记录预约信息 → 关联会员账号
↓
预约成功 → 可在个人中心查看
5.4 收银结算流程
前台员工创建结算 → /shouyinjiesuan/add
↓
录入费用明细 → 会员费、课程费、场地费
↓
系统计算总费用 → 自动汇总
↓
关联会员账号 → huiyuanzhanghao字段
↓
会员查看待支付记录 → ispay="未支付"
↓
会员确认支付 → 更新ispay="已支付"
↓
完成结算 → 费用统计报表
六、系统特色功能
6.1 多角色权限控制
| 角色 | 功能权限 | 数据权限 |
|---|---|---|
| 管理员 | 所有功能 | 所有数据 |
| 前台员工 | 收银结算、会员管理、问题解答 | 相关业务数据 |
| 教练 | 课程管理、教学资源、课程表 | 自己的课程数据 |
| 会员 | 课程预约、场地预约、健康记录 | 自己的预约记录 |
权限实现机制:
- menu表配置各角色的菜单权限
- 后端通过Session获取tableName判断角色
- 前端动态渲染菜单和按钮
6.2 账号安全机制
- 密码错误计数:max_password_wrong字段
- 自动锁定:连续5次错误自动锁定
- 手动解锁:管理员可解锁或密码重置
- Token过期:1小时有效期
6.3 数据统计功能
- 按值统计(柱状图)
- 时间维度统计(日/周/月/年)
- 分组统计(饼图)
- 多字段统计(折线图)
6.4 私信聊天功能
- 会员与教练/管理员私信
- 消息已读状态追踪
- 好友关系管理
七、API接口清单
7.1 用户认证接口
| 接口路径 | 方法 | 说明 |
|---|---|---|
| /huiyuan/login | POST | 会员登录 |
| /huiyuan/register | POST | 会员注册 |
| /huiyuan/logout | POST | 会员退出 |
| /huiyuan/session | GET | 获取当前用户 |
| /huiyuan/resetPass | POST | 密码重置 |
| /jiaolian/login | POST | 教练登录 |
| /qiantaiyuangong/login | POST | 前台员工登录 |
7.2 课程管理接口
| 接口路径 | 方法 | 说明 |
|---|---|---|
| /kechengxinxi/page | GET | 课程分页列表 |
| /kechengxinxi/list | GET | 课程列表 |
| /kechengxinxi/info/{id} | GET | 课程详情 |
| /kechengxinxi/save | POST | 新增课程 |
| /kechengxinxi/update | POST | 更新课程 |
| /kechengxinxi/delete | POST | 删除课程 |
| /kechengyuyue/page | GET | 预约分页列表 |
| /kechengyuyue/add | POST | 新增预约 |
| /kechengyuyue/value/{x}/{y} | GET | 预约统计 |
7.3 场地管理接口
| 接口路径 | 方法 | 说明 |
|---|---|---|
| /changdixinxi/page | GET | 场地分页列表 |
| /changdixinxi/info/{id} | GET | 场地详情 |
| /changdiyuyue/add | POST | 新增场地预约 |
7.4 收银结算接口
| 接口路径 | 方法 | 说明 |
|---|---|---|
| /shouyinjiesuan/page | GET | 结算分页列表 |
| /shouyinjiesuan/save | POST | 新增结算 |
| /shouyinjiesuan/update | POST | 更新支付状态 |
| /shouyinjiesuan/value/{x}/{y} | GET | 费用统计 |
八、部署说明
8.1 后端部署
# 1. 导入数据库
mysql -u root -p < cl05925562.sql
# 2. 修改配置文件
修改application.yml数据库连接信息
# 3. 打包项目
mvn clean package -DskipTests
# 4. 启动服务
java -jar springboot-schema.jar
8.2 前端部署
# 1. 安装依赖
npm install
# 2. 配置后端地址
修改.env.production文件
# 3. 打包项目
npm run build
# 4. 部署dist目录到Web服务器
九、总结
本体育馆管理系统采用Spring Boot + Vue3前后端分离架构,实现了体育馆的全面数字化管理。系统特点:
- 技术先进:Spring Boot 2.2.2 + Vue 3主流技术栈
- 架构清晰:前后端分离,职责明确
- 功能完善:课程、场地、财务、健康等核心业务
- 安全可靠:密码锁定机制 + Token认证
- 数据可视化:ECharts统计分析
- 扩展性好:模块化设计,易于功能扩展
更多推荐
所有评论(0)