ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

学生选课系统毕业设计:Java+微信小程序+MySQL 架构拆解与避坑指南

学生选课系统毕业设计:Java+微信小程序+MySQL 架构拆解与避坑指南 简介这是面向计算机相关专业毕业设计或课程设计的微信小程序学生选课系统完整源码包使用Java 微信小程序 MySQL搭建涵盖学生、教师、管理员三类角色包含首页、个人中心、学生管理、教师管理、课程类型管理、课程信息管理、选课信息管理、取消选课管理、管理员管理、系统管理等功能模块可实现课程浏览、在线选课、取消选课及课程与人员管理等核心流程。项目采用MVC分层结构与Servlet请求处理内置数据库文件适合用来学习小程序端与Java后端联调、权限控制及日常教务管理系统的开发思路。压缩包共1245个文件约24.79MB除前后端源码外还包含大量png图片、js、vue、wxml/wxss等小程序与后台界面文件以及java、xml、sql等后端配置和数据库脚本并附带bat启动脚本与说明文档便于导入Eclipse/IDEA及微信开发者工具后直接运行调试。目前已有63人学习下载较为适合初次接触全栈小程序项目的学生作为选题参考和实践模板。1. 学生选课系统毕业设计Java小程序MySQL 这套源码包到底能给你什么临近毕业设计提交最尴尬的场景不是代码写不出来而是写完了不知道论文怎么把业务讲圆。学生选课系统微信小程序 Java MySQL是高校毕设最常见的题目组合压缩包里的 LW 一般是配套的设计文档小程序端做课程浏览与选课Java 后端做接口与业务校验MySQL 存学生、课程、选课记录三张核心表。手上有套能跑的源码省掉的就是从零搭工程、设计库表、调通联调这三块最耗时的事精力放在读懂它、改出亮点、写进论文上。下面按这类源码包最常见的结构拆开讲先看表结构和业务链路再用 IDEA 跑通后端用微信开发者工具联调小程序最后聊验收避坑和答辩前值得做的小改造。按顺序走一遍半天能把整个系统摸透。2. 系统架构与数据模型选课系统的核心链路和表设计2.1 角色与业务链路学生、教师、管理员三方如何协作拿到一个毕业设计源码包我习惯先看三样东西README 里的启动说明、数据库脚本、小程序页面路由。这套学生选课系统本质上是“角色 资源 动作”的模型三张核心表对应三类角色学生用小程序的登录页进系统浏览课程列表执行选课和退课查自己的课表教师和管理员通常走 Web 管理后台负责发布课程、查看选课名单、录入成绩、维护账号。小程序端只是用户侧真正的业务重心在后端 Service 层。拿“选课”这个核心动作来说它不是往select_course表里插一条记录那么简单。一次完整的选课至少要经过四步判断课程是否存在、是否到了选课时间、学生有没有选过这门课、课程容量还有没有余量。这四步少一步都会在演示和论文评阅时被老师问住。很多源码包的问题恰恰在于前三步写全了唯独“容量余量”用的是“先查再插”的写法并发一上来就会超卖这个我在第 5 章会专门展开。再从数据流角度看一遍小程序发请求 → 后端 Controller 收参 → Service 做业务校验 → Mapper 操作数据库 → 结果一层层返回 → 小程序 setData 渲染。这条链路里最容易出问题的不是某个技术点而是前后端字段名对不上。比如后端返回的 JSON 字段是courseName小程序端 wxml 里写的是cname页面就白屏。所以看源码时我的建议是先画一张角色、功能、接口的对照表再动手改代码能省掉后面一大半联调时间。2.2 数据库表设计从学生表到选课表的字段与约束这类选题的库表一般不会太复杂。核心是学生表、课程表、选课记录表再配一张教师表或管理员表。先看应用最广的建表脚本-- 学生表 CREATE TABLE student ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, sno VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(64) NOT NULL COMMENT 密码(MD5或BCrypt), major VARCHAR(50) DEFAULT NULL COMMENT 专业, grade VARCHAR(10) DEFAULT NULL COMMENT 年级, PRIMARY KEY (id), UNIQUE KEY uk_sno (sno) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生信息表; -- 课程表 CREATE TABLE course ( id INT NOT NULL AUTO_INCREMENT, cno VARCHAR(20) NOT NULL COMMENT 课程编号, cname VARCHAR(100) NOT NULL COMMENT 课程名称, teacher VARCHAR(50) NOT NULL COMMENT 授课教师, credit DECIMAL(3,1) DEFAULT 2.0 COMMENT 学分, capacity INT DEFAULT 60 COMMENT 课程容量, selected INT DEFAULT 0 COMMENT 已选人数, time_slot VARCHAR(50) DEFAULT NULL COMMENT 上课时间如 周一3-4节, PRIMARY KEY (id), UNIQUE KEY uk_cno (cno) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程表; -- 选课记录表 CREATE TABLE select_course ( id INT NOT NULL AUTO_INCREMENT, student_id INT NOT NULL COMMENT 学生ID(关联student.id), course_id INT NOT NULL COMMENT 课程ID(关联course.id), create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, PRIMARY KEY (id), UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课记录表;几个答辩时容易被追问的细节第一为什么不直接用capacity - selected算余量而要单独存一个selected字段。因为退课时要回滚人数如果用 count 去查选课记录表再减一高频操作下会有锁竞争和性能问题直接在course表维护一个计数器配合 SQL 条件更新天然能挡住超卖代码也简单。第二select_course表上必须加UNIQUE KEY uk_student_course (student_id, course_id)。这是数据库层面的最后一道防线哪怕后端 Service 逻辑写漏了重复插入也会直接抛 DuplicateKeyException而不是生成两条脏数据。很多源码包默认只有主键拿到手第一件事就该把这条索引加上。第三表字段用sno、cno做唯一键而不是直接用姓名或课程名。因为姓名课程名可能重名编号不会学生登录用sno而不是 id也方便老师在管理端按学号检索。2.3 数据字典与小程序页面的映射关系建完表再对照小程序端页面把字段确认一遍。常见对应关系是登录页用student.snopassword调/user/login首页课程列表返回course表整行数据小程序端展示cname、teacher、credit和剩余容量“我的课表”页面查select_course关联course返回当前学生的已选课程列表退课操作则是删除select_course记录并把course.selected减一。这里有个大多数教程不会提的坑如果源码包用的是 MyBatis-Plus实体类字段和表字段通过map-underscore-to-camel-case自动映射。但当表里有time_slot这种下划线字段后端返回 JSON 时键名到底是timeSlot还是time_slot由实体上的注解和 Jackson 配置决定不是前端说了算。建议先用 Apifox 看一眼真实返回再写页面绑定不要凭猜。这一步定了后面联调会非常顺。3. 用 IDEA 跑通 Java 后端导入源码、改配置、初始化数据3.1 导入项目前的三分钟检查拿到源码包后别急着双击 pom.xml先确认三件事免得 IDEA 里飘红一片。第一JDK 版本。这类毕业设计源码绝大多数基于 Spring Boot 2.xJDK 8 或 11 最稳如果你本机是 JDK 17 甚至 21容易遇到依赖不兼容的玄学问题。第二Maven 仓库。检查 settings.xml 里有没有配置阿里云镜像否则依赖下载可能卡半小时。第三Lombok 插件。源码里大量用Data、Slf4j没装插件直接报找不到 getter/setter。我一般在命令行先确认环境java -version mvn -v echo %JAVA_HOME% # Windows echo $JAVA_HOME # Linux / macOS如果本机还没装 MySQL按常见做法装一个 8.0 版本root 密码记好后面所有配置都用它。装完后打开 IDEAFile → Open 选中解压出来的后端目录等 Maven 把依赖拉完。如果右下角提示未配置 SDK在 Project Structure 里把 Project SDK 切到本机 JDKLanguage Level 选 8 或 11。这一步不做编译阶段就会翻车而且报错信息还不直白。3.2 修改数据源配置并用初始化脚本建库依赖拉完之后找到src/main/resources/application.yml或application.properties。Spring Boot 项目一般用 yml选课系统最核心的配置是数据源长这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/course_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted几个参数说明characterEncodingutf8保证写入数据库的中文不乱码serverTimezoneAsia/Shanghai解决时区差 8 小时allowPublicKeyRetrievaltrue是连接 MySQL 8.0 时常见报错的开关不开会抛Public Key Retrieval is not allowed。logic-delete-field是 MyBatis-Plus 的逻辑删除配置如果你的表里有deleted字段就保留没有就删掉否则查询会自动拼上WHERE deleted 0查不到数据还以为自己写错了 SQL。改好配置后先建库再导表。可以用 Navicat、Workbench也可以直接用命令行mysql -uroot -p进入 MySQL 后执行CREATE DATABASE IF NOT EXISTS course_system DEFAULT CHARSET utf8mb4; USE course_system;然后把源码包里的 SQL 文件导入。如果包里没带初始化脚本就把第 2 章那段建表 SQL 粘贴执行再手动插几条课程数据INSERT INTO course (cno, cname, teacher, credit, capacity, time_slot) VALUES (C001, Java程序设计, 张老师, 3.0, 60, 周一3-4节), (C002, 数据库原理, 李老师, 2.5, 50, 周三1-2节), (C003, 微信小程序开发, 王老师, 2.0, 40, 周五5-6节);插完这三条课程列表接口至少能返回数据后端登录也才能有测试账号可用。很多同学卡在这一步是因为 SQL 脚本里的库名和 yml 配置里的库名不一致导完数据发现查不到先检查库名是否匹配。3.3 启动项目并按接口清单自测后端入口是src/main/java下带SpringBootApplication注解的启动类右键 Run。看到日志里出现Started Application in xx seconds且没有红色异常就说明起来了。我习惯再确认一眼端口IDEA 控制台通常会打印Tomcat started on port(s): 8080。接下来用 Apifox 或 Postman 按接口清单过一遍方法路径作用POST/api/user/login学号加密码登录返回 tokenGET/api/course/list获取课程列表POST/api/course/select选课body 传 studentId、courseIdPOST/api/course/cancel退课GET/api/course/my?studentId1我的课表先测登录拿到 token 后选课接口在 Header 里带上Authorization: token值。如果后端用过滤器统一校验 token不带这个头会返回 401。这里最容易出问题的是选课接口的入参有的源码包用RequestBody接收 JSON有的用RequestParam接收表单参数Body 格式选错会报 400。遇到不确定时直接看 Controller 方法签名签名怎么写的工具里就怎么发。Service 层核心逻辑是整个系统最值得读的部分通常长这样Transactional(rollbackFor Exception.class) public Result selectCourse(Integer studentId, Integer courseId) { Course course courseMapper.selectById(courseId); if (course null) { throw new BizException(课程不存在); } if (course.getSelected() course.getCapacity()) { throw new BizException(课程已选满); } Long count selectCourseMapper.selectCount( new LambdaQueryWrapperSelectCourse() .eq(SelectCourse::getStudentId, studentId) .eq(SelectCourse::getCourseId, courseId)); if (count 0) { throw new BizException(不能重复选课); } selectCourseMapper.insert(new SelectCourse(studentId, courseId)); int updated courseMapper.increaseSelected(courseId); if (updated 0) { throw new BizException(选课失败请重试); } return Result.ok(); }这段代码里有两个关键细节。第一Transactional(rollbackFor Exception.class)Spring 事务默认只对 RuntimeException 回滚如果你的业务异常类继承的是 Exception不声明 rollbackFor就会出现“报错了但数据写了一半”的尴尬局面。第二increaseSelected对应的 SQL 必须是条件更新不能是普通 update否则超卖问题依旧存在。对应的 Mapper 写法是Update(UPDATE course SET selected selected 1 WHERE id #{courseId} AND selected capacity) int increaseSelected(Param(courseId) Integer courseId);这条 SQL 把“检查余量”和“更新余量”合并成一个原子操作数据库层面保证同一时刻只有一个请求能成功自增。我遇到过源码包里只写UPDATE course SET selected selected 1 WHERE id #{courseId}的并发一打就超卖把这一行改掉才修好。4. 微信小程序端联调从开发者工具打开到真机预览4.1 小程序项目结构与页面路由后端跑通后打开微信开发者工具导入源码包里的小程序目录。这个过程先分清工程类型根目录有app.json的是原生小程序直接导入如果走的是 uni-app需要先在 HBuilderX 里打开编译一遍再运行到微信开发者工具。两种项目结构差别很大看错方向会白折腾半天。原生小程序的核心文件是app.json它决定页面路由和底部导航。一个典型的选课系统路由配置长这样{ pages: [ pages/login/login, pages/index/index, pages/course/course, pages/course/detail, pages/mine/mine ], window: { navigationBarTitleText: 学生选课系统, navigationBarBackgroundColor: #4A90D9, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/course/course, text: 选课 }, { pagePath: pages/mine/mine, text: 我的 } ] } }pages数组第一项是启动页这里把登录页放第一位配合 onLaunch 里的登录态判断跳转。tabBar 的页面必须是pages数组里存在的路径写错一个单词开发者工具编译不报错但底部导航就少一项需要留意。另外不同机型顶部导航栏高度不一致适配时不要写死成固定 px可以用wx.getSystemInfoSync().statusBarHeight动态计算否则全面屏上标题会顶到状态栏。4.2 封装 wx.request 并处理登录态所有页面都要发请求第一件事是封装公共请求函数。很多源码包在页面里直接写wx.request联调时改后端地址要把每个页面改一遍很痛。我一般在utils/request.js里统一封装const BASE_URL http://192.168.1.100:8080/api; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 200) { resolve(res.data); } else if (res.statusCode 401) { wx.removeStorageSync(token); wx.reLaunch({ url: /pages/login/login }); } else { reject(res); } }, fail(err) { reject(err); } }); }); } module.exports { request, BASE_URL };关键参数说明BASE_URL别写http://127.0.0.1:8080/api。小程序跑在手机或开发者工具里127.0.0.1 指的是手机自己不是你的电脑。开发阶段建议填电脑的局域网 IP比如http://192.168.1.100:8080/api。Content-Type用application/json时data 自动序列化为 JSON后端要用RequestBody接如果后端是RequestParam这里得改application/x-www-form-urlencoded否则参数传不过去。这是非常隐蔽的联调问题前端报 400 时先查这里。登录页的代码逻辑也比较固定Page({ data: { sno: , password: }, onSnoInput(e) { this.setData({ sno: e.detail.value }); }, onPasswordInput(e) { this.setData({ password: e.detail.value }); }, async onLogin() { const { sno, password } this.data; if (!sno || !password) { wx.showToast({ title: 请输入学号和密码, icon: none }); return; } const res await request(/user/login, POST, { sno, password }); if (res.code 200) { wx.setStorageSync(token, res.data.token); wx.setStorageSync(studentId, res.data.studentId); wx.switchTab({ url: /pages/index/index }); } else { wx.showToast({ title: res.msg || 登录失败, icon: none }); } } });注意wx.switchTab只能跳 tabBar 页面。index 注册在 tabBar 里所以用 switchTab如果目标页不是 tabBar 页得用wx.navigateTo或wx.redirectTo搞混了页面跳不过去控制台也不报错是个容易绕晕的点。4.3 选课页面的余量刷新与防重复点击课程列表页是系统的门面通常在onLoad和onPullDownRefresh里各拉一次课程列表。选课按钮的处理要防两件事重复点击和余量显示过时。参考实现Page({ data: { courseList: [], selecting: false }, async onLoad() { this.loadCourses(); }, async loadCourses() { const res await request(/course/list, GET); if (res.code 200) { this.setData({ courseList: res.data.records }); } }, async onSelect(e) { if (this.data.selecting) return; this.setData({ selecting: true }); const { id } e.currentTarget.dataset; const studentId wx.getStorageSync(studentId); wx.showLoading({ title: 正在选课... }); try { const res await request(/course/select, POST, { studentId, courseId: id }); if (res.code 200) { wx.showToast({ title: 选课成功, icon: success }); this.loadCourses(); } else { wx.showToast({ title: res.msg || 选课失败, icon: none }); } } finally { wx.hideLoading(); this.setData({ selecting: false }); } } });this.data.selecting是全局开关防止接口没返回时疯狂点按钮。但要强调前端防重复点击只是体验优化真正的防重必须靠后端唯一索引和条件更新。我实际调试时遇到过前端加了开关、并发下仍然跑出多条记录的情况原因就是后端接口没做幂等这种问题光靠前端永远堵不住。5. 部署与联调避坑连不上后端、超卖、乱码的 5 个真实翻车点5.1 启动阶段JDK 版本不匹配和 MySQL 8 连接参数现象一IDEA 运行启动类控制台抛java.lang.IllegalStateException或BeanCreationException日志里还带cannot resolve symbol Data之类的提示。原因通常是两类JDK 版本和 Spring Boot 版本对不上或者 Lombok 插件没装。解决Java 8 的机器优先把 Spring Boot 版本控制在 2.3.x 以下IDEA 装 Lombok 插件并在 Settings → Build → Compiler → Annotation Processors 里勾选 Enable annotation processing。改完重启再 Maven clean 一次基本能消停。现象二点启动后报Public Key Retrieval is not allowed或者Access denied for user rootlocalhost。前者是 MySQL 8.0 驱动连接规则变化导致的连接串加上allowPublicKeyRetrievaltrueuseSSLfalse就能解决后者就是账号密码错误去 application.yml 核对不要怀疑别的。还有个容易忽略的点如果 SQL 文件里带了CREATE DATABASE course_system而你的实例里已经存在同名空库导入会报 Database already exists导致后面的表没建进去。手动删掉原库重新导入即可。5.2 联调阶段真机预览连不上本机后端现象微信开发者工具里接口请求正常一到手机上真机预览所有请求全部request:fail或一直转圈。原因有三个按出现频率排第一BASE_URL 写的是 127.0.0.1手机访问的是自己第二电脑防火墙拦了后端端口第三小程序正式环境要求接口域名必须是备案过的 HTTPS开发阶段没开“不校验合法域名”开关。解决BASE_URL 改成电脑局域网 IPWindows 用ipconfig、macOS 用ifconfig查地址手机和电脑连同一个 Wi-FiWindows 防火墙入站规则里允许后端端口或临时关掉防火墙测试微信开发者工具右上角“详情 → 本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。如果急着现场演示也可以用“真机调试”功能不走合法域名限制但要求开发者账号处于登录状态。5.3 业务阶段重复选课与超卖问题的排查现象同一个学生连续点选课数据库里出现两条相同选课记录或者课程已选人数超过容量。原因前端按钮只做了 loading 拦截网络慢时两个请求先后到达后端后端 Service 用了“先查余量再 update”的写法没有事务兜底也没有唯一索引。第 2 章强调的唯一索引就是后悔药只要索引在重复插入直接抛 DuplicateKeyException至少不会产生脏数据。解决分三步第一步确认select_course表上有联合唯一索引第二步把 Service 选课方法加上Transactional(rollbackFor Exception.class)第三步把“查余量加自增”改成条件更新 SQL。三步做完用 Postman 同时发五个请求打选课接口最终selected不会超过capacity。这个结果本身就是答辩现场能直接演示的亮点。5.4 展示阶段中文乱码和时区错乱现象小程序端课程名称显示??或者后端返回的选课时间比实际多了 8 小时。原因表字符集不是 utf8mb4、连接串没指定characterEncodingutf8或者时区参数没配置。这类问题在教室投影演示时尤其尴尬调起来又很玄学。解决建表语句统一用DEFAULT CHARSETutf8mb4连接串带上characterEncodingutf8serverTimezoneAsia/Shanghai后端实体时间字段加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)。如果历史数据已经乱码先执行ALTER TABLE course CONVERT TO CHARACTER SET utf8mb4;再重新插入数据。6. 动手改点源码再上场并发验证、Excel 导出和答辩的展示技巧6.1 用并发请求验证选课接口的可靠性答辩前最值得做的一件事是给选课接口发一轮并发请求把“没有超卖”当成可展示的测试结论。具体做法Postman 里建一个选课请求Runner 设置迭代 50 次、并发 10跑完查course.selected字段并核对select_course表里是否只有一条记录。没做第 5 章修复前这一步大概率能复现超卖修复后再跑结论就变成了“系统在高并发下不会超卖”写进论文测试章节很有分量。不用 Postman 时一段简单的 JS 脚本也能模拟const requests []; for (let i 0; i 50; i) { requests.push( fetch(http://localhost:8080/api/course/select, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ studentId: 1, courseId: 1 }) }).then(res res.json()) ); } Promise.allSettled(requests).then(results { const ok results.filter(r r.status fulfilled); console.log(成功请求数:, ok.length); });注意要固定同一个 studentId 和 courseId最终理想结果是成功数远低于 50且第二遍再跑会提示“不能重复选课”。这个现象本身就证明了防重逻辑生效。6.2 给管理端加一个 Excel 导出接口源码包的管理端如果只有查看功能加分最简单的方向是加一个导出选课名单。用 Apache POI 或 EasyExcel 都行毕设体量用 POI 足够。Controller 层加接口设置响应头Content-Disposition为附件EasyExcel.write(response.getOutputStream())直接写出。核心代码十行左右但展示时“管理端一键导出选课名单 Excel”比任何话术都直观答辩老师看到的是你考虑了真实使用场景。6.3 答辩展示的节奏与心态验收演示我建议按这个顺序走打开数据库展示三张表和核心数据说明字段为什么这么设计启动后端用 Apifox 演示登录和选课接口的返回 JSON最后操作小程序端完成一次完整的选课和退课。中途故意快速点两次选课按钮让系统提示“不能重复选课”这一下就把前面的技术准备全串起来了。我自己第一次演示踩过的坑是现场教室网络不稳定手机连不上电脑的局域网后来养成的习惯是提前在开发者工具里录好一遍操作真机不行就放录制画面两套方案都备着心里不慌。希望帮到你。本文还有配套的精品资源点击获取
返回列表