当前位置: 首页 > news >正文

学生选课微信小程序全栈开发包(含SSM后台源码、MySQL建表脚本与部署说明)

本文还有配套的精品资源,点击获取

简介:直接可用的学生选课微信小程序完整工程,前端基于微信原生框架(mp-weixin结构),已适配微信开发者工具,包含app.、app.js、app.wxss等标准入口文件;后端采用SSM架构(Spring+SpringMVC+MyBatis),Java源码位于src目录,pom.xml已声明全部依赖,target为编译输出路径;数据库使用MySQL,db目录提供完整建表语句与初始化数据;配套文档含系统功能说明、角色权限说明(学生/教师/管理员三端操作流程)、环境配置步骤、接口简要说明及常见问题提示。学生端支持课程浏览、选课、退课、收藏管理;教师端可维护本人授课课程、导出选课名单;管理员端覆盖用户增删改查、课程分类设置、选课审核开关、系统参数调整等核心管理功能。所有配置项均带中文注释,路径与命名符合主流开发规范,适合高校课程设计、毕业设计快速搭建或教学演示使用。

1. 这不是“又一个Demo”,而是一套能直接跑进教室的选课系统

我带过六届计算机专业的课程设计,每年都有至少三组学生卡在“选课系统怎么才算做完”这个问题上。不是写不出代码,而是写出来的系统——前端页面花里胡哨但登录后404,后端接口能跑通但一并发就报Connection reset,数据库建了五张表却漏了外键约束,导出Excel名单时中文全变成问号……最后交作业那天,学生抱着笔记本在机房门口等我,眼神里全是“老师,它好像……不太听使唤”。

这套“学生选课微信小程序全栈开发包”,就是我从这些真实踩坑现场里,一刀一刀削出来的“教学级可用体”。它不追求炫技的微服务架构,也不堆砌Spring Cloud全家桶;它用最稳的SSM(Spring + SpringMVC + MyBatis)打底,MySQL 5.7+ 兼容,微信小程序原生框架(mp-weixin结构),所有路径、命名、注释全部按高校实验室机房那台老i5电脑+Windows 10+微信开发者工具稳定版的实际环境来对齐。学生双击微信小程序的学生选课系统/mp-weixin目录就能打开开发者工具,教师导入项目就能演示“教师端查看选课名单”这个核心教学环节,管理员改个配置就能开关“本学期选课通道”。它没有一行代码是为“看起来高级”写的,每一行都写着“今天下午三点前必须跑通”。

关键词里的微信小程序,不是指“能用微信打开”,而是指它已通过微信官方基础库2.28.3版本实测,app.json"style": "v2"已启用,所有<button open-type="getUserInfo">类授权逻辑已替换为<button open-type="getPhoneNumber">兼容方案,并在project.config.json中明确标注了AppID占位符与合法域名白名单配置位置;学生选课系统,不是泛泛的功能罗列,而是把“学生点击课程卡片→弹出选课确认框→提交后实时更新底部Tab栏红点数→刷新收藏列表”这一闭环链路,在pages/course/detail/detail.jspages/mine/collect/collect.js里做了三重状态同步校验;SSM框架,不是只放了个pom.xml,而是把Spring事务边界精确控制在CourseService.javaselectCourse()方法内,MyBatis的<foreach>批量插入语句在CourseMapper.xml第87行做了batchSize="50"硬编码限制,防止MySQLmax_allowed_packet溢出;MySQL建库,不是丢个.sql文件了事,db/01_init_schema.sql里每张表都带COMMENT中文说明,user_role字段用TINYINT(1)而非ENUMcourse_schedule表的week_days字段用SET('周一','周二','周三','周四','周五','周六','周日'),既保证查询效率,又让非DBA出身的指导老师一眼看懂数据含义。它解决的从来不是“能不能做”,而是“能不能在45分钟课堂演示里,不掉链子地讲清楚选课业务流”。

2. 整体架构设计:为什么选SSM+原生小程序?而不是Spring Boot+uni-app?

2.1 三层解耦不是为了画图好看,而是为了让学生“看得见摸得着”

很多教学项目喜欢上手就甩个Spring Boot,理由很充分:自动配置、起步依赖、内嵌Tomcat……但实际带课时我发现,学生面对application.yml里密密麻麻的spring.datasource.hikari.*参数,第一反应是截图发群里问“老师,这个timeout单位是毫秒还是秒?”——他们缺的不是配置能力,而是对“连接池到底在管什么”的具象认知。

所以这套包坚持用原始SSM三件套,目的非常明确:把每一层的“胶水”都暴露出来。
-Spring层src/main/resources/spring/applicationContext.xml里,<tx:annotation-driven transaction-manager="transactionManager"/>这行配置,配合@Transactional注解,让学生亲手删掉它,再点选课按钮,立刻看到“选课成功但数据库没写入”的脏数据现象;
-SpringMVC层src/main/resources/springmvc/springmvc-servlet.xml<mvc:annotation-driven/>开启后,@RequestBody@ResponseBody的转换逻辑,能让他们在CourseController.java里加断点,亲眼看到JSON字符串如何被Jackson反序列化成CourseVO对象;
-MyBatis层src/main/resources/mybatis/mybatis-config.xml<setting name="logImpl" value="STDOUT_LOGGING"/>打开控制台SQL日志,学生调试时不用猜“我的SQL到底执行没”,直接看控制台刷出的Preparing: INSERT INTO student_course...Parameters: 2023001(String), 101(Integer)

这种“慢半拍”的设计,恰恰是教学场景最需要的呼吸感。它不掩盖问题,而是把问题变成可触摸的教学切片。

2.2 微信小程序端:拒绝“黑盒式”框架,回归原生开发心智

uni-app或Taro这类跨端框架,对学生做毕设确实省事,但对理解“小程序生命周期”是灾难性的。比如onLoadonShow的区别,uni-app封装后学生永远分不清该在哪个钩子里调API;再比如自定义组件通信,uni-app的$emit$on让学生误以为事件总线是万能的,结果在真实小程序里遇到this.selectComponent找不到实例时彻底懵圈。

因此,本包前端严格采用微信原生mp-weixin结构,且做了三处关键教学适配:
1.页面路由显性化app.json"pages"数组按教学逻辑排序——["pages/index/index", "pages/course/list/list", "pages/course/detail/detail", "pages/mine/mine"],学生打开app.json就能直观建立“首页→课程列表→课程详情→我的”业务流映射;
2.API调用标准化封装utils/request.js里统一管理wx.request,所有接口请求都走request.post('/api/course/select', {studentId, courseId}),错误处理统一拦截if (res.statusCode === 401) wx.navigateTo({url: '/pages/login/login'}),避免学生在每个页面重复写授权逻辑;
3.状态管理轻量化:不引入Redux或Pinia,用app.js全局globalData存用户token和角色,pages/mine/mine.jsgetApp().globalData.role === 'teacher'直接控制按钮显隐,代码少、逻辑清、调试易——毕竟,教学生理解“状态在哪”比教他们背诵“状态管理原理”重要十倍。

2.3 数据库设计:用“教学友好型范式”替代“教科书式第三范式”

db/01_init_schema.sql里的表结构,是我和三位高校数据库老师反复推演的结果。它没追求理论上的完美范式,而是把“学生能看懂、教师能讲透、答辩能说清”作为最高准则。

以核心的student_course选课关系表为例:

CREATE TABLE `student_course` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', `student_id` VARCHAR(20) NOT NULL COMMENT '学号', `course_id` INT NOT NULL COMMENT '课程ID', `status` TINYINT(1) DEFAULT 1 COMMENT '选课状态:1-已选,0-已退', `created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX `idx_student` (`student_id`), INDEX `idx_course` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生选课关系表';

这里刻意没做student_idcourse_id的外键约束(FOREIGN KEY),原因很实在:高校实验环境MySQL常禁用外键(尤其阿里云RDS默认关闭),学生导入SQL时若报错ERROR 1215 (HY000): Cannot add foreign key constraint,90%会放弃调试。取而代之的是在CourseService.javaselectCourse()方法里,用两次SELECT COUNT(*)先校验学号和课程ID是否存在,再执行INSERT——代码多写几行,但教学稳定性提升一个数量级。

再看course课程主表,credit学分字段用DECIMAL(2,1)而非INT,因为现实中有“1.5学分”的体育课;teacher_ids教师ID字段用VARCHAR(100)存逗号分隔字符串(如'T2023001,T2023005'),而非新建course_teacher关联表。这不是偷懒,而是考虑到:
- 学生做课程设计时,常需快速展示“某门课由哪几位老师合上”,用FIND_IN_SET('T2023001', teacher_ids)一条SQL就能查出,比写JOIN清晰十倍;
- 教师端维护课程时,“添加授课教师”功能只需前端拼接字符串,后端UPDATE course SET teacher_ids = ? WHERE id = ?,零SQL复杂度;
- 答辩时老师问“如果一位老师离职,如何批量清理他教的所有课?”,学生能脱口而出:“UPDATE course SET teacher_ids = REPLACE(teacher_ids, 'T2023001,', '') WHERE teacher_ids LIKE '%T2023001,%'”,这种具象答案比背诵“关联表设计原则”有力得多。

3. 核心模块实现与实操要点:从环境搭建到三端联调

3.1 环境准备:避开Windows下最痛的三个坑

部署成功率,往往卡在环境配置的第一步。根据我收集的237份学生报错日志,Windows环境下三大高频陷阱必须前置规避:

提示:以下操作均在微信小程序的学生选课系统根目录执行,非mp-weixin子目录

坑一:MySQL时区导致CURRENT_TIMESTAMP写入NULL
现象:导入db/01_init_schema.sql后,student_course.created_time字段全为NULL,后台日志报WARN [DruidDataSource] discard long time none received connection
根因:Windows版MySQL默认时区为SYSTEM,而JDBC驱动要求serverTimezone=GMT%2B8
解决方案:修改src/main/resources/jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/course_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useSSL=false

注意%2B+的URL编码,直接写GMT+8会解析失败。实测下来,漏掉allowPublicKeyRetrieval=true会导致MySQL 8.0+连接超时,这个细节在readme.txt第12行有加粗提示。

坑二:微信开发者工具无法识别app.json中的"usingComponents"
现象:打开pages/course/list/list页面,控制台报Component is not found in path "components/course-card/course-card"
根因:微信开发者工具对路径大小写敏感,而Windows文件系统不区分大小写,components目录若被误命名为Components(首字母大写),工具会找不到。
解决方案:在资源管理器中确认mp-weixin/components/course-card/路径全小写,且course-card.json"component": true必须存在。我在mp-weixin目录下放了个check_path.bat脚本,双击运行会自动检测所有组件路径合法性。

坑三:Java编译编码导致中文注释乱码
现象:CourseController.java// 查询课程列表显示为// 妗犺璇剧▼鍒楄〃,IDEA编译报错非法字符
根因:Windows记事本保存的.java文件默认ANSI编码,而Maven编译要求UTF-8。
解决方案:用VS Code打开任意.java文件 → 右下角点击ANSI→ 选择Save with EncodingUTF-8。更一劳永逸的方法是在pom.xml中强制指定编码:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <!-- 关键! --> </configuration> </plugin>

3.2 后端核心逻辑:事务边界与并发选课的“教科书级”实现

学生选课最经典的并发问题——两个学生同时抢最后一门课,如何保证数据库只写入一条记录?本包在CourseService.java中给出了教学场景最优解:数据库唯一索引 + 应用层重试,而非一上来就上分布式锁。

@Transactional(rollbackFor = Exception.class) public Result selectCourse(String studentId, Integer courseId) { // 步骤1:检查课程余量(非原子操作,仅作快速过滤) Course course = courseMapper.selectById(courseId); if (course.getRemainNum() <= 0) { return Result.fail("课程已满"); } // 步骤2:尝试插入选课记录(唯一索引保障幂等) StudentCourse sc = new StudentCourse(); sc.setStudentId(studentId); sc.setCourseId(courseId); sc.setStatus((byte) 1); try { studentCourseMapper.insert(sc); // 唯一索引:UNIQUE KEY `uk_student_course` (`student_id`, `course_id`) } catch (DuplicateKeyException e) { return Result.fail("您已选过该课程"); } // 步骤3:扣减余量(关键:此处必须用UPDATE ... WHERE id = ?,避免ABA问题) int updated = courseMapper.updateRemainNum(courseId, -1); if (updated == 0) { // 余量不足,回滚整个事务(@Transactional保证) throw new RuntimeException("选课失败:课程余量不足"); } return Result.success("选课成功"); }

这里藏着三个教学重点:
-为什么步骤1的remainNum检查不加锁?因为它是“乐观预判”,真正决定成败的是步骤2的唯一索引冲突检测。教学时让学生删掉UNIQUE KEY,再压测,立刻看到重复选课数据——这就是索引价值的最直观证明;
-为什么步骤3用updateRemainNum而非SELECT ... FOR UPDATE因为FOR UPDATE在高并发下易引发死锁,而UPDATE course SET remain_num = remain_num - 1 WHERE id = ? AND remain_num > 0一句SQL完成条件更新,MySQL的行锁机制天然保障原子性;
-@Transactional为何必须标注在service方法上?CourseController.java@PostMapping("/select")方法上加@Transactional是无效的!因为Spring AOP代理只对service层生效。这个坑,我让学生在控制器里加断点调试三次才刻进DNA。

3.3 小程序端三端权限控制:用app.js全局状态实现“零配置切换”

三端角色(学生/教师/管理员)的权限控制,没用复杂的JWT或RBAC模型,而是回归最朴素的app.js全局变量 + 页面onLoad动态渲染,原因很直白:学生答辩时,老师问“权限怎么控制的?”,他能指着app.js第15行globalData: { userInfo: null, role: 'student' }说清楚,而不是背诵“基于声明的访问控制”。

具体实现分三步:
1.登录态注入pages/login/login.js中,用户输入学号/工号后,调用wx.login()获取code,传给后端/api/user/login接口,返回{role: 'teacher', token: 'xxx'},存入wx.setStorageSync('userInfo', res.data)并同步到getApp().globalData
2.页面权限拦截pages/mine/mine.jsonLoad里:

onLoad() { const app = getApp(); if (app.globalData.role === 'student') { this.setData({ tabList: ['我的选课', '我的收藏'] }); } else if (app.globalData.role === 'teacher') { this.setData({ tabList: ['我的课程', '选课名单'] }); } else { this.setData({ tabList: ['用户管理', '课程管理', '系统设置'] }); } }
  1. 按钮级显隐控制pages/course/detail/detail.wxml中:
<!-- 教师专属操作 --> <view wx:if="{{app.globalData.role === 'teacher'}}"> <button bindtap="handleEditCourse">编辑课程</button> </view> <!-- 管理员专属操作 --> <view wx:if="{{app.globalData.role === 'admin'}}"> <button bindtap="handleAuditSelect">审核选课</button> </view>

这种方案的好处是:学生改app.jsrole值,立刻看到界面变化,无需重启项目;教师演示时,用不同账号登录,同一套代码自动适配三端视图;答辩时,学生能当场修改roleadmin,演示“管理员删除违规课程”全流程——所有操作都在眼皮底下,没有黑盒。

3.4 数据库初始化:从空库到可演示系统的三步落地

db/目录下的脚本不是一次性导入完就结束,而是按教学节奏分层加载:

脚本文件执行时机教学价值关键内容
01_init_schema.sql首次部署建立数据库骨架包含7张表DDL,每张表COMMENT注明业务含义,如userrole字段注释为'角色:0-学生,1-教师,2-管理员'
02_init_data.sql导入schema后快速填充演示数据插入3个学生、2个教师、1个管理员,5门课程(含1门“满员”课程用于并发测试),10条选课记录
03_update_config.sql系统上线前配置系统开关UPDATE sys_config SET config_value = '1' WHERE config_key = 'select_open'开启选课通道

特别提醒:02_init_data.sql中所有INSERT语句都显式指定字段名,如INSERT INTO user (id, username, password, real_name, role) VALUES ('S2023001', 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三', 0);,而非INSERT INTO user VALUES (...)。这是为了防止后续表结构增加字段时,学生执行脚本报Column count doesn't match value count错误。密码用MD5(123456)硬编码,方便学生快速登录测试,readme.txt第3页有明文密码对照表。

4. 实操过程详解:从零开始跑通“学生选课”完整链路

4.1 第一步:本地环境一键启动(Windows 10 + JDK 1.8 + MySQL 5.7 + 微信开发者工具)

整个流程控制在15分钟内,所有命令均来自readme.txt第5节“快速启动指南”,我把它拆解为可复制粘贴的终端指令:

后端启动(CMD窗口):

# 1. 进入Java后端目录 cd src # 2. 使用Maven打包(跳过测试避免因H2数据库配置报错) mvn clean package -Dmaven.test.skip=true # 3. 启动SpringMVC容器(内置Tomcat,端口8080) java -jar target/course-system-1.0-SNAPSHOT.jar

注意:若提示JAVA_HOME not set,请先配置JDK环境变量;若报Port 8080 already in use,修改src/main/resources/springmvc/springmvc-servlet.xml<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">port属性为8081

前端启动(微信开发者工具):
- 打开微信开发者工具 → 选择mp-weixin目录 → 点击“编译”;
- 若首次编译报错Cannot find module 'miniprogram-render',在mp-weixin目录下执行:

npm install --save miniprogram-render
  • 编译成功后,点击右上角“详情” → “本地设置” → 勾选“不校验合法域名、https证书、web-view(业务域名)”;
  • 在模拟器中点击“我的” → “登录”,输入学号S2023001,密码123456,即可进入学生端首页。

此时,浏览器访问http://localhost:8080/api/course/list应返回JSON课程列表,微信开发者工具控制台无红色报错,即后端与前端通信已打通。

4.2 第二步:学生端全流程演示(课程浏览→选课→退课→收藏)

以学号S2023001(张三)为例,完整走一遍业务闭环:

  1. 课程浏览:首页点击“全部课程”,进入pages/course/list/list页面,列表显示5门课程,其中《数据结构》剩余名额为0(满员),《高等数学》剩余15
  2. 选课操作:点击《高等数学》卡片 → 进入详情页 → 点击“立即选课”按钮 → 弹出确认框 → 点击“确定”;
  3. 状态反馈:页面顶部显示“选课成功”,底部Tab栏“我的选课”红点数字+1,返回首页刷新,该课程“已选”按钮置灰;
  4. 退课验证:进入“我的选课”页面 → 找到《高等数学》→ 点击“退课”→ 确认后,红点数字-1,首页课程卡片按钮恢复为“立即选课”;
  5. 收藏管理:在课程详情页点击右上角“收藏”图标 → 再次点击取消收藏 → 进入“我的收藏”页面验证增删。

这个过程中,关键观察点有三处:
-网络面板:在微信开发者工具“Network”标签页,筛选XHR,能看到/api/course/list/api/course/select/api/student/course等接口请求,状态码均为200
-数据库验证:用Navicat连接本地MySQL,查询student_course表,应看到student_id='S2023001'course_id=101(高等数学ID)的记录,status=1
-并发安全:开两个微信开发者工具实例,分别用S2023001S2023002登录,同时点击《高等数学》选课,最终student_course表只有一条记录,且course.remain_num准确减为14——这就是前面讲的唯一索引+UPDATE双重保障的效果。

4.3 第三步:教师端与管理员端协同验证(角色切换与数据联动)

教学演示的价值,往往体现在多角色协作场景。我们用同一套数据库,验证三端数据实时联动:

教师端操作(工号T2023001,密码123456):
- 登录后进入“我的课程”,看到《高等数学》和《数据结构》两门课;
- 点击《高等数学》右侧“导出名单”按钮 → 触发/api/teacher/course-students?courseId=101接口 → 下载students_101.xlsx文件;
- 用Excel打开,可见S2023001(张三)、S2023002(李四)等已选学生名单,姓名、学号、选课时间完整。

管理员端操作(账号admin,密码123456):
- 登录后进入“系统设置” → 找到“选课开关” → 将select_open值从1改为0→ 点击“保存”;
- 此时学生端再进入课程详情页,“立即选课”按钮变为灰色,提示“当前未开放选课”;
- 教师端“导出名单”功能不受影响,证明权限控制精准到按钮级别。

这个联动过程,让学生直观理解:
- 后端SysConfig表如何成为系统运行的“总开关”;
- 前端pages/system/config/config.js如何通过wx.request实时拉取配置并setData更新UI;
- 为什么管理员修改配置后,学生端无需刷新页面就能感知状态变化(因为每次进入课程列表页都会调用getSysConfig接口)。

4.4 第四步:部署到学生机房服务器(CentOS 7 + Nginx反向代理)

高校机房常需将系统部署到局域网服务器供全班访问。本包已预置deploy/centos-deploy.sh脚本,全程自动化:

#!/bin/bash # deploy/centos-deploy.sh # 1. 安装Java 1.8 yum install java-1.8.0-openjdk-devel -y # 2. 安装MySQL 5.7(阿里云镜像源) rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm yum install mysql-community-server -y systemctl start mysqld # 获取初始密码:grep 'temporary password' /var/log/mysqld.log # 3. 导入数据库 mysql -uroot -p'初始密码' -e "CREATE DATABASE course_db CHARACTER SET utf8mb4;" mysql -uroot -p'初始密码' course_db < db/01_init_schema.sql mysql -uroot -p'初始密码' course_db < db/02_init_data.sql # 4. 启动后端(后台运行) nohup java -jar /opt/course-system/target/course-system-1.0-SNAPSHOT.jar > /var/log/course.log 2>&1 & # 5. 配置Nginx反向代理(/etc/nginx/conf.d/course.conf) echo "server { listen 80; server_name course.local; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host \$host; } }" > /etc/nginx/conf.d/course.conf systemctl restart nginx

部署完成后,机房学生只需在浏览器访问http://course.local(需提前在C:\Windows\System32\drivers\etc\hosts添加192.168.1.100 course.local),即可使用微信扫码登录——所有操作与本地开发完全一致,真正实现“一套代码,多环境复用”。

5. 常见问题与排查技巧实录:那些年我们共同debug过的深夜

5.1 微信小程序端典型问题速查表

问题现象可能原因排查步骤解决方案
页面白屏,控制台报Cannot read property 'xxx' of undefinedapp.jsglobalData.userInfo未初始化,页面onLoad里直接访问getApp().globalData.userInfo.role1. 在app.jsonLaunch里加console.log('app launched')
2. 在页面onLoad第一行加console.log('page loaded', getApp().globalData)
确保登录成功后再跳转页面,wx.navigateTo前加if (getApp().globalData.userInfo) {...}判断
点击按钮无反应,network无请求wxmlbindtap事件名与js中函数名不一致,或函数未定义在Page({})1. 检查wxml使用VS Code的“转到定义”功能(Ctrl+Click),快速定位函数声明位置
课程图片不显示,路径为/static/images/course1.jpgstatic目录未放在mp-weixin根目录下,或图片文件名大小写不匹配(Linux区分大小写)1. 在微信开发者工具资源管理器中确认mp-weixin/static/images/路径存在
2. 右键图片 → “在资源管理器中显示”,核对文件名是否为course1.jpg而非Course1.jpg
统一使用小写字母+数字命名图片,static/images/路径在app.json中无需配置,小程序自动识别

5.2 后端Java服务高频故障处理

问题:启动时报Caused by: java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
这是典型的依赖缺失。pom.xmlspring-webmvc坐标被注释或版本号错误。打开pom.xml,找到:

<!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> -->

去掉注释符号<!-- -->,保存后重新mvn clean package。教学时我故意留了两处类似注释,让学生自己找出来——这是培养“读报错日志”能力的第一课。

问题:登录成功后,getApp().globalData.userInfo始终为null
根因在于wx.request的异步回调未正确赋值。常见错误写法:

// ❌ 错误:在success回调外赋值 let userInfo = null; wx.request({ url: '/api/user/login', success: res => { userInfo = res.data; } }); getApp().globalData.userInfo = userInfo; // 此时userInfo还是null! // ✅ 正确:在success回调内赋值并跳转 wx.request({ url: '/api/user/login', success: res => { getApp().globalData.userInfo = res.data; wx.switchTab({url: '/pages/mine/mine'}); } });

问题:MySQL插入中文变问号,SELECT查出来是???
这是字符集未统一的经典问题。三处必须一致:
1. MySQL服务端:SHOW VARIABLES LIKE 'character_set_%';确保character_set_server=utf8mb4
2. 数据库:CREATE DATABASE course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
3. JDBC连接串:jdbc.url=...&characterEncoding=UTF-8&serverTimezone=GMT%2B8
漏掉任何一处,中文都会丢失。我在db/README.md里放了三行SHOW命令,让学生逐条执行验证。

5.3 数据库与权限协同问题深度解析

场景:管理员在后台删除了学生S2023001,但该学生微信端仍能正常登录并选课
这不是Bug,而是设计选择。本包采用“软删除”策略:user表中is_deleted字段为TINYINT(1)0表示正常,1表示已删除。管理员删除操作实际执行:

UPDATE user SET is_deleted = 1 WHERE id = 'S2023001';

而后端UserService.javalogin()方法中,查询逻辑为:

User user = userMapper.selectOne(new QueryWrapper<User>() .eq("id", username) .eq("password", MD5(password)) .eq("is_deleted", 0)); // 关键:只查未删除用户

所以,只要is_deleted=1,登录就失败。若学生还能登录,说明:
- 管理员执行的是DELETE FROM user WHERE id = 'S2023001'(物理删除),而非后台提供的“删除”按钮;
- 或者userMapper.selectOne()方法未加is_deleted = 0条件(检查UserMapper.xml第45行)。

这个案例教学价值极高:它让学生理解“软删除”如何平衡数据安全与业务需求,也教会他们看懂MyBatis的QueryWrapper构造逻辑。

5.4 教学扩展建议:让课程设计不止于“能跑通”

这套包预留了多个教学扩展接口,方便教师布置进阶任务:

  • 增加课程评价功能db/目录下有04_extend_review.sql脚本,新增course_review表;pages/course/detail/detail.wxml中已预留<view class="review-section">占位;学生需补全ReviewService.java/api/review/save接口;
  • 接入校园一卡通认证utils/auth.jsloginByCard()函数留空,要求学生调用学校提供的HTTP接口,传入学号和一卡通加密令牌;
  • 生成选课统计图表pages/admin/statistics/statistics.jsloadChartData()调用/api/admin/statistics返回空JSON,学生需在CourseController.java中实现按学院、年级、课程类型的聚合查询。

我自己带课时,会让学生抽签选择一个扩展点,在答辩时演示“从需求分析→数据库设计→接口开发→前端联调”的完整过程。这套包的价值,从来不在“交付即结束”,而在于它是一块足够结实的跳板,让学生能稳稳跃向工程实践的深水区。

6. 最后一点个人体会:关于“教学级代码”的尊严

写这篇文档时,我翻出了2018年带的第一届学生做的选课系统——那个项目里,CourseController.java有372行,@RequestMapping写了17个,if-else嵌套到第七层,System.out.println("debug")散落在23个地方。当时我觉得“能跑就行”,直到有学生在答辩现场,被老师问“你这个try-catch里为什么吞掉了异常?”,他愣了三秒,说“因为不吞掉,页面就白屏了”。

后来我花了整整两个月,把所有教学项目重构成现在这样:
-CourseController.java控制在120行以内,每个方法只做一件事;
- 所有catch块必须log.error("选课失败", e)并返回Result.fail()
-utils/目录下每个工具类都有@Description注释,说明适用场景和禁忌;
-readme.txt里“常见问题”章节,按学生提问频率排序,第一条就是“为什么我的页面白屏?请先检查app.js是否初始化”。

这不是代码洁癖,而是对教学对象的尊重。学生不是来背诵设计模式的,他们是来理解“当用户点击那个按钮时,我的代码究竟发生了什么”。这套包里没有一行代码是为了“显得专业”而存在,每一行都经过真实课堂的千锤百炼——它可能不够酷,但足够诚实;它可能不够新,但足够可靠;它可能不够炫,但足够让学生,在45分钟里,真正看懂一个系统是如何呼吸的。

如果你正带着学生做课程设计,或者正在为毕业设计选题发愁,不妨把这套包当作一块磨刀石。先让它跑起来,再一点点拆开,看看事务怎么控制,看看权限怎么流转,看看数据怎么从MySQL流进小程序的setData。真正的工程能力,从来不在云端,而在你调试成功的那一刻,控制台里刷出的那一行绿色200 OK里。

本文还有配套的精品资源,点击获取

简介:直接可用的学生选课微信小程序完整工程,前端基于微信原生框架(mp-weixin结构),已适配微信开发者工具,包含app.、app.js、app.wxss等标准入口文件;后端采用SSM架构(Spring+SpringMVC+MyBatis),Java源码位于src目录,pom.xml已声明全部依赖,target为编译输出路径;数据库使用MySQL,db目录提供完整建表语句与初始化数据;配套文档含系统功能说明、角色权限说明(学生/教师/管理员三端操作流程)、环境配置步骤、接口简要说明及常见问题提示。学生端支持课程浏览、选课、退课、收藏管理;教师端可维护本人授课课程、导出选课名单;管理员端覆盖用户增删改查、课程分类设置、选课审核开关、系统参数调整等核心管理功能。所有配置项均带中文注释,路径与命名符合主流开发规范,适合高校课程设计、毕业设计快速搭建或教学演示使用。


本文还有配套的精品资源,点击获取

http://www.rkmt.cn/news/1438417.html

相关文章:

  • AI驱动招聘自动化:四大核心场景与成本效益深度解析
  • 【读书笔记】《架构即未来》精华解读
  • 保姆级教程:用Python和nuscenes-devkit从零玩转nuScenes自动驾驶数据集(附完整代码)
  • 别只当备份用!解锁PostgreSQL逻辑复制的5个高阶玩法:从CDC到微服务数据分发
  • 【字节跳动】豆包全用户统一对话全量归档公共源码
  • 你的clusterProfiler富集分析结果可靠吗?深入解读p值、q值与基因ID转换的那些‘坑’
  • AI智能体安全盲区:传统检测失效与新一代行为分析框架
  • µVision串口回环测试原理与工程实践
  • 海光 特有的Python 包 下载地址 必须有 DCU 专用版(底层含 CUDA/ROCm 二进制)
  • AI时代软件工程师的进化:从编码执行者到系统策展人
  • 神经形态计算与脉冲编码技术解析
  • 大数据分析实战指南:从核心概念到企业落地全流程解析
  • 别再乱写documentclass了!IEEEtran类选项全解析,从会议到期刊一篇搞定
  • Unity里播放WebRTC直播流?试试这个WebView插件,5分钟搞定(附完整C#读写HTML代码)
  • RT-Thread实战:信号量、互斥量、事件集,到底该用哪个?一个真实项目案例帮你选型
  • 【字节跳动】自动追溯每一位用户所有登录设备、登录地点、登录时间、切换账号记录,全域统一采集
  • 从旋转矩阵到游戏开发:伴随矩阵求逆在Unity中的一次实战应用
  • Orange Pi 5 Plus接口配置避坑指南:为什么你的UART/I2C/SPI/PWM/CAN启用后没反应?
  • PHP依赖注入与服务容器深度剖析
  • Flink 1.17 监控实战:5分钟搞定JMX和Slf4j日志双指标上报
  • 别再让SSD‘偏科’了!聊聊主控芯片里的‘雨露均沾’算法:动态与静态磨损均衡到底怎么选?
  • 手把手教你为旧版Linux系统(如Xubuntu 16.04)打RT补丁并编译内核
  • 别再只盯着Stegsolve了!聊聊CTF图片隐写中那些‘非主流’工具:从foremost分离到outguess解密实战
  • 告别Putty:用Windows Terminal或VSCode远程SSH连接树莓派,体验更现代的终端操作
  • 用AVR单片机解码DALI信号:一个定时器+GPIO中断的实战拆解(附Microchip参考代码)
  • FreeRTOS任务栈分配踩坑记:为什么我的LVGL任务跑着跑着就卡住了?
  • 避开Gazebo仿真坑:手把手教你配置Livox非重复扫描雷达的URDF模型
  • 抖音素材收集革命:5分钟搞定无水印批量下载,自媒体人必备神器!
  • Spring Boot项目引入自家SDK JAR包踩坑记:从恼人的打包警告到优雅的依赖管理方案
  • PHP依赖注入容器原理与实现