基于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);
    }
}

代码解析:

  1. @RestController:标识为RESTful API控制器,返回JSON数据
  2. @IgnoreAuth:自定义注解,标记无需Token验证的公开接口
  3. 登录安全机制:密码错误计数 + 自动锁定功能
  4. EntityWrapper:MyBatis Plus条件构造器,灵活构建查询条件
  5. MPUtil工具类:封装查询条件处理逻辑
  6. @SysLog:自定义注解,配合AOP切面记录操作日志
  7. 统计分析:提供按值统计、分组统计、总数统计多种接口
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方法...
}

设计要点:

  1. @TableName:MyBatis Plus注解,映射数据库表名
  2. @TableId(type = IdType.AUTO):指定主键自增策略
  3. @JsonFormat:Jackson注解,序列化时格式化日期
  4. @DateTimeFormat:Spring注解,接收参数时解析日期
  5. Serializable:支持序列化传输
  6. 泛型设计:支持从其他对象复制属性

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

路由设计特点:

  1. 嵌套路由:children配置实现模块化页面结构
  2. 路由懒加载() => import()动态导入,优化首屏加载
  3. HashHistory模式:无需服务器配置,适合静态部署
  4. 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前后端分离架构,实现了体育馆的全面数字化管理。系统特点:

  1. 技术先进:Spring Boot 2.2.2 + Vue 3主流技术栈
  2. 架构清晰:前后端分离,职责明确
  3. 功能完善:课程、场地、财务、健康等核心业务
  4. 安全可靠:密码锁定机制 + Token认证
  5. 数据可视化:ECharts统计分析
  6. 扩展性好:模块化设计,易于功能扩展

更多推荐