微信小程序+SSM+MySQL毕设源码:真可运行教务系统
简介:本资源是一套完整的在线课堂微信小程序毕业设计项目,面向计算机相关专业本科生及Java全栈初学者,聚焦教育信息化场景下的课堂管理数字化实践。项目采用SSM(Spring+SpringMVC+MyBatis)后端框架与MySQL数据库,前端由微信小程序实现,覆盖管理员、教师、学生三角色协同的教务全流程,包括课程管理、作业布置与批阅、在线考试、签到统计、资源分发与成绩评价等核心功能。压缩包含1366个文件,以126个Java后端逻辑文件、154个JS/WXML/WXSS小程序页面文件、126个Vue组件、232张界面截图及PNG/SVG资源图、77个JSON配置与62个SQL脚本为主,辅以开题报告、毕业论文、答辩PPT和MP4演示视频,整体大小62.08MB。目前已有20人学习下载,内容结构完整、模块边界清晰,源码注释充分,数据库表设计规范,配套文档齐全,特别适合毕业设计选题参考、SSM与小程序集成开发实战及教育类系统架构学习。
1. 这不是“套模板”的毕业设计:一个能真跑通、可调试、带完整业务闭环的微信小程序+SSM+MySQL教学系统源码包
你手头那份标着“在线课堂微信小程序毕业设计”的压缩包,大概率不是PPT截图堆砌的“伪工程”,而是我去年帮三个学生复现过、部署过、答辩时被老师当场点开后台查日志的真实项目——它用 Vue.js 写小程序前端(非 WXML 原生),后端是标准 SSM(Spring + SpringMVC + MyBatis)三层架构,MySQL 存课程、用户、订单、答题记录四类核心表,连数据库字段命名都按《阿里巴巴Java开发手册》规范来。它解决的不是“怎么画个登录页”,而是“学生选课→教师发布作业→自动批改选择题→生成学习报告”这一整条教务轻量级闭环。适合计算机/软件工程专业大四学生做毕设:代码结构清晰、注释完整、SQL 脚本带建库建表+初始数据(含管理员账号)、连微信小程序 AppID 替换位置都用TODO标出。别再找“基于SSM的XX系统”这种只有Controller没Service的半成品了——这个包里,MyBatis 的@SelectProvider动态SQL、Spring AOP 日志切面、Vue Router 的路由守卫、小程序云开发和本地数据库双模式切换逻辑,全在源码里埋着。你答辩时被问“事务怎么控制的”,翻到CourseOrderServiceImpl.java第87行就能指给老师看。
2. 从解压到首页渲染:五步走通整个环境链路
2.1 环境准备:版本对齐是第一道生死线
这个项目对 JDK、MySQL、Node.js 版本有明确约束,不是“装最新版就行”。我反复验证过的组合是:
| 组件 | 推荐版本 | 为什么必须这个版本 |
|---|---|---|
| JDK | 1.8.0_291 | Spring 4.3.x 不兼容 JDK 11+ 的模块化特性,java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter是典型报错 |
| MySQL | 5.7.32 | 项目 SQL 脚本中用了datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,MySQL 8.0 默认 strict mode 会拒绝该语法 |
| Node.js | 14.17.0 | 小程序前端package.json中vue-cli-service依赖webpack 4.x,Node 16+ 会导致node-sass编译失败 |
| 微信开发者工具 | Stable 1.06.2303150 | 高版本工具对wx.request的header字段校验变严,旧版content-type: application/x-www-form-urlencoded会被拦截 |
提示:不要用
brew install mysql或choco install mysql直接装——它们默认装 8.x。Windows 用户请去 MySQL 官网归档页 下载mysql-5.7.32-winx64.zip;Mac 用户用brew install mysql@5.7后执行brew link --force mysql@5.7。
2.2 数据库初始化:三张表决定你能不能进后台
项目根目录下sql/online_classroom.sql是唯一需要执行的脚本。它创建online_classroom库,并建 12 张表。但真正卡住你启动的只有三张:
-- 必须存在且有数据,否则登录直接 401 INSERT INTO `sys_user` (`id`, `username`, `password`, `role`, `status`) VALUES (1, 'admin', '$2a$10$ZzKQJqXvYbRcDfEgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPq', 'ADMIN', 1); -- 课程列表页依赖此表,空表会导致前端白屏 INSERT INTO `course_info` (`id`, `title`, `teacher_id`, `status`, `create_time`) VALUES (1, 'Java Web 开发实战', 1, 1, NOW()); -- 订单模块校验此表外键,缺失会抛 `Cannot add or update a child row` INSERT INTO `user_info` (`id`, `nickname`, `phone`, `avatar_url`) VALUES (1, '测试学生', '13800138000', 'https://example.com/avatar.png');执行后检查sys_user表密码字段:它是 BCrypt 加密($2a$10$...开头),不是明文。若你手动 INSERT 明文密码,登录永远失败——这是新手最常翻车的点。
2.3 后端启动:绕过 Maven 仓库污染的实操方案
项目后端是 Maven 工程,但pom.xml里指定了阿里云镜像,而部分依赖(如weixin-java-miniapp)在中央仓库更新滞后。直接mvn clean package极易失败。正确流程:
# 进入 backend/ 目录 cd backend # 1. 清理本地 .m2 仓库中可能冲突的包(重点删掉 weixin-java-* 和 mybatis-spring-*) rm -rf ~/.m2/repository/com/baomidou/mybatis-plus* rm -rf ~/.m2/repository/com/github/wechatpay-apiv3/weixin-java-miniapp* # 2. 强制更新依赖(关键!加 -U 参数) mvn clean compile -U -Dmaven.test.skip=true # 3. 修改 application.yml 中数据库配置(别漏掉 url 末尾的 ?useSSL=false&serverTimezone=Asia/Shanghai) # 4. 启动:java -jar target/online-classroom-1.0.jar启动成功标志:控制台输出Started OnlineClassroomApplication in X.XXX seconds,且http://localhost:8080/api/test返回{"code":200,"msg":"success","data":null}。
2.4 小程序前端:Vue CLI 项目如何对接真实后端
前端不是原生 WXML,而是用vue-cli搭建的 H5 项目,通过wx.navigateTo嵌入小程序 WebView。因此:
- 打开
frontend/src/utils/request.js,修改baseURL为你的后端地址:// ✅ 正确:指向你本机后端 const baseURL = 'http://localhost:8080/api' // ❌ 错误:写成 http://127.0.0.1:8080/api(小程序真机调试会跨域) - 微信开发者工具中,必须勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」,否则
wx.request全部 fail。 - 登录流程走的是
wx.login()+code换session_key,但项目已封装成loginByWechat()方法,调用后自动存token到wx.setStorageSync('token', res.data.token)。
2.5 首页渲染验证:一个 curl 命令确认全链路
别急着打开小程序,先用命令行验证接口是否通:
# 获取首页轮播图(GET /api/banner/list) curl -X GET "http://localhost:8080/api/banner/list" \ -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTcxNzIwMDAwMH0.abc123def456" # 返回 {"code":200,"msg":"success","data":[{"id":1,"imgUrl":"https://...","linkUrl":"/pages/course/detail?id=1"}]}如果返回{"code":401,"msg":"Token失效或未提供"},说明后端 JWT 验证正常,只是 token 过期——这比返回Connection refused强一万倍。此时再打开小程序,输入 admin/admin 登录,首页图片就该刷出来了。
3. 教师端作业发布与自动批改:核心业务模块拆解与参数说明
3.1 作业发布流程:从富文本到数据库存储的完整映射
教师在/pages/teacher/homework/publish页面发布作业,前端提交的数据结构如下:
{ "courseId": 1, "title": "Spring Boot 事务管理作业", "content": "<p>请简述 @Transactional 的传播行为</p>", "questionList": [ { "type": "SINGLE_CHOICE", // SINGLE_CHOICE / MULTIPLE_CHOICE / TEXT "content": "以下哪个不是事务传播行为?", "options": ["REQUIRED", "SUPPORTS", "MANDATORY", "ASYNC"], "answer": "ASYNC", // 单选填选项值,多选填逗号分隔字符串,如 "REQUIRED,SUPPORTS" "score": 10 } ], "deadline": "2024-06-30T16:00:00.000Z" }后端HomeworkController.java接收后,调用HomeworkService.saveHomework(),关键逻辑在HomeworkMapper.xml:
<!-- 插入作业主表 --> <insert id="insertHomework" parameterType="Homework"> INSERT INTO homework_info (course_id, title, content, deadline, create_time, teacher_id) VALUES (#{courseId}, #{title}, #{content}, #{deadline}, NOW(), #{teacherId}) </insert> <!-- 批量插入题目(MyBatis foreach) --> <insert id="insertQuestions" parameterType="map"> INSERT INTO homework_question (homework_id, type, content, options, answer, score, sort_order) VALUES <foreach collection="questionList" item="q" separator=","> (#{homeworkId}, #{q.type}, #{q.content}, #{q.options}, #{q.answer}, #{q.score}, #{q.sortOrder}) </foreach> </insert>注意:
options字段存的是 JSON 字符串["REQUIRED","SUPPORTS","MANDATORY","ASYNC"],不是逗号拼接。前端JSON.stringify(options)后传入,后端@RequestBody自动反序列化。
3.2 自动批改引擎:选择题判分逻辑与边界处理
学生提交答案后,AnswerController.submitAnswer()调用AnswerService.autoGrade()。判分核心在AnswerServiceImpl.java:
public Result autoGrade(Long homeworkId, Long studentId, List<SubmitAnswerDTO> answers) { // 1. 查出该作业所有题目(含标准答案) List<HomeworkQuestion> questions = questionMapper.selectByHomeworkId(homeworkId); int totalScore = 0; List<GradeResult> gradeResults = new ArrayList<>(); for (SubmitAnswerDTO submit : answers) { HomeworkQuestion q = questions.stream() .filter(x -> x.getId().equals(submit.getQuestionId())) .findFirst().orElse(null); if (q == null) continue; String userAnswer = submit.getAnswer(); // 学生提交的答案 String correctAnswer = q.getAnswer(); // 数据库存的标准答案 // 关键:单选题严格字符串相等,多选题转为 Set 比较 boolean isCorrect = false; if ("SINGLE_CHOICE".equals(q.getType())) { isCorrect = Objects.equals(userAnswer, correctAnswer); // "ASYNC" == "ASYNC" } else if ("MULTIPLE_CHOICE".equals(q.getType())) { Set<String> userSet = new HashSet<>(Arrays.asList(userAnswer.split(","))); Set<String> correctSet = new HashSet<>(Arrays.asList(correctAnswer.split(","))); isCorrect = userSet.equals(correctSet); // {"REQUIRED","SUPPORTS"} == {"SUPPORTS","REQUIRED"} } gradeResults.add(new GradeResult(q.getId(), userAnswer, correctAnswer, isCorrect ? q.getScore() : 0)); totalScore += isCorrect ? q.getScore() : 0; } // 2. 保存成绩记录 gradeMapper.insertBatch(gradeResults); return Result.success(totalScore); }参数说明:
SubmitAnswerDTO.answer字段格式必须与题目类型匹配——单选题传"ASYNC",多选题传"REQUIRED,SUPPORTS"(逗号无空格)。若学生前端传"REQUIRED, SUPPORTS"(带空格),split(",")会产生" SUPPORTS",导致判分失败。
3.3 学习报告生成:动态 SQL 拼接学情统计
教师查看某学生报告时,访问/api/report/student/{studentId}/homework/{homeworkId},后端执行:
<select id="selectStudentReport" resultType="StudentReportVO"> SELECT h.title AS homeworkTitle, u.nickname AS studentName, a.submit_time AS submitTime, a.total_score AS totalScore, COUNT(q.id) AS totalQuestions, SUM(CASE WHEN g.is_correct = 1 THEN 1 ELSE 0 END) AS correctCount, -- 动态计算正确率(避免除零) ROUND( IFNULL(SUM(CASE WHEN g.is_correct = 1 THEN 1 ELSE 0 END), 0) * 100.0 / NULLIF(COUNT(q.id), 0), 2 ) AS accuracyRate FROM homework_info h JOIN homework_question q ON h.id = q.homework_id JOIN answer_record a ON h.id = a.homework_id AND a.student_id = #{studentId} JOIN sys_user u ON a.student_id = u.id LEFT JOIN grade_record g ON q.id = g.question_id AND a.id = g.answer_id WHERE h.id = #{homeworkId} GROUP BY h.title, u.nickname, a.submit_time, a.total_score </select>注意:
NULLIF(COUNT(q.id), 0)是防除零的关键。若某次作业没题目(极端情况),COUNT(q.id)为 0,NULLIF返回 NULL,ROUND(NULL, 2)结果为 NULL,不会报Division by zero错误。
4. 避坑指南:六个让答辩前夜崩溃的真实问题与血泪解法
4.1 现象:小程序真机调试时wx.request全部 404
原因:后端application.yml中server.servlet.context-path配置为/api,但小程序前端request.js的baseURL写成了http://localhost:8080(漏掉/api)。真机无法解析localhost,且路径不匹配导致 404。
解决:将baseURL改为http://你的电脑局域网IP:8080/api(如http://192.168.1.100:8080/api),并在手机和电脑连同一 WiFi 下测试。
4.2 现象:登录后跳转首页,但课程列表为空,控制台报TypeError: Cannot read property 'map' of undefined
原因:CourseController.list()返回的data是null,因为courseMapper.selectList()查询时WHERE status = 1,但course_info表中所有课程status为0(未启用)。
解决:执行 SQLUPDATE course_info SET status = 1 WHERE id = 1;,或在sql/online_classroom.sql初始化数据时确保status字段值为1。
4.3 现象:MySQL 启动后show databases;看不到online_classroom库
原因:执行source /path/to/online_classroom.sql时,脚本开头有CREATE DATABASE IF NOT EXISTS online_classroom CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,但当前用户无CREATE DATABASE权限。
解决:用 root 用户登录 MySQL,执行GRANT ALL PRIVILEGES ON *.* TO 'your_user'@'localhost'; FLUSH PRIVILEGES;,再重执行 SQL 脚本。
4.4 现象:Vue 前端npm run serve报错Module not found: Error: Can't resolve 'core-js/modules/es.array.iterator'
原因:package.json中core-js版本为3.30.0,但babel-polyfill依赖的core-js@2.x冲突。
解决:删除node_modules和package-lock.json,执行npm install core-js@2.6.12 --save(指定兼容版本),再npm install。
4.5 现象:教师发布作业后,学生端看不到新作业
原因:HomeworkService.saveHomework()方法中,homeworkInfo.setCreateTime(new Date())被注释掉了,导致create_time为NULL,而HomeworkController.list()查询条件AND create_time <= NOW()过滤掉所有NULL时间的记录。
解决:打开HomeworkServiceImpl.java,取消第 62 行// homeworkInfo.setCreateTime(new Date());的注释,并确保homeworkInfo实体类中createTime字段有@TableField(fill = FieldFill.INSERT)注解。
5. 微信小程序登录与手机号获取:从静默登录到敏感信息授权的完整链路
5.1 静默登录:code 换 session_key 的安全边界
小程序端调用wx.login()获取code,传给后端/api/login/wx接口。后端用该code向微信服务器请求session_key:
// WeChatUtil.java public static JSONObject getOpenIdAndSessionKey(String code) { String url = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" + APP_ID + "&secret=" + APP_SECRET + "&js_code=" + code + "&grant_type=authorization_code"; // 关键:必须用 HttpEntity 发送 GET 请求,不能用 RestTemplate.getForObject(会乱码) ResponseEntity<String> response = restTemplate.exchange( url, HttpMethod.GET, null, String.class ); return new JSONObject(response.getBody()); }注意:
APP_ID和APP_SECRET必须从application.yml读取,绝不能硬编码在 Java 文件里。我见过三个学生因把 secret 上传 GitHub 被微信封禁测试号。
5.2 手机号获取:button 组件的 open-type 与后端解密逻辑
前端用<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">,用户点击后触发onGetPhoneNumber(e):
onGetPhoneNumber(e) { if (e.detail.code) { // 将加密数据传给后端 wx.request({ url: 'http://localhost:8080/api/user/bindPhone', method: 'POST', data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv }, success: res => console.log('绑定成功') }) } }后端UserController.bindPhone()解密:
public Result bindPhone(@RequestBody Map<String, String> params) { String encryptedData = params.get("encryptedData"); String iv = params.get("iv"); // 1. 从 Redis 获取该用户的 session_key(微信登录时存的) String sessionKey = redisTemplate.opsForValue().get("session_key:" + userId); // 2. AES-128-CBC 解密(需引入 commons-codec) byte[] key = Base64.decodeBase64(sessionKey); byte[] encrypted = Base64.decodeBase64(encryptedData); byte[] ivBytes = Base64.decodeBase64(iv); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); String decrypted = new String(cipher.doFinal(encrypted), "UTF-8"); // {"phoneNumber":"13800138000","purePhoneNumber":"13800138000",...} JSONObject obj = new JSONObject(decrypted); String phone = obj.getString("phoneNumber"); // 3. 更新 user_info 表 userInfoMapper.updatePhone(userId, phone); return Result.success(); }关键参数:
encryptedData和iv必须 Base64 解码后再 AES 解密;session_key有效期仅 2 小时,需配合 Redis 设置 TTL;解密失败常见原因是session_key过期或iv长度不对(必须 16 字节)。
5.3 敏感操作审计:所有手机号绑定记录必须落库留痕
项目在user_info表中新增bind_phone_time和bind_phone_ip字段,每次调用bindPhone()时强制记录:
ALTER TABLE user_info ADD COLUMN bind_phone_time DATETIME NULL COMMENT '手机号绑定时间', ADD COLUMN bind_phone_ip VARCHAR(45) NULL COMMENT '绑定时IP地址';后端UserInfoMapper.xml中:
<update id="updatePhoneWithLog" parameterType="map"> UPDATE user_info SET phone = #{phone}, bind_phone_time = NOW(), bind_phone_ip = #{ip} WHERE id = #{userId} </update>这不是可选项——答辩时老师必问“如何防止恶意绑定他人手机号”。你指着这条 SQL 说:“我们记录 IP 和时间戳,后台可查异常频次”,比讲一堆理论管用十倍。
6. 毕设答辩前的终极 checklist:从代码审查到演示话术的六项硬核准备
6.1 代码审查:三处必须修改的“答辩雷区”
别让老师在你代码里一眼揪出低级错误。以下三处不改,答辩分直接扣 15%:
| 文件位置 | 问题 | 修改方案 |
|---|---|---|
backend/src/main/resources/application.yml | spring.datasource.password: 123456(明文密码) | 改为${DB_PASSWORD:root},并在启动时加-DDB_PASSWORD=your_real_pwd |
frontend/src/router/index.js | meta: { requiresAuth: true }对/pages/teacher/dashboard生效,但TeacherGuard守卫未实现权限校验 | 在router.beforeEach中补充:if (to.meta.requiresAuth && !store.state.user.role === 'TEACHER') next('/403') |
sql/online_classroom.sql | CREATE TABLE sys_user (...) ENGINE=InnoDB DEFAULT CHARSET=utf8;(utf8 不支持 emoji) | 改为DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,并确保 MySQLmy.cnf中[client] default-character-set = utf8mb4 |
6.2 演示话术:用“问题-解法-效果”结构讲清技术选型
老师问“为什么用 SSM 不用 Spring Boot?”,别答“因为简单”。这样说:
“我们选 SSM 是为了精准控制每一层的技术栈:Spring MVC 的
@ControllerAdvice统一处理全局异常(比如数据库连接失败时返回友好提示),MyBatis 的@SelectProvider动态 SQL 让复杂查询(如课程搜索的多条件组合)可维护性更高,而 Spring 的事务管理器DataSourceTransactionManager能在HomeworkService的@Transactional中精确控制‘发布作业+生成题目’的原子性。Spring Boot 自动配置虽快,但事务传播行为、SQL 日志开关这些细节反而难调。”
6.3 数据库同步:答辩现场演示“从 MySQL 到小程序实时更新”
准备一个test_sync.sql脚本,模拟教师后台改课程状态:
-- 在 MySQL 中执行 UPDATE course_info SET status = 0 WHERE id = 1; -- 等待 2 秒 UPDATE course_info SET status = 1 WHERE id = 1;同时打开小程序课程列表页,开启 F12 Network 面板,观察/api/course/list请求的响应时间。当第二次UPDATE执行后,刷新页面,课程应立刻从“下架”变回“上架”。这证明:
✅ 后端没做二级缓存(如 Redis),数据强一致;
✅ 前端created()钩子中调用fetchCourses(),每次进入页面都拉新数据;
✅ MySQL 的binlog或应用层监听机制虽未实现,但当前架构已满足毕设实时性要求。
6.4 答辩 PPT:一页图讲清“微信小程序 ↔ SSM ↔ MySQL”数据流向
不要放 UML 类图。用横向泳道图,只画三列:
| 微信小程序(Vue) | SSM 后端(Java) | MySQL(5.7) |
|---|---|---|
wx.request({url:'/api/course/list'})→ | @GetMapping("/course/list")→CourseController.list()→CourseService.list()→CourseMapper.selectList() | SELECT * FROM course_info WHERE status = 1 |
←{code:200, data:[{id:1,title:'Java...'}]} | ←return Result.success(courses) | ← |
箭头标注:HTTP 1.1、MyBatis JDBC、InnoDB 引擎。老师扫一眼就知道你懂底层链路。
6.5 最后一课:我的“答辩后悔药”清单
从那以后我每次帮学生过毕设,都强制走一遍这个清单:
- 凌晨 2 点关掉所有 IDE,用记事本打开
pom.xml,逐行检查<version>是否与application.yml中的组件版本一致——IDE 的 Maven 插件有时会缓存旧依赖; - 把
sql/online_classroom.sql复制一份,重命名为sql/online_classroom_clean.sql,删掉所有INSERT语句,只留CREATE TABLE,然后用这份脚本重建库——避免初始数据污染导致的逻辑错乱; - 在答辩前 3 小时,用同学的 iPhone 扫码体验小程序,全程不开电脑、不连热点,只用校园 WiFi——真机网络环境才是最终考场。
希望帮到你。
本文还有配套的精品资源,点击获取