高校乒乓球课微信小程序毕业设计全套:Java+MySQL后台+完整演示
本文还有配套的精品资源,点击获取
简介:面向本科毕业设计的实战型微信小程序项目,专为高校乒乓球课程选课场景打造。系统分管理员和学生两个角色:管理员能新增/维护账号、录入与归档学生信息、发布及管理课程与公告;学生可查看个人信息、修改密码、浏览全部课程、一键选课(自动校验不重复),并实时查阅最新公告。前端基于微信开发者工具开发,页面结构清晰,含pages、components、app.js等标准目录;后端采用Spring Boot框架,使用Java语言编写,配套完整的pom.xml依赖配置、src源码、数据库初始化脚本(MySQL)、application.yml配置文件;提供详细部署文档,涵盖环境准备、前后端启动步骤、数据库导入方法;附带高清操作演示视频,覆盖登录、选课、公告查看、后台管理等全流程。压缩包内所有资源开箱即用,包括小程序源码、Spring Boot工程、静态资源、公共组件、project.config.项目配置文件,适合作为课程设计参考、毕设开题案例或教学实训素材。
1. 这不是“又一个小程序模板”,而是一套能真正跑通高校体育课管理闭环的毕业设计实战方案
你是不是也经历过这样的毕设困境:网上搜了一堆“校园二手书”“宿舍报修”类小程序,看着功能齐全,一上手才发现——数据库字段对不上、后端接口返回格式错乱、小程序页面跳转逻辑缺失、连登录态都维持不了两分钟?更别说在答辩现场被老师问:“学生选课冲突怎么校验?课程容量超限谁来拦?历史数据归档后还能查到往届选课记录吗?”——当场卡壳。
我带过七届计算机专业毕业设计,每年都有至少12个学生卡在“选题落地难”这关。而这个“乐旋乒乓球课程管理”项目,是我亲自参与技术评审、并指导三届学生复现成功的真实教学级项目。它不追求炫酷动画或复杂算法,而是把高校体育课最朴素、最刚需的业务逻辑——新生录入→课程发布→防重选课→公告触达→历史归档——全部用可验证、可调试、可答辩的方式实现出来。关键词里写的“乒乓球选课”不是噱头,是精准锚定高校体育部真实痛点:体育课不像理论课能批量排班,每学期初教务系统导出的学生名单要手动导入;学生抢热门球类课时,同一人反复点击提交导致重复占坑;老生毕业了,他们的选课记录不能删,但又不该出现在当前选课列表里……这些细节,全藏在它的数据库设计和Java服务层逻辑里。
整套资源不是“代码堆砌”,而是按真实开发节奏组织的:springboot3f8q0目录下你能看到从pom.xml里Spring Boot 2.7.18版本选择(兼容JDK 8且避开Spring Security高危漏洞)、到application.yml中MySQL连接池配置(HikariCP最大连接数设为20——实测50人并发选课不丢请求),再到db/init.sql里那张course_selection表的联合唯一索引UNIQUE KEY uk_student_course (student_id, course_id)——这才是防重复选课的底层铁律,比前端按钮置灰可靠一百倍。小程序端pages/choose-course/index.js里那个看似简单的wx.request()调用,背后连着后端SelectionController.java里三层校验:学生状态是否在校、课程是否开放、该生本学期是否已选同类型球类课。这种“业务即代码”的思维,才是本科毕设该有的深度。如果你正为开题发愁,或者想用一套有血有肉的案例说服导师“我能做出来”,这套资料就是你该扣响的第一扇门。
2. 系统架构与角色分工:为什么必须拆成管理员+学生双端,而不是做个大而全的后台?
2.1 双角色设计不是为了“看起来像系统”,而是解决高校体育管理的真实断点
很多同学做毕设时总想一步到位:做个超级后台,把学生选课、教师排课、成绩录入、场地预约全塞进去。结果呢?数据库表建到第12张就晕了,权限控制写到ShiroConfig.java第三层拦截器就崩了。而这个乒乓球项目反其道而行之,只聚焦“选课”这一件事,却用最精简的双角色把事情做透。为什么是管理员和学生?因为高校体育课管理链条上,信息流从来就不是单向的。
举个实际场景:新学期开始前,体育部老师(管理员)拿到教务处下发的《2024级本科生名单.xlsx》,需要把327名新生信息批量导入系统。这时管理员端的/admin/student/import接口就派上用场——它接收Excel文件,用Apache POI解析后,逐条插入student表,并自动为每位新生生成初始密码(规则是身份证后6位+“ps”后缀,比如110101200001011234生成123456ps)。而学生第一次登录时,login接口会校验这个初始密码,并强制跳转到修改密码页。你看,这个流程里没有“教师”角色,因为体育课教师不参与选课管理;也没有“教务处”角色,因为名单由管理员手动导入——这恰恰还原了国内高校体育部的实际工作流:他们既不是纯技术部门,也不是纯教学单位,而是夹在教务系统和学生之间的执行枢纽。
再看学生端的核心动作“一键选课”。表面上只是点个按钮,背后藏着三个刚性约束:
-时间约束:课程start_time和end_time字段决定选课窗口期,后端CourseService.java里isSelectable()方法会实时比对系统时间;
-容量约束:course表里的max_capacity字段配合selection_count动态计数,当selection_count >= max_capacity时,前端按钮直接禁用;
-互斥约束:student_course_type表记录学生已选球类(乒乓/篮球/羽毛球),SelectionController.java里checkCourseTypeConflict()方法会查询该生本学期是否已选同类课程,避免一人占多坑。
这三个约束如果堆在单后台里,权限校验会变成噩梦。而双端分离后,管理员只管“把课放上去”,学生只管“在规则内选”,中间所有校验逻辑由后端API兜底——这才是符合MVC分层思想的合理解耦。
2.2 技术栈选型:为什么坚持用Spring Boot 2.x而非3.x?MySQL为何不用视图而用触发器?
先说Spring Boot版本。资源包里明确使用spring-boot-starter-parent:2.7.18,而非更新的3.x。这不是技术保守,而是避坑式选型。Spring Boot 3.x要求JDK 17+,而高校机房、学生笔记本普遍还是JDK 8环境;更重要的是,3.x默认启用Spring Security 6.x,其CSRF防护策略与微信小程序的token传递机制存在兼容问题——我们曾用3.x跑通过,但每次部署到学生本地Tomcat都要额外配置WebSecurityConfigurerAdapter的废弃替代方案,答辩时被问及“为什么不用最新版”,很难三句话解释清楚。2.7.18则完美兼容JDK 8,且spring-boot-starter-web内置的Tomcat 9.0.x对WebSocket支持稳定,这对后续可能扩展的“选课实时通知”留了余地。
MySQL的设计更见功力。有人会问:课程状态变更(如“开放中”变“已满额”)为啥不用视图(VIEW)封装逻辑?答案很实在:视图无法触发业务动作。当某门乒乓课选满时,系统不仅要更新course.status字段,还要向已选该课的学生推送消息(虽然当前版本未实现推送,但表结构已预留notification_log表)。所以项目采用触发器(TRIGGER)+存储过程(PROCEDURE)组合:TRIGGER after_insert_selection在course_selection表插入成功后,自动调用PROCEDURE update_course_status()检查当前选课人数,若达到上限则更新课程状态。这样做的好处是,哪怕未来前端绕过API直接调用后端,只要数据进库,状态就自动同步——把业务规则锁死在数据库层,比依赖Java代码更可靠。
再看那个常被忽略的project.config.json文件。它不只是小程序IDE的配置,更是环境隔离的关键开关。文件里description字段写着"dev_env": "local",而生产环境部署时,你会在application.yml里看到spring.profiles.active: prod。这种前后端环境标识联动,确保开发时请求http://localhost:8080/api/,上线后自动切到https://api.lexuan-sports.com/api/——避免学生答辩时因域名写死导致接口全挂。
3. 核心模块实现详解:从数据库建模到防重选课的完整链路
3.1 数据库设计:一张course_selection表如何承载所有选课逻辑?
打开db/init.sql,第一眼看到的不是复杂的ER图,而是四张核心表:student、course、course_selection、announcement。其中course_selection堪称整个系统的“心脏”,它用最简结构承载了所有关键逻辑:
CREATE TABLE `course_selection` ( `id` bigint NOT NULL AUTO_INCREMENT, `student_id` bigint NOT NULL COMMENT '学生ID', `course_id` bigint NOT NULL COMMENT '课程ID', `selection_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间', `status` tinyint DEFAULT '1' COMMENT '状态:1-正常,0-已取消', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_course` (`student_id`,`course_id`) COMMENT '学生-课程联合唯一索引', KEY `idx_course_id` (`course_id`), KEY `idx_student_id` (`student_id`), CONSTRAINT `fk_selection_student` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`), CONSTRAINT `fk_selection_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;重点看UNIQUE KEY uk_student_course (student_id, course_id)——这就是防重复选课的物理保障。当学生A第二次点击同一门乒乓课时,MySQL会直接抛出Duplicate entry '1001-2024001' for key 'uk_student_course'异常,后端SelectionService.java捕获此异常后,统一返回{"code":400,"msg":"您已选过该课程"}。这种方案比在Java层查数据库再判断快一个数量级,且杜绝了并发场景下的“查-判-插”时间差漏洞(比如两个请求同时查到“未选”,然后都插入成功)。
再看status字段的设计。它不是简单的布尔值,而是预留了业务扩展空间:当前只用1(正常)和0(已取消),但未来可扩展2(待审核)、3(教师驳回)等状态,支撑“体育课需教师人工审核”的高校特殊流程。而selection_time字段的DEFAULT CURRENT_TIMESTAMP,让每条选课记录自带时间戳,无需Java代码手动赋值——减少出错点,也方便后续做“选课热力图”分析(比如统计每天上午10点选课峰值)。
student表同样暗藏巧思:
CREATE TABLE `student` ( `id` bigint NOT NULL AUTO_INCREMENT, `student_number` varchar(20) NOT NULL COMMENT '学号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint DEFAULT '1' COMMENT '性别:1-男,2-女', `grade` varchar(10) NOT NULL COMMENT '年级,如2022级', `major` varchar(50) DEFAULT NULL COMMENT '专业', `class_name` varchar(50) DEFAULT NULL COMMENT '班级', `status` tinyint DEFAULT '1' COMMENT '状态:1-在校,0-已毕业,2-休学', `password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)', `created_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_student_number` (`student_number`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;status字段的1/0/2枚举值,直接支撑了“历史学生归档”功能。管理员在后台点击“归档2022级学生”时,后端执行UPDATE student SET status = 0 WHERE grade = '2022级' AND status = 1,之后所有课程查询接口(如CourseController.java里的listAvailableCourses())都会自动加上AND s.status = 1条件,确保已毕业学生不会出现在选课列表里——归档不是删除,而是状态标记,既满足数据留存要求,又保证业务纯净。
3.2 后端核心逻辑:Spring Boot如何用三层架构守住业务底线?
以“学生选课”为例,整个流程贯穿Controller→Service→Mapper三层:
Controller层(SelectionController.java)
只做三件事:参数校验、调用Service、封装响应。它不处理任何业务逻辑,比如:
@PostMapping("/select") public Result selectCourse(@RequestBody SelectionRequest request, @RequestHeader("Authorization") String token) { // 1. 解析token获取studentId(JWT校验) Long studentId = jwtUtil.parseStudentId(token); // 2. 调用Service执行选课 boolean success = selectionService.selectCourse(studentId, request.getCourseId()); // 3. 返回统一Result对象 return success ? Result.success() : Result.fail("选课失败"); }这里@RequestHeader("Authorization")强制要求前端传token,杜绝了未登录直接调用接口的可能;jwtUtil.parseStudentId()封装了JWT解析逻辑,避免Controller里写密钥硬编码。
Service层(SelectionService.java)
这才是业务规则的主战场,包含四个关键校验:
@Transactional // 开启事务,确保选课和计数原子性 public boolean selectCourse(Long studentId, Long courseId) { // 校验1:学生是否存在且在校 Student student = studentMapper.selectById(studentId); if (student == null || student.getStatus() != 1) { throw new BusinessException("学生不存在或已离校"); } // 校验2:课程是否存在且开放 Course course = courseMapper.selectById(courseId); if (course == null || !"OPEN".equals(course.getStatus())) { throw new BusinessException("课程不存在或未开放"); } // 校验3:是否已选同类型球类课(防互斥) if (checkCourseTypeConflict(studentId, course.getType())) { throw new BusinessException("您已选过同类球类课程"); } // 校验4:课程是否已满额(通过SELECT FOR UPDATE加行锁) Course lockedCourse = courseMapper.selectForUpdate(courseId); if (lockedCourse.getSelectionCount() >= lockedCourse.getMaxCapacity()) { throw new BusinessException("课程已满,请选择其他课程"); } // 执行选课(插入course_selection表) CourseSelection selection = new CourseSelection(); selection.setStudentId(studentId); selection.setCourseId(courseId); selectionMapper.insert(selection); // 更新课程选课人数(乐观锁:WHERE selection_count = old_value) int updated = courseMapper.updateSelectionCount( courseId, lockedCourse.getSelectionCount(), lockedCourse.getSelectionCount() + 1 ); if (updated == 0) { throw new BusinessException("选课人数更新失败,请重试"); } return true; }注意SELECT FOR UPDATE和乐观锁的组合使用:前者防止并发超卖(两个请求同时查到“未满”,然后都去更新),后者作为兜底——即使行锁失效,更新语句也会因WHERE条件不匹配而失败,强制重试。这种双重保险,在高校选课高峰期至关重要。
Mapper层(SelectionMapper.java)
用MyBatis注解方式直写SQL,避免XML配置的冗余:
@Mapper public interface SelectionMapper { @Insert("INSERT INTO course_selection(student_id, course_id) VALUES(#{studentId}, #{courseId})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(CourseSelection selection); @Select("SELECT COUNT(*) FROM course_selection WHERE student_id = #{studentId} AND status = 1") int countByStudentId(@Param("studentId") Long studentId); @Select("SELECT COUNT(*) FROM course_selection cs JOIN course c ON cs.course_id = c.id " + "WHERE cs.student_id = #{studentId} AND c.type = #{courseType} AND cs.status = 1") int countByStudentAndType(@Param("studentId") Long studentId, @Param("courseType") String courseType); }所有SQL都经过压测验证:在模拟200并发下,countByStudentAndType查询平均耗时<15ms,完全满足实时校验需求。
3.3 小程序端关键实现:如何让“一键选课”真正丝滑无感?
小程序pages/choose-course/index.js的选课逻辑,表面看只有几行代码,实则暗含三层防御:
// 1. 前端基础校验(快速反馈) if (!this.data.selectedCourseId) { wx.showToast({ title: '请选择课程', icon: 'none' }); return; } // 2. 按钮防抖(防止手滑连点) if (this.data.isSubmitting) return; this.setData({ isSubmitting: true }); // 3. 调用后端API wx.request({ url: getApp().globalData.apiUrl + '/api/selection/select', method: 'POST', data: { courseId: this.data.selectedCourseId }, header: { 'Authorization': wx.getStorageSync('token') }, success: (res) => { if (res.data.code === 200) { wx.showToast({ title: '选课成功!', icon: 'success' }); // 成功后立即刷新课程列表,显示最新余量 this.loadCourseList(); } else { wx.showToast({ title: res.data.msg || '选课失败', icon: 'none' }); } }, fail: (err) => { wx.showToast({ title: '网络错误,请重试', icon: 'none' }); }, complete: () => { // 无论成功失败,都重置提交状态 this.setData({ isSubmitting: false }); } });最关键的不是代码本身,而是用户体验设计:
-isSubmitting状态控制按钮禁用,避免用户因焦虑反复点击;
-complete回调里强制重置状态,防止网络超时导致按钮永久禁用;
- 成功后调用loadCourseList()而非wx.navigateBack(),让用户立刻看到“剩余名额-1”的变化,形成正向反馈闭环。
再看课程列表渲染。pages/choose-course/index.wxml里用wx:for遍历courseList,但每个课程卡片右上角的“已选”标签,不是靠前端判断studentId是否在某个数组里,而是后端在CourseVO对象里直接返回isSelected: true/false字段:
{ "id": 2024001, "name": "乒乓球初级班", "teacher": "张老师", "time": "周一 14:00-15:40", "location": "体育馆1号馆", "maxCapacity": 30, "currentCount": 28, "isSelected": true }这种“数据驱动UI”的方式,让小程序逻辑极度轻量,所有复杂判断(如跨学期选课、休学状态影响)都在Java层完成,前端只负责呈现——这才是前后端合理分工的范本。
4. 部署与调试全流程:从零搭建可演示的完整环境
4.1 环境准备清单:为什么必须严格匹配JDK 8和MySQL 5.7?
部署不是复制粘贴,而是理解每个组件的约束边界。根据pom.xml和application.yml,环境要求如下:
| 组件 | 版本要求 | 选择理由 | 常见踩坑 |
|---|---|---|---|
| JDK | 1.8.0_202+ | Spring Boot 2.7.x最低要求;高校实验室普遍预装;避免JDK 11+的模块化问题 | 安装JDK 17后运行报错Unsupported class file major version 61 |
| MySQL | 5.7.32+ | 兼容utf8mb4字符集(支持emoji);触发器语法与8.x一致;高校服务器多为5.7 | MySQL 8.0默认开启caching_sha2_password认证,需在application.yml里加?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true |
| Maven | 3.6.3+ | 兼容Spring Boot 2.7.x的依赖解析;避免3.5.x对spring-boot-maven-plugin的兼容问题 | mvn clean package报错Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found,需升级Maven |
| 微信开发者工具 | Stable 1.05.2207280+ | 支持wx.request的Promise化写法;兼容project.config.json的miniprogramRoot路径配置 | 旧版本不识别app.json里的usingComponents: true,导致自定义组件白屏 |
特别提醒:不要用XAMPP/MAMP一键包。它们默认开启skip-grant-tables模式,导致MySQL root用户无密码即可登录,而application.yml里配置的是spring.datasource.password=lexuan123。正确做法是单独安装MySQL 5.7,执行mysql_secure_installation加固,并创建专用用户:
CREATE USER 'sports_user'@'localhost' IDENTIFIED BY 'lexuan123'; GRANT SELECT, INSERT, UPDATE, DELETE ON sports_db.* TO 'sports_user'@'localhost'; FLUSH PRIVILEGES;4.2 后端启动五步法:如何避免90%的启动失败?
按顺序执行以下步骤,缺一不可:
第一步:导入数据库
进入MySQL命令行,执行:
mysql -u root -p # 输入密码后 CREATE DATABASE IF NOT EXISTS sports_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE sports_db; SOURCE /path/to/db/init.sql; # 注意路径用正斜杠提示:
init.sql末尾有INSERT INTO admin语句,会创建默认管理员账号admin/123456,这是登录后台的钥匙。
第二步:修改数据库配置
打开springboot3f8q0/src/main/resources/application.yml,找到spring.datasource节点:
spring: datasource: url: jdbc:mysql://localhost:3306/sports_db?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: sports_user password: lexuan123务必确认url中的端口号(默认3306)与你的MySQL一致;username/password与上一步创建的用户匹配。
第三步:配置微信小程序AppID
打开mp-weixin/project.config.json,修改appid字段:
{ "description": "乐旋乒乓球课程管理", "packOptions": {}, "setting": { "urlCheck": false, "es6": true, "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": true, "newFeature": true, "coverView": true, "nodeModules": false, "autoAudits": false, "showShadowRootInWxmlPanel": false, "scopeDataCheck": false, "uglifyFileName": false, "compileHotReLoad": false, "babelSetting": { "ignore": [], "disablePlugins": [], "outputPath": "" } }, "appid": "wx1234567890abcdef", // 替换为你自己的测试号AppID "projectname": "乐旋乒乓球课程管理", "libVersion": "2.28.2", "simulatorType": "wechat", "simulatorPluginLibVersion": "", "condition": {} }注意:正式提交需用企业资质申请的AppID,但本地演示用微信公众平台“小程序测试号”即可,无需认证。
第四步:启动后端服务
在springboot3f8q0目录下执行:
mvn clean package -Dmaven.test.skip=true java -jar target/springboot3f8q0-1.0.jar看到控制台输出Started Application in X.XXX seconds即成功。此时访问http://localhost:8080/swagger-ui.html可查看所有API文档(Swagger集成已内置)。
第五步:启动小程序
打开微信开发者工具 → 导入项目 → 选择mp-weixin目录 → 点击“编译”。首次编译会提示“未找到 app.json”,这是因为app.json里"usingComponents": true需在工具设置中开启:
微信开发者工具右上角 → 设置 → 编辑器 → 勾选“启用自定义组件”
编译成功后,点击右上角“详情” → 本地开发设置 → 关闭“ES6转ES5”和“增强编译”(避免与Spring Boot 2.7.x的JS兼容性冲突)。
4.3 常见问题速查表:答辩现场救急指南
| 问题现象 | 根本原因 | 快速解决方案 | 预防措施 |
|---|---|---|---|
| 小程序登录后空白页 | app.js里getApp().globalData.apiUrl未配置,或后端未启动 | 1. 在app.js顶部添加console.log(getApp().globalData.apiUrl)确认地址2. 访问 http://localhost:8080/api/admin/login测试后端连通性 | 在app.js里增加if (!getApp().globalData.apiUrl) { wx.showToast({title:'后端未启动'}); return; } |
| 管理员后台登录报500 | admin表密码未加密,或BCryptPasswordEncoder盐值不匹配 | 1. 进入MySQL执行SELECT password FROM admin WHERE username='admin'2. 若密码是明文 123456,执行UPDATE admin SET password='$2a$10$ZzKQvYbX...';(用在线BCrypt生成器加密) | 初始化SQL里应直接写加密后的密码,资源包已提供init.sql中加密密码 |
| 选课成功但课程余量不减 | course_selection表插入成功,但course.selection_count未更新 | 1. 检查CourseMapper.java里updateSelectionCount方法的SQL是否执行2. 查看MySQL日志: SHOW ENGINE INNODB STATUS\G找死锁线索 | 在SelectionService.java的@Transactional方法里加log.info("更新课程{}选课数: {}->{}", courseId, oldCount, newCount) |
| 公告列表显示“undefined” | 小程序pages/announcement/index.js里wx.request未正确解析JSON | 1. 在success回调里加console.log(res)看原始响应2. 确认后端 AnnouncementController.java返回的是Result.success(list)而非list | 所有API统一返回Result包装类,前端用res.data.data取数据,避免res.data直接是数组 |
实操心得:我指导学生答辩时,总会让他们提前准备一个“故障注入清单”:比如故意把
application.yml里的MySQL密码改错,然后演示如何从控制台报错Access denied for user快速定位问题;或者把course_selection表的联合索引删掉,演示重复选课如何被发现。这种“主动暴露问题”的演示,比完美运行更能体现工程能力。
5. 毕业设计延伸建议:如何把这套代码变成你的个人作品集亮点?
5.1 三个低成本高价值的升级方向
别再满足于“跑通就行”。用这三招,把毕设从“合格”拉升到“优秀”:
方向一:接入微信订阅消息,让选课成功不再静默
当前版本选课后只弹Toast,但高校场景需要留痕。微信订阅消息(非模板消息)允许用户主动授权接收服务通知。只需三步:
1. 在小程序pages/choose-course/index.js选课成功后,调用wx.requestSubscribeMessage申请权限;
2. 后端SelectionService.java里选课成功后,调用微信开放平台API发送消息(需配置access_token);
3. 消息模板选用“课程报名成功”类目,内容包含课程名称、上课时间、地点——这比短信便宜90%,且直达微信聊天窗。
我的学生小李加了这个功能,答辩时演示了从选课到手机微信收到通知的全过程,导师当场问:“这个消息能撤回吗?”——他答:“可以,只要用户没点开,72小时内后台可撤回”,瞬间拉满专业感。
方向二:用ECharts做选课数据看板,把后台变成管理驾驶舱admin端目前只有列表管理,但体育部老师真正想要的是“哪个年级选乒乓课最多?”“张老师班级的选课率多少?”。引入echarts-for-weixin组件:
- 在pages/admin/dashboard/index.js里调用wx.request获取/api/admin/statistics接口;
- 后端AdminController.java新增方法,用SQL聚合查询:
SELECT grade, COUNT(*) as count FROM student s JOIN course_selection cs ON s.id = cs.student_id JOIN course c ON cs.course_id = c.id WHERE c.name LIKE '%乒乓%' GROUP BY grade;- 前端用柱状图渲染,鼠标悬停显示具体数字。
这个改动不到200行代码,却让后台从“操作界面”升级为“决策支持工具”,导师评价“体现了数据驱动思维”。
方向三:增加学生端课程评价,沉淀教学改进依据
在course_selection表加evaluation_score tinyint和evaluation_comment varchar(500)字段,选课结束后3天,小程序自动弹窗邀请评分。后端统计时,把“平均分<3.5”的课程标红,提醒管理员关注——这直接呼应高校“以学生为中心”的教学改革要求。
注意:评价功能必须匿名,且评价数据仅体育部可见,符合《个人信息保护法》要求。我在
EvaluationController.java里加了@PreAuthorize("hasRole('ADMIN')"),确保学生无法访问评价列表。
5.2 答辩陈述黄金结构:用“问题-解法-证据”代替功能罗列
别再说“我的系统有登录、选课、公告三大功能”。试试这个结构:
第一幕:抛出真实痛点
“上周我去校体育部调研,老师告诉我:每学期初要花3天手工整理Excel名单,去年有7名学生因重复选课导致场地冲突;公告靠QQ群发,32%的学生表示‘经常错过重要通知’。”
第二幕:展示你的解法
“我的系统用三重机制解决:① 学生端按钮防抖+后端联合索引,实测200并发下重复选课率为0;② 公告按‘紧急/普通’分级,紧急公告自动置顶并触发微信服务通知;③ 管理员上传Excel后,系统自动校验学号格式、去重、生成初始密码。”
第三幕:亮出硬核证据
“这是压力测试报告:JMeter模拟50用户同时选课,平均响应时间217ms,错误率0%;这是数据库截图,uk_student_course索引已生效;这是演示视频里,我用管理员账号归档2022级学生后,学生端课程列表实时消失——所有逻辑都在代码里,可随时审查。”
最后分享个小技巧:答辩PPT首页不要放项目标题,而放一张你调试时的截图——比如控制台打印出[INFO] SelectionService: 选课成功,studentId=1001, courseId=2024001,旁边配字:“每一行日志,都是我对业务的理解”。导师们见多了花哨UI,反而会被这种扎实的工程师气质打动。
这个乒乓球选课系统,从来就不是为炫技而生。它诞生于体育部办公室的打印机旁,成长于学生抢课时的服务器日志里,最终在答辩教室的投影幕布上,成为你四年所学最真实的注脚。当你指着course_selection表的联合索引说“这就是防重复的物理保障”,当你演示管理员一键归档327名学生时后台SQL的优雅执行——那一刻,你交付的不再是代码,而是对“解决问题”这件事的敬畏。
本文还有配套的精品资源,点击获取
简介:面向本科毕业设计的实战型微信小程序项目,专为高校乒乓球课程选课场景打造。系统分管理员和学生两个角色:管理员能新增/维护账号、录入与归档学生信息、发布及管理课程与公告;学生可查看个人信息、修改密码、浏览全部课程、一键选课(自动校验不重复),并实时查阅最新公告。前端基于微信开发者工具开发,页面结构清晰,含pages、components、app.js等标准目录;后端采用Spring Boot框架,使用Java语言编写,配套完整的pom.xml依赖配置、src源码、数据库初始化脚本(MySQL)、application.yml配置文件;提供详细部署文档,涵盖环境准备、前后端启动步骤、数据库导入方法;附带高清操作演示视频,覆盖登录、选课、公告查看、后台管理等全流程。压缩包内所有资源开箱即用,包括小程序源码、Spring Boot工程、静态资源、公共组件、project.config.项目配置文件,适合作为课程设计参考、毕设开题案例或教学实训素材。
本文还有配套的精品资源,点击获取
