news 2026/10/5 3:56:11

SpringBoot+JSP健身管理系统开发实战:从选型到部署全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+JSP健身管理系统开发实战:从选型到部署全记录

如果你最近在找 JavaWeb 方向的练手项目或者课程设计题目,八成会看到"基于 SpringBoot 的健身管理系统"这类字眼。SpringBoot + MySQL + JSP + Maven 这个组合,在校园项目和技术练习里确实一直很能打:前端不折腾、后端够用、数据库逻辑清晰,一个人从建库到跑通全套也就几天时间。我前阵子正好完整做了一个类似项目,这篇就把从选型、建表、搭框架、写业务,到部署阶段踩过的坑完整串一遍。适合两类人看:一类是正在选毕设/课设题目、想找个完整参考路径的在校生;另一类是平时写 CRUD 但没试过 JSP 方案的 Java 后端,想快速了解这套老组合在 SpringBoot 下到底怎么落地。

1. 这套 JavaWeb 老组合为什么还能打:选型逻辑先说透

先回答很多人会问的第一个问题:都什么年代了,为什么还在用 JSP?其实答案非常现实——课程设计和中小型管理系统的核心诉求是"快速做出一个能演示、能答辩、能落地的完整系统",不是造一个高并发微服务架构。SpringBoot + JSP + MySQL + Maven 刚好卡在这个诉求上。

1.1 为什么不用前后端分离

前后端分离现在确实是主流,Vue 或 React 配合 SpringBoot 做接口开发很常见。但放到课设和练手场景里,它有非常现实的成本:你需要额外搭建 Node 环境、处理跨域、设计接口文档、维护两套工程,还要考虑前端打包产物怎么塞进 SpringBoot。对一个人来说,这套链路很容易在"前端环境起不来"或者"打包路径配错"上卡两天。而 JSP 方案直接把页面丢在src/main/webapp/WEB-INF/jsp下,后端ModelAndView或者Model带数据,页面用 JSTL 循环渲染,一条链路通到底。你说它不够现代,我承认;你说它做管理系统效率高,我也承认。

1.2 为什么是 JSP 而不是 Thymeleaf、Freemarker

SpringBoot 官方现在主推 Thymeleaf,但这套模板引擎对多数做课设的同学来说,上手成本反而更高。Thymeleaf 的语法跟 HTML 更像,理念也更现代,但网上能找到的 SpringBoot + Thymeleaf 的详细项目和 JSP 项目比,数量还是差一个量级。JSP 的好处在于:教程存量巨大、问题排查经验多、很多企业老系统还在用。你随便搜一个"SpringBoot 健身管理系统",大概率就是 JSP 版本。遇到 Bug 时,同样的报错信息你能找到的解决方案数量,比冷门模板引擎多得多。这里没有"谁更好"的问题,只有"谁更容易让你做完"的问题。

1.3 Maven、MySQL 在这个项目里到底承担什么

Maven 是项目构建和依赖管理工具,它的核心价值是:你不需要手动去下载 jar 包、把它们复制到 lib 目录。比如你要用 MySQL 驱动,在pom.xml里加一行依赖,Maven 会自动从仓库拉取。SpringBoot 的版本管理能力也很强,它帮你锁定了大量依赖的兼容版本,避免"自己瞎配版本然后跑不起来"的灾难。

MySQL 则是这个系统的数据仓库。健身管理系统里所有核心数据——会员、教练、课程、预约记录、订单——最终都要落库。选择 MySQL 而不是 Oracle、PostgreSQL 的原因也很简单:轻量、常见、文档多、可视化工具成熟。Navicat、DataGrip、命令行都支持得很好,后面查数据、改字段都非常方便。这套组合不是"最先进",但它是"上手最快、出错最少、资料最多"的现实选择。

2. 数据库先行:健身系统的表结构我是这么拆的

我习惯在写任何代码之前,先把数据库表设计出来。因为表结构直接决定了业务逻辑的长相。健身管理系统虽然叫"管理系统",但核心业务其实就几件事:会员管理、教练管理、课程管理、预约报名、订单支付。围绕这五件事,我把表拆成了两组:主表(基础资料)和业务表(流转记录)。

2.1 会员、教练、课程:三张核心主表

会员表是系统里最基本的实体。我设计会员表时重点放了几个字段:登录账号(username)、密码(password)、姓名(real_name)、手机号(phone)、身高体重(height/weight,后面算 BMI 用)、会员状态(status,1 正常、0 冻结)。

教练表(coach)同样是一张主表,除了基本资料,还有一个 specialty 字段表示擅长方向,比如增肌、减脂、康复训练。课程表(course)是比较关键的,它既要关联教练信息,也要记录课程类型、名额和时间。我用的字段大致是:course_name(课程名称)、course_type(0 团课、1 私教)、coach_id(教练外键)、max_num(最大人数)、current_num(已报名人数)、course_time(上课时间)、price(课程价格)、status(课程状态,1 上架、0 下架)。

CREATE TABLE `member` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录账号', `password` VARCHAR(100) NOT NULL COMMENT '加密后的密码', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `gender` TINYINT DEFAULT 1 COMMENT '1男 2女', `age` INT DEFAULT NULL, `height` DECIMAL(5,2) DEFAULT NULL COMMENT '身高cm', `weight` DECIMAL(5,2) DEFAULT NULL COMMENT '体重kg', `status` TINYINT DEFAULT 1 COMMENT '1正常 0冻结', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';

这里有一个我踩过的设计教训:不要用明文存密码。否则答辩的时候导师随便看一眼数据库,你基本没法解释。最省事的方式是存 MD5 或 BCrypt 摘要,登录时再把输入密码加密后比对。

2.2 预约记录和订单表:业务闭环的关键

有了主表,还要有记录"谁在什么时候干了什么"的业务表。预约表(booking_record)负责记录会员预约了哪节课程、预约状态如何。订单表(order_info)记录支付订单的流水号、金额、支付状态。这两张表是系统里最容易出现数据混乱的地方,设计要点一句话总结:所有状态变更都要留痕。

CREATE TABLE `booking_record` ( `id` INT NOT NULL AUTO_INCREMENT, `member_id` INT NOT NULL COMMENT '会员ID', `course_id` INT NOT NULL COMMENT '课程ID', `booking_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '预约时间', `status` TINYINT DEFAULT 0 COMMENT '0已预约 1已完成 2已取消', PRIMARY KEY (`id`), KEY `idx_member` (`member_id`), KEY `idx_course` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表'; CREATE TABLE `order_info` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(50) NOT NULL COMMENT '订单号', `member_id` INT NOT NULL, `course_id` INT NOT NULL, `amount` DECIMAL(10,2) NOT NULL COMMENT '实付金额', `status` TINYINT DEFAULT 0 COMMENT '0未支付 1已支付 2已退款', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `pay_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

订单号为什么要单独设唯一索引?因为订单号是面向用户的凭证,后面查账、对账、退款都要靠它。千万别偷懒直接拿自增主键当订单号,那会暴露你的订单量,而且线下沟通很不方便。我习惯用时间戳 + 随机数生成订单号,比如FT202506071730123456。

2.3 一句 SQL 看懂业务闭环

很多同学建完表之后还是糊的,不知道业务到底怎么流转。我教一个小技巧:设计完表之后,尝试用一条多表 SQL 来描述一次完整业务流程。比如"查某个会员预约过的所有课程名称、教练姓名和上课时间":

SELECT c.course_name, co.name AS coach_name, c.course_time, b.status FROM booking_record b LEFT JOIN course c ON b.course_id = c.id LEFT JOIN coach co ON c.coach_id = co.id WHERE b.member_id = 1 ORDER BY c.course_time DESC;

这条 SQL 能跑通,说明你的外键关系设计基本没毛病。如果 JOIN 不出来,说明字段关联有问题,趁早改。系统越到后面越依赖这些联表查询,早期把关系理清能省一大半的事。

3. 搭骨架流程:Maven 依赖、工程结构、JSP 视图解析一次配好

数据库设计完后,就该建工程了。这一步最容易出问题的地方,不是 Java 代码本身,而是pom.xml和配置文件。很多人的 SpringBoot + JSP 项目跑不起来,十有八九是依赖没配全或者视图解析器没生效。

3.1 pom.xml 核心依赖清单与版本策略

我强烈建议用 SpringBoot 2.7.x,而不是 SpringBoot 3.x。不要追新,这是经验之谈:SpringBoot 3 把包名从javax.servlet改成了jakarta.servlet,很多老教程、老版本的 JSP 相关依赖直接不兼容。对 JSP 项目来说,用 2.7.x 是从教材到实战全部能顺畅衔接的稳妥选择。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <!-- Web 基础 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- JSP 解析核心依赖 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <!-- JSTL 标签库 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- MyBatis 持久层 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <!-- 热部署,改代码自动重启 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> </dependencies>

tomcat-embed-jasper的scope我建议配成provided。原因是我之前遇到过一个挺恶心的报错:Duplicate ServletContainerInitializer,排查半天发现是内嵌 Tomcat 同时加载了外部 Tomcat 的容器初始化器,把scope改成provided后问题消失。这条经验网上不容易搜到,属于典型的"遇到过才记得住"。

另外如果 Maven 拉依赖很慢,记得去 Maven 安装目录下的conf/settings.xml里配阿里云镜像仓库。中央仓库在国内有时候慢到怀疑人生,换成镜像之后,mvn clean package的速度完全是两个世界。

3.2 application.yml 关键配置(含 MySQL 连接串细节)

配置文件的重点有三个:数据源、JSP 视图解析、编码。

server: port: 8080 servlet: encoding: charset: UTF-8 enabled: true force: true spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/fitness_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: root mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.fitness.entity

serverTimezone=Asia/Shanghai是必须的,否则 MySQL 8.x 会报时区错误。useSSL=false避免无证书时报错;allowPublicKeyRetrieval=true解决新版 MySQL 驱动的公钥检索限制。这三个参数,是我做 MySQL 8 连接时几乎每次都要带的"三件套",少了哪个都可能给你来一发不明所以的SQLException。

3.3 分包结构建议

单模块的 SpringBoot 项目,我习惯这样分包:

src/main/java/com/example/fitness/ ├── FitnessApplication.java ├── controller/ # Controller 层,接收请求 ├── service/ # Service 层,业务逻辑 ├── mapper/ # MyBatis Mapper 接口 ├── entity/ # 实体类 ├── config/ # 配置类,拦截器、视图配置等 ├── interceptor/ # 自定义拦截器 └── common/ # 公共返回体、异常、工具类

很多新手喜欢把所有代码堆在 Controller 里,一个类一千行,最后自己都看不懂。我的建议是:Controller 只做参数接收和页面跳转,业务逻辑全部下沉到 Service。这不仅是代码美观问题,后面加权限、加事务的时候,你会感谢自己当初的分层。

FitnessApplication.java没什么特别,标准的启动类就行:

@SpringBootApplication @MapperScan("com.example.fitness.mapper") public class FitnessApplication { public static void main(String[] args) { SpringApplication.run(FitnessApplication.class, args); } }

@MapperScan可以省掉在每个 Mapper 接口上写@Mapper的重复操作。如果你想更直观,两个方案选一个就行,别两个都写然后出现奇怪的 Bean 冲突提示。

3.4 启动时验证 JSP 视图解析是否生效

配置写完后,写一个极简 Controller 测试页面能不能打开:

@Controller public class IndexController { @GetMapping("/") public String index(Model model) { model.addAttribute("title", "健身管理系统"); return "index"; } }

然后在src/main/webapp/WEB-INF/jsp/index.jsp下放一个最简单的 JSP 文件,启动项目访问http://localhost:8080/。如果能看到页面,说明视图解析器配好了。如果 404,优先检查spring.mvc.view.prefix路径是不是拼错了——JSP 文件必须放在WEB-INF/jsp目录下,而不是resources目录,这是spring-boot-starter-web的一个"潜规则",放错位置视图解析器是找不到的。

4. 业务模块编码实录:会员登录、课程预约、订单生成

骨架搭好之后,进入真正出功能的核心阶段。我不打算把每个模块的完整代码贴一遍,那样篇幅太长,重点讲三个最值得细抠的环节:登录会话、预约校验、订单状态。这三个环节你能想明白,整个系统其他模块基本就是复制粘贴微调。

4.1 会员登录:用 Session 还是 JWT

单机部署的 SpringBoot + JSP 管理系统,我建议直接用HttpSession,一点都不丢人。JWT 是为了解决分布式、多端场景的方案,你这个项目就一台 Tomcat,没必要把一个简单问题复杂化。登录逻辑非常简单:

@Service public class MemberService { @Autowired private MemberMapper memberMapper; public Member login(String username, String password) { Member member = memberMapper.findByUsername(username); if (member == null) { return null; } String md5Pwd = MD5Util.md5(password + member.getSalt()); if (!md5Pwd.equals(member.getPassword())) { return null; } return member; } }

Controller 里把查到的会员对象放入 Session,之后每个页面想展示"当前登录人"就从 Session 取:

@PostMapping("/doLogin") public String doLogin(String username, String password, HttpSession session) { Member member = memberService.login(username, password); if (member == null) { return "redirect:/member/login?error=1"; } session.setAttribute("loginMember", member); return "redirect:/course/list"; }

密码加密我推荐用 BCrypt 或者"MD5 + 盐值"的方式。这里有个小细节:同一个会员的密码摘要里要带上随机盐值,否则张三和李四密码相同时,数据库里摘要也会一样,一旦泄露就是成片沦陷。尽管课设项目安全级别不用太高,但这个习惯值得养成。

4.2 课程预约:事务、重复预约和名额校验

预约是整个系统里业务规则最密集的地方。它至少要满足三个约束:课程必须存在且上架、名额未满、同一会员不能重复预约。我写预约逻辑时,直接把校验和扣减名额放在一个事务方法里,避免并发下出现超卖:

@Transactional(rollbackFor = Exception.class) public boolean bookCourse(Integer memberId, Integer courseId) { Course course = courseMapper.findById(courseId); if (course == null || course.getStatus() != 1) { throw new BusinessException("课程不存在或已下架"); } if (course.getCurrentNum() >= course.getMaxNum()) { throw new BusinessException("课程名额已满"); } int count = bookingMapper.countByMemberAndCourse(memberId, courseId); if (count > 0) { throw new BusinessException("您已预约过该课程"); } // 插入预约记录 BookingRecord record = new BookingRecord(); record.setMemberId(memberId); record.setCourseId(courseId); record.setStatus(0); bookingMapper.insert(record); // 课程已报名人数 +1 courseMapper.increaseCurrentNum(courseId); return true; }

@Transactional很重要。你想一下:如果插入预约记录成功,但increaseCurrentNum失败了,数据会变成"会员有预约记录,课程名额没减少",两边不一致。有了事务,要么一起成功,要么一起回滚。写业务代码的时候,凡是涉及多张表变更的操作,都问自己一句:要不要加事务。

4.3 订单生成:流水号与状态流转

订单模块我推荐做成"先创建未支付订单,再模拟支付回调"的模式。流程是:会员选中课程 → 生成订单号 → 订单状态为"未支付" → 点击支付 → 把状态改成"已支付" → 顺带把课程预约记录状态改成"已完成"。

public String createOrder(Integer memberId, Integer courseId, BigDecimal amount) { String orderNo = "FT" + System.currentTimeMillis() + RandomStringUtils.randomNumeric(4); OrderInfo order = new OrderInfo(); order.setOrderNo(orderNo); order.setMemberId(memberId); order.setCourseId(courseId); order.setAmount(amount); order.setStatus(0); orderMapper.insert(order); return orderNo; }

真正接入支付网关的话,这里的"模拟支付成功"会被替换成微信/支付宝的回调接口,但订单状态机的设计是通用的:未支付 → 已支付 → 已完成,或者未支付 → 已取消。这个状态流转想清楚后,后面加任何支付渠道都是往这个框架里塞。

4.4 JSP 页面怎么取数据

后端数据传到页面的方式有两种:Model和Session。列表数据一般放Model,通过请求转发带到 JSP;当前登录用户这类全局信息放Session。JSP 页面用 JSTL 标签取数:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <h3>当前登录:${sessionScope.loginMember.realName}</h3> <table> <c:forEach items="${courseList}" var="course"> <tr> <td>${course.courseName}</td> <td>${course.coachName}</td> <td>${course.currentNum} / ${course.maxNum}</td> <td> <a href="/course/book?courseId=${course.id}">预约</a> </td> </tr> </c:forEach> </table> </body> </html>

注意 JSP 文件顶部一定写contentType="text/html;charset=UTF-8",并且引入 JSTL 的taglib。这两行漏了,中文乱码和标签不解析两个问题会同时找上门。

5. 从开发到部署踩过的坑:JSP 打包、MySQL 连接、热部署与乱码

这个章节我把它放在最后压轴,因为这些坑几乎每个做 SpringBoot + JSP 项目的人都会踩一遍。有些问题网上解法很分散,当时花了我不少时间才拼凑出完整答案。

5.1 SpringBoot 打 jar 包跑不了 JSP,换 war 解决

最经典的一个坑:本地 IDE 里正常启动,一切都能用,但mvn clean package打成 jar 后,java -jar xxx.jar启动,访问页面直接 404。原因在于SpringBoot 的可执行 jar 默认不支持 JSP,JSP 需要 Jasper 编译器实时编译,而内嵌到 jar 里的 JSP 文件无法被正常解析。

解决方案是把项目打成 war 包,部署到外部 Tomcat。需要三步:

第一步,修改pom.xml的打包方式:

<packaging>war</packaging>

第二步,启动类继承SpringBootServletInitializer:

@SpringBootApplication @MapperScan("com.example.fitness.mapper") public class FitnessApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(FitnessApplication.class); } public static void main(String[] args) { SpringApplication.run(FitnessApplication.class, args); } }

第三步,把打出来的 war 丢到 Tomcat 的webapps目录,启动 Tomcat 后访问。如果不想部署外部 Tomcat,也可以考虑直接把前后端改成纯静态资源方案,但那就偏离 JSP 主题了。所以,做 JSP 项目,从一开始就要计划用 war 包交付。

5.2 MySQL 8.0 连接:时区、SSL、驱动类三连坑

MySQL 8.0 普及后,连接配置比老版本多了一堆讲究,而且报错信息往往看不懂。我把三个最常遇到的坑整理成了表格:

现象原因解决方案
报错The server time zone value ... unrecognizedMySQL 服务器时区与驱动不一致连接串加serverTimezone=Asia/Shanghai
报错Public Key Retrieval is not allowedMySQL 8 默认加密规则要求客户端获取公钥连接串加allowPublicKeyRetrieval=true
报错 SSL 相关告警或连接被拒驱动默认启用 SSL,但本地无有效证书连接串加useSSL=false

另外,旧教程里会写driver-class-name: com.mysql.jdbc.Driver,这个类名在 MySQL 8.x 驱动里已经迁移成了com.mysql.cj.jdbc.Driver。如果你照抄老代码,启动会直接报ClassNotFoundException。这个改动因为看起来很小,经常被忽略。

5.3 JSP 中文乱码:三层保险怎么加

乱码问题真的是老生常谈,但我每次都会被问。中文字段从数据库查到页面变成"???",或者表单提交后中文变成乱码。解决思路是三层同时设编码:

  • 第一层:JSP 文件顶部写contentType="text/html;charset=UTF-8"和pageEncoding="UTF-8";
  • 第二层:application.yml里配置server.servlet.encoding.force=true,强制请求和响应都用 UTF-8;
  • 第三层:数据库连接串指定characterEncoding=utf8,并且数据库表默认字符集用utf8mb4。

三层都配上后,中文乱码基本能根除。有时候排查半天,最后发现是某个 JSP 文件漏了第一层,所以写页面模板时把这些固定声明提前复制好,别每次手敲。

5.4 Maven 依赖冲突与 IDEA 部署注意点

Maven 依赖冲突常见的表现是:某个ClassNotFoundException或NoSuchMethodError时有时无,让人摸不着头脑。排查时用mvn dependency:tree看依赖树,重点观察同一个依赖是否有多个版本。比如mysql-connector-java,如果你在 pom 里手动指定了旧版本,而 SpringBoot 父 POM 也管理了一个新版本,两个版本同时出现就会出现诡异问题。

IDEA 跑 SpringBoot + JSP 项目时还有一个很隐蔽的问题:修改 JSP 后,内嵌 Tomcat 不一定会重新编译,页面刷新还是老样子。spring-boot-devtools可以解决一部分问题,但 JSP 的热更新效果不稳定。我实际操作中的习惯是:改完 JSP 页面,直接重启应用,省得被缓存坑到怀疑人生。

6. 上线前补上的东西:权限拦截、统计看板和扩展思路

当核心功能能跑通,系统已经不是"半成品"了。但想让它看起来像一个完整的、可以交给用户的系统,还差三样东西:权限控制、数据统计、健壮性处理。

6.1 用拦截器把登录和角色权限补齐

目前的系统,如果你直接访问/order/list,后端并不会检查你是否登录。这在演示时很尴尬,随便输入 URL 就能绕过登录。用 Spring 拦截器解决:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginMember"); if (loginUser == null) { response.sendRedirect(request.getContextPath() + "/member/login"); return false; } return true; } }

然后在配置类里注册拦截范围:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/member/login", "/member/doLogin", "/css/**", "/js/**", "/images/**"); } }

如果你有管理员和普通会员两种角色,就在拦截器里判断下 Session 里的角色字段,或者再加一个权限拦截器。这个环节虽然代码量不大,但"能不能防住直接访问"是答辩演示时经常被追问的点。

6.2 给首页加一个统计面板

管理系统没有数据统计,总觉得缺了点"管理系统"的味道。加一个简单的首页统计面板并不复杂。我一般会在首页上方放四张卡片:会员总数、今日预约人数、本月营收、上架课程数。

@GetMapping("/dashboard") public String dashboard(Model model) { model.addAttribute("memberCount", dashboardMapper.countMember()); model.addAttribute("todayBookCount", dashboardMapper.countTodayBooking()); model.addAttribute("monthIncome", dashboardMapper.sumMonthIncome()); model.addAttribute("courseCount", dashboardMapper.countCourse()); return "admin/dashboard"; }

对应的 SQL 大致长这样:

-- 今日预约人数 SELECT COUNT(DISTINCT member_id) FROM booking_record WHERE DATE(booking_time) = CURDATE(); -- 本月营收 SELECT IFNULL(SUM(amount), 0) FROM order_info WHERE status = 1 AND pay_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01');

如果你想让看板更直观,可以引入 ECharts 画折线图或者饼图。前端引入一个 eCharts 的 JS 文件即可,不需要 Vue 这种大框架。这个加分项性价比很高。

6.3 还能扩展什么:图表、支付、小程序

到这里,一个完整的 SpringBoot + JSP 健身管理系统已经从数据库到页面全部贯穿了。如果你还有时间,想让它更像一个"产品",我建议按以下顺序扩展:

  • 数据可视化:用 ECharts 画课程预约人数趋势、教练课时统计,替换掉纯数字展示;
  • 真实支付接入:用沙箱环境接微信支付或支付宝支付,把订单表状态流转跑通真实回调;
  • 导出 Excel:管理员导出会员名单、预约报表,用 Hutool 工具类几行代码就能实现;
  • 小程序/H5 端:因为接口逻辑已经跑通了,后端再加一个@RestController风格的接口模块,给小程序端复用,不过这时候就该考虑要不要走前后端分离了。

最后一个方向是我个人最推荐的:把系统的接口层面做好,方便以后再扩展其他端。虽然现在 JSP 页面直接渲染数据,但 Service 层的业务逻辑是完全可复用的。等哪天真要改造为前后端分离,你只需要在 Controller 层把ModelAndView换成@ResponseBody返回 JSON,Service 层一行都不用动。

最后说点我自己的体会:别急着上框架和花哨功能,把"课程列表-预约-签到-支付"这条主链路先用最简单的 JSP 页面跑通,比什么设计模式都重要。整个 SpringBoot + JSP 项目里,真正难的不是 CRUD 本身,而是对状态变更的考虑——名额是否满、是否重复预约、支付后课程状态怎么变。把这些业务规则梳理清楚,后面无论换成 Vue 前后端分离,还是换成微服务,你都会发现核心逻辑是一样的。希望这篇笔记能帮你少走一点我自己走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 3:56:06

Rust Send与Sync详解:从所有权到线程安全的并发基石

1. 先搞清楚&#xff1a;Send和Sync到底在说哪两件事第一次看到Send和Sync这两个 trait 的人&#xff0c;十有八九是一头雾水的——它们不像Iterator或者Display那样能一眼看出用途&#xff0c;官方文档的解释也比较绕&#xff1a;"可以跨线程转移所有权"和"可以…

作者头像 李华
网站建设 2026/10/5 3:55:15

基于SpringBoot的社区养老服务管理平台设计与实现

这个毕设选题我太熟悉了&#xff0c;每年毕业季都有不少学生拿它当主攻方向。Java SpringBoot Web&#xff0c;做社区养老服务管理平台&#xff0c;看着是“老三样”&#xff0c;但如果把业务吃透、把技术栈用扎实&#xff0c;这绝对是个能拿优秀论文的题目。今天我就把这个项…

作者头像 李华
网站建设 2026/10/5 3:55:10

Python网络入侵检测系统实战:数据集、特征工程与模型封装

简介&#xff1a;一套面向计算机、信息安全等专业毕业设计场景的网络入侵检测系统完整项目资料&#xff0c;基于Python实现&#xff0c;包含完整源码、训练数据集与技术文档&#xff0c;适用于本科毕业设计、研究生课程实践及安全工程二次开发参考。资料包共48个文件&#xff0…

作者头像 李华
网站建设 2026/10/5 3:54:51

单相PWM可控整流器Matlab仿真:双闭环控制与PI参数整定全解析

干了几年电力电子仿真&#xff0c;见太多人一上来就拖模块、截图发朋友圈说“仿真跑通了”&#xff0c;结果一问他“电感为什么取3mH”“电压环带宽为什么这么低”“启动那下电流为什么那么大”&#xff0c;全是一脸懵。单相PWM可控整流器在Matlab里的仿真&#xff0c;其实特别…

作者头像 李华
网站建设 2026/10/5 3:54:46

Linux信号机制详解:从内核原理到优雅退出实战

写这文章之前我想先问一句&#xff1a;你写Linux程序的时候&#xff0c;有没有遇到过这种情况——程序跑得好好的&#xff0c;按下CtrlC没反应&#xff0c;或者进程莫名其妙就没了&#xff0c;连个core dump都没留下&#xff1f;又或者你明明在代码里写了signal(SIGCHLD, handl…

作者头像 李华
网站建设 2026/10/5 3:54:30

Windows 10上通过Docker部署Milvus向量数据库完整指南

作为常年折腾开发环境的过来人&#xff0c;我越来越觉得向量数据库这块东西早晚是躲不掉的。不管是做RAG、语义搜索还是AI Agent的记忆管理&#xff0c;你总得有个地方把文本、图片转出来的embedding向量存起来&#xff0c;还得能按相似度快速捞回来。而在这么多向量数据库里面…

作者头像 李华