
简介这是一套面向高校计算机相关专业毕业设计的完整项目资料基于Spring Boot框架开发大学生体质测试管理系统采用B/S架构与MySQL数据库适合正在准备毕业设计或需要Java Web实战案例的学生与开发者参考。资源包共797个文件约53.23MB涵盖99个Java源码文件、41个Vue组件、34个HTML页面、53个CSS样式及164个JavaScript脚本另含SQL建表脚本、项目说明文档与演示视频完整呈现前后端分离的工程结构。系统划分首页、体质测试、公告资讯、留言板、个人中心与后台管理等模块管理员可管理用户、教师、体质测试、测试报告与成绩用户和教师则按权限查看对应数据功能边界清晰。已有285人学习下载可帮助读者快速理解Spring Boot项目分层设计、权限控制与数据库表结构并借助开发说明与录屏完成环境搭建和二次开发是毕业设计选题与答辩准备的实用参考。1. 从一份体测成绩单说起这套 SpringBoot 管理系统到底解决什么问题每年九月高校体育部最头疼的事不是组织测试而是测完之后那堆数据。50 米跑、坐位体前屈、800 米、1000 米、引体向上、肺活量几千名学生的成绩分散在十几张 Excel 里辅导员催着要汇总体育老师要算达标率教务处要按《国家学生体质健康标准》出报表。手工合并表格的后果就是学号对不上、成绩重复录入、某个学院漏交一批数据最后谁也不知道哪份才是最终版。基于 SpringBoot 框架的大学生体质测试管理系统本质就是把这套流程从 Excel 搬到 Web 端学生能查自己的成绩和历年变化体育老师能批量录入和审核管理员能按学院、年级、项目维度出统计报表。它属于典型的计算机毕业设计选题——业务边界清晰、技术栈主流、工作量可控同时又能覆盖 SpringBoot 后端开发、MyBatis 持久化、前后端分离、权限控制这些面试常问的点。如果你正在找计算机毕业设计题目或者想拿一个真实业务场景练 SpringBoot 全栈这个方向值得认真做一遍。下面我按实际开发顺序把选型、建表、接口、踩坑和验收一条条讲清楚。2. 技术选型与工程骨架为什么这套组合最适合毕业设计落地2.1 后端为什么锁定 SpringBoot MyBatis 而不是别的毕业设计的时间窗口通常只有两三个月还要同时写论文、准备答辩。技术选型的核心原则不是“用最新最炫的”而是“生态成熟、出问题能搜到答案、代码量可控”。SpringBoot 在这个场景下的优势很直接内嵌 Tomcat一个main方法就能跑起来不用单独配 Web 服务器starter 依赖把版本冲突问题基本抹平application.yml集中管理数据源、端口、日志改配置不用翻 XML。持久层选 MyBatis 而不是 JPA理由更实际。体测系统里有大量统计查询——按学院算平均分、按项目算达标率、按学年做同比——这些用 MyBatis 手写 SQL 反而比 JPA 的 Criteria API 更直观也更容易在答辩时讲清楚“这条 SQL 干了什么”。分页用 PageHelper 插件一行代码搞定物理分页这是 MyBatis 生态里最成熟的方案。前端如果时间紧用 Thymeleaf 做服务端渲染最省事如果想让简历好看一点就上 Vue Axios 做前后端分离后端只返回 JSON。两种都能跑通我一般建议基础一般的同学先用 Thymeleaf 把业务跑通再考虑拆前后端。2.2 用 Spring Initializr 生成骨架并跑通第一个接口不要手动建 Maven 工程直接用 IDEA 的 Spring Initializr 或者 start.spring.io 生成省去写pom.xml的功夫。勾选依赖时把下面这几个选上Spring Web、MyBatis Framework、MySQL Driver、Lombok。生成后先改application.yml把数据源配好server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fitness_test?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # XML 映射文件位置 type-aliases-package: com.example.fitness.entity configuration: map-underscore-to-camel-case: true # 数据库下划线自动映射驼峰字段map-underscore-to-camel-case这个配置一定要开否则student_id映射不到studentId查出来全是 null这是新手最常见的翻车点之一。serverTimezone也必须带MySQL 8 不配时区会直接报连接异常。然后写一个最简单的健康检查接口验证工程能跑RestController RequestMapping(/api/health) public class HealthController { GetMapping public MapString, Object check() { MapString, Object result new HashMap(); result.put(status, UP); result.put(timestamp, System.currentTimeMillis()); return result; } }启动类跑起来浏览器访问http://localhost:8080/api/health能看到 JSON 就说明骨架没问题。这一步别跳过很多后面排查半天的“接口 404”根源就是启动类包路径不对导致组件扫描不到。2.3 分层结构怎么切才不会被答辩老师追问标准分层是 controller / service / mapper / entity但毕业设计里我建议再加一层 DTO 和 VO。DTO 接前端传参VO 返回给前端entity 只对应数据库表。这样做的好处是体测成绩表里有敏感字段比如身份证号直接返回 entity 会泄露用 VO 可以只挑需要的字段。答辩时老师问“为什么不用 entity 直接返回”你能答出字段隔离和安全考虑就是加分项。包结构建议按业务模块分而不是按技术层分com.example.fitness ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo └── common # 统一返回结果、异常处理、工具类common包里放一个ResultT统一返回格式前端处理响应会省很多事。这个习惯从毕业设计就养成工作后直接受益。3. 数据库设计与核心表体测业务的数据模型怎么建才不返工3.1 五张核心表撑起整个系统体测系统的数据关系其实不复杂抓住“谁、测了什么、什么时候测的、成绩多少、达没达标”这条主线就够了。核心表我一般设计成五张表名作用关键字段student学生信息id, student_no, name, gender, college_id, class_namecollege学院信息id, college_nametest_item测试项目id, item_name, unit, sort_ordertest_record测试记录核心id, student_id, item_id, score, test_year, test_datesys_user系统用户id, username, password, role, student_idtest_record是整张系统的核心student_id item_id test_year建议加联合唯一索引防止同一个学生同一年同一个项目被录入两次。这个约束在数据库层面挡住比在代码里判断靠谱得多。性别字段用tinyint1 男 2 女而不是字符串省空间也方便统计。测试项目表里unit存单位秒、厘米、毫升前端展示时直接拼上去不用硬编码。3.2 建表 SQL 与初始化数据CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender TINYINT NOT NULL COMMENT 1男2女, college_id BIGINT NOT NULL, class_name VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE test_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, item_id BIGINT NOT NULL, score DECIMAL(6,2) NOT NULL COMMENT 成绩数值, test_year VARCHAR(10) NOT NULL COMMENT 如2024-2025, test_date DATE, UNIQUE KEY uk_student_item_year (student_id, item_id, test_year), INDEX idx_year (test_year) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字符集一定用utf8mb4不要用utf8。学生姓名里偶尔有生僻字utf8存不进去会直接报错这个坑我在真实项目里踩过。score用DECIMAL不用FLOAT成绩要精确到小数点后一位浮点数会有精度问题。初始化数据里test_item按国家标准把项目录进去身高体重、肺活量、50 米跑、坐位体前屈、立定跳远、引体向上男/仰卧起坐女、1000 米男/800 米女。sort_order控制前端展示顺序别依赖 id 排序。3.3 达标判定逻辑放在哪一层达标标准是分性别、分年级、分项目的比如大一男生 50 米跑 7.1 秒是优秀大三大四标准会放宽。这套规则如果写死在代码里改一次标准就要重新发版。我的做法是单独建一张score_standard表存gender grade item_id level min_score max_score判定时查表比对。public String judgeLevel(Long itemId, Integer gender, String grade, BigDecimal score) { // 根据项目、性别、年级查出该项目的所有等级区间 ListScoreStandard standards standardMapper.selectByItem(itemId, gender, grade); for (ScoreStandard std : standards) { // 注意跑步类项目成绩越小越好逻辑要区分 if (std.getIsAsc() 1) { if (score.compareTo(std.getMinScore()) 0 score.compareTo(std.getMaxScore()) 0) { return std.getLevel(); } } else { if (score.compareTo(std.getMinScore()) 0 score.compareTo(std.getMaxScore()) 0) { return std.getLevel(); } } } return 不及格; }这里的关键参数是isAsc——标记这个项目是“越大越好”还是“越小越好”。跑步、50 米这类是越小越好肺活量、跳远是越大越好。如果不区分跑步成绩 7 秒会被判成不及格这是逻辑上的硬伤。把判定规则做成配置表答辩时老师问“标准变了怎么办”你答“改数据不改代码”这就是工程思维的体现。4. 核心功能实现成绩录入、批量导入与统计报表怎么写4.1 单个成绩录入接口与参数校验成绩录入看着简单但校验规则不少学生必须存在、项目必须存在、成绩必须在合理范围、同一年同一项目不能重复。用 SpringBoot 的Valid注解做参数校验比在方法里写一堆 if 清爽得多。PostMapping(/record) public ResultVoid addRecord(RequestBody Valid TestRecordDTO dto) { // 1. 校验学生是否存在 Student student studentMapper.selectById(dto.getStudentId()); if (student null) { return Result.fail(学生不存在); } // 2. 校验成绩范围防止录入 999 这种脏数据 if (dto.getScore().compareTo(BigDecimal.ZERO) 0 || dto.getScore().compareTo(new BigDecimal(9999)) 0) { return Result.fail(成绩数值超出合理范围); } // 3. 唯一索引兜底捕获重复插入异常 try { testRecordMapper.insert(dto.toEntity()); } catch (DuplicateKeyException e) { return Result.fail(该学生本学年此项目成绩已存在); } return Result.success(); }DuplicateKeyException是 Spring 对数据库唯一约束冲突的封装捕获它比先查再插更高效也避免了并发下的竞态问题。参数校验的边界值0 和 9999根据实际项目调整肺活量可能上万所以上限要放宽。4.2 用 EasyExcel 做批量导入避开 POI 的内存坑体测数据最大的来源是 Excel。用 Apache POI 直接读几千行数据就可能 OOM因为 POI 会把整个文件加载到内存。阿里开源的 EasyExcel 用 SAX 模式逐行读内存占用极低是毕业设计里处理 Excel 的首选。PostMapping(/import) public ResultImportResult importExcel(RequestParam(file) MultipartFile file) { ListTestRecordImportDTO failList new ArrayList(); int[] successCount {0}; try { EasyExcel.read(file.getInputStream(), TestRecordImportDTO.class, new AnalysisEventListenerTestRecordImportDTO() { Override public void invoke(TestRecordImportDTO data, AnalysisContext context) { // 逐行处理每行独立事务失败不影响其他行 try { recordService.saveOne(data); successCount[0]; } catch (Exception e) { data.setFailReason(e.getMessage()); failList.add(data); } } Override public void doAfterAllAnalysed(AnalysisContext context) { // 全部解析完成后的回调 } }).sheet().doRead(); } catch (IOException e) { return Result.fail(文件读取失败); } ImportResult result new ImportResult(successCount[0], failList); return Result.success(result); }关键点是逐行独立处理某一行学号不存在或成绩格式错误只记录这一行的失败原因不影响其他行导入。返回结果里带上成功条数和失败明细前端可以导出失败清单让老师修正后重新导入。这个设计比“全部成功或全部回滚”更实用因为 Excel 数据脏是常态。4.3 统计报表的 SQL 怎么写才不慢统计报表是体测系统的门面功能。按学院算达标率、按项目算平均分、按学年做趋势对比这些查询如果写不好几千条数据就能让页面卡住。按学院统计达标率的 SQLSELECT c.college_name, COUNT(*) AS total, SUM(CASE WHEN r.level ! 不及格 THEN 1 ELSE 0 END) AS pass_count, ROUND(SUM(CASE WHEN r.level ! 不及格 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS pass_rate FROM test_record r JOIN student s ON r.student_id s.id JOIN college c ON s.college_id c.id WHERE r.test_year #{year} GROUP BY c.college_name ORDER BY pass_rate DESC;这里有个性能细节level字段如果是在录入时就算好存进test_record的统计时直接查就行如果每次统计都实时判定SQL 会变得很复杂且慢。我的做法是在录入和导入时就计算好level并落库统计只做聚合。代价是标准表变更后需要重算历史数据但体测标准几年才改一次这个代价可以接受。test_year上建了索引student_id和college_id是外键关联字段也要确保有索引。几千条数据可能感觉不出来但答辩演示时如果数据量上万没索引的查询会明显卡顿老师一眼就能看出来。5. 避坑与排查这套系统开发中最容易翻车的五个地方5.1 中文乱码从数据库到前端一条链都要查现象学生姓名在数据库里显示正常但接口返回给前端变成问号或乱码。原因乱码可能出现在三个环节——数据库连接字符集、表字符集、HTTP 响应编码。MySQL 连接 URL 里如果没带characterEncodingutf8驱动会用默认编码表如果建成了latin1存进去就是错的SpringBoot 返回 JSON 默认是 UTF-8但如果手动设置了produces且写错也会乱。解决连接 URL 加useUnicodetruecharacterEncodingutf8建表统一utf8mb4检查server.servlet.encoding.charsetUTF-8和forcetrue。三个地方都确认一遍基本能根治。5.2 分页查询总数不对PageHelper 的 count 查询被 JOIN 干扰现象列表分页显示每页 10 条但总页数算出来偏大或偏小。原因PageHelper 会自动生成 count 查询但如果你的 SQL 里有JOIN且GROUP BY自动生成的 count 可能不准。比如按学院分组统计时count 算的是分组前的行数。解决在 mapper 方法上手动指定 count 查询或者用PageHelper.startPage(pageNum, pageSize, false)关闭自动 count自己写一个 count 方法。这个坑在带 JOIN 的列表查询里特别常见。5.3 批量导入时事务失效同类内部调用不经过代理现象导入方法上加了Transactional但某一行失败后前面成功的行也被回滚了或者反过来该回滚的没回滚。原因Spring 的事务是基于 AOP 代理的如果在同一个类里 A 方法直接调用本类的 B 方法B 上有Transactional调用不经过代理事务注解不生效。解决把需要独立事务的方法抽到另一个 Service 类里通过注入调用或者用AopContext.currentProxy()拿到代理对象再调。毕业设计里更简单的做法是逐行处理时不加事务靠唯一索引和手动补偿保证一致性。5.4 前端跨域开发阶段 403 但 Postman 正常现象用 Postman 调接口一切正常浏览器里前端请求报 CORS 错误。原因浏览器同源策略拦截Postman 不受此限制。前后端分离时前端跑在 5173 或 8081后端跑在 8080端口不同就是跨域。解决后端加全局 CORS 配置或者用 Vite 的server.proxy做代理。生产环境建议用 Nginx 反向代理把前端和后端放同一个域名下从根上避免跨域。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) // 生产环境要改成具体域名 .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns和allowedOrigins的区别当allowCredentials(true)时allowedOrigins(*)会报错必须用allowedOriginPatterns。这个细节很多人卡半天。5.5 打包部署后接口 404jar 包里 mapper XML 没打进去现象IDEA 里跑得好好的mvn package之后java -jar启动访问接口报 404 或 mapper 绑定异常。原因MyBatis 的 XML 文件默认放在src/main/java下的 mapper 包里Maven 打包时只编译.java文件.xml不会被复制到target/classes。解决在pom.xml的build里加资源目录配置把src/main/java下的 XML 也纳入打包resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources或者更规范的做法是把 XML 统一放到src/main/resources/mapper下mapper-locations配成classpath:mapper/*.xml。两种都行关键是打包后确认target/classes里能看到 XML 文件。6. 从能跑到能答辩验收清单与一个提升演示效果的小技巧系统能跑起来只是及格线答辩时老师看的不是“功能有没有”而是“边界处理得怎么样、数据一致性有没有考虑、异常情况怎么兜底”。我整理了一份验收清单按这个过一遍基本能挡住大部分追问。验收项检查方法合格标准成绩重复录入同一学生同一项目同一年录两次第二次提示已存在不产生脏数据批量导入脏数据Excel 里混入不存在的学号该行失败并返回原因其他行正常导入达标判定边界录入刚好等于及格线的成绩判定结果与标准表一致分页总数带 JOIN 的列表翻到最后一页总页数与实际数据量吻合权限隔离学生账号访问管理接口返回 403 或跳转登录打包部署java -jar启动后访问所有接口无 404、无 mapper 绑定异常最后分享一个提升演示效果的小技巧准备一份 200 条左右的模拟数据覆盖不同学院、不同年级、不同达标等级。答辩演示时先展示一个学生的成绩趋势图用 ECharts 画折线再切到管理端展示学院达标率排行。数据量够大统计图才有说服力老师也能直观看到系统的实际价值。模拟数据不要手动录写一个DataInitRunner实现CommandLineRunner项目启动时自动灌入。这样换台电脑演示数据环境一键还原不用带着 SQL 文件到处跑。Component public class DataInitRunner implements CommandLineRunner { Autowired private MockDataService mockDataService; Override public void run(String... args) { // 只在数据为空时初始化避免重复灌数据 if (studentMapper.count() 0) { mockDataService.generate(200); } } }这个CommandLineRunner在开发阶段特别实用但记得加数据量判断否则每次重启都插一遍唯一索引会直接报错。上线前把这段逻辑注释掉或者加个配置开关。我自己做毕业设计时最大的教训是功能写完就以为万事大吉结果答辩前一周发现打包部署跑不起来熬夜排查 mapper XML 的打包问题。后来养成习惯每完成一个模块就mvn package跑一次确认 jar 包能独立启动。这个习惯比任何技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取