1. 网络教学系统的需求边界与核心模块划定
先说结论:这个题目看着常规,但每年答辩翻车的人都不少。原因大多不是代码跑不起来,而是需求边界划得太散——学生端、教师端、后台管理端三个入口做成一锅粥,功能堆了一堆,最后连自己都说不清"系统到底为用户解决了什么问题"。
1.1 我当时是怎么梳理功能清单的
这个项目叫"大学生计算机基础网络教学系统",关键词是"计算机基础",不是"计算机全专业课程平台"。所以核心场景就三个:学生在线看课件、看视频、交作业;教师上传课件、布置作业、批作业;管理员管账号管课程。其他花里胡哨的功能,比如在线考试、直播答疑、论坛社交,在毕设周期内完全可以砍掉或做成"预留扩展点"。
我最终落地的功能模块如下:
| 模块 | 学生端 | 教师端 | 管理端 |
|---|---|---|---|
| 用户认证 | 登录注册 | 登录 | 登录 |
| 课程学习 | 查看课程列表、进入章节、观看视频、下载课件 | 维护课程和章节 | 审核课程上下架 |
| 作业 | 查看作业、在线提交、查看成绩 | 布置作业、批改打分 | 查看整体数据 |
| 公告 | 查看公告 | 发布公告 | 全局管理 |
| 个人中心 | 修改密码、查看学习记录 | 管理自己的课程 | 用户管理 |
你可能会问:那"学习记录"和"课程上下架"要不要做?我的建议是看时间和精力。这两个功能其实共享一张表就能实现,后面数据库设计里我会写清楚,做进去以后答辩时能聊的东西会多很多。
1.2 一个容易被忽略的模块:公告系统
很多毕设项目一开始根本没想到公告。等你做到前后端联调时,发现首页空荡荡的没有内容,才想起来要"填充数据"。公告表实现成本极低,却能同时出现在学生端首页和教师端后台,是一个性价比很高的模块。实际写的时候就是一张表加两个接口,前端一个轮播或列表组件搞定。
2. 技术选型:这套三件套好在哪,以及替代方案的取舍
这个题目能成为毕业设计常青树,很大原因是 SpringBoot + Vue + MySQL 这套组合对大多数本科生的学习曲线足够平缓,同时又能覆盖企业级开发的大部分基础概念。你答辩时可以讲的东西非常多:RESTful 接口设计、前后端分离、ORM 映射、鉴权方案、文件存储、部署策略,每一个都能展开聊。
2.1 关于 Spring Boot 的版本选择,别再踩版本坑
我看到很多同学的 pom.xml 直接粘贴了 Spring Initializr 上最新的 3.x 版本,然后发现项目起不来。这里有一个非常现实的兼容性问题:
- Spring Boot 3.x 强制要求JDK 17+,很多学校机房和笔记本装的是 JDK 8 或 11;
- Spring Boot 3.x 中 javax.* 包改成了 jakarta.*,网上很多老教程和组件示例直接复制会编译报错;
- 部分自己写的工具类、第三方 starter 还没适配 3.x。
如果你只是做毕设,我强烈建议留在 2.7.x。别觉得版本落后,毕业设计院的重点是"完整度和逻辑清晰"。我当时用的是 Spring Boot 2.7.14 + JDK 1.8,配套 MyBatis Plus 3.5.3,跑起来非常稳。等答辩结束、想自己再折腾,再上 3.x 不迟。
2.2 为什么用 Vue 而不是 JSP,这个题必须会答
答辩时老师大概率会问:"为什么不直接用 JSP + Spring Boot 一套搞定?"标准答法是:前后端分离能让前端开发和后端开发并行推进,Vue 的组件化思想适合复用课程卡片、视频播放器这类 UI 模块;同时前后端通过 JSON 交互,数据结构清晰,后续要接小程序或移动端也不用重写后端。
还有一层原因你可以提:Vue 生态对"视频播放"这类场景支持好,配合 hls.js 能轻松处理 m3u8 格式的视频流,用 JSP 做的话这块要费不少劲。这句话一出来,懂行的老师会点头。
3. 数据库设计:六张核心表如何支撑整个教学链路
数据库设计是最先动手也最不能返工的部分。后期改表结构等于改接口、改前端字段,牵一发动全身。我最后把表收敛到了六张,加上两张辅助表,所有功能都能覆盖。
3.1 user 表:角色字段别用字符串
用户的角色我用的是 tinyint 类型:0 学生、1 教师、2 管理员。前端的路由守卫和后端的拦截器直接比对数字即可,性能好、代码也好写。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '学号/工号', `password` varchar(255) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` varchar(50) DEFAULT NULL, `role` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0学生 1教师 2管理员', `avatar` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;密码我用了 BCryptPasswordEncoder,别用 MD5。这个点答辩时讲出来,老师会认为你考虑到了安全问题。
3.2 course 表 + chapter 表:课程与章节的父子关系
课程表需要包含封面图、简介、属性和教师 id。教师 id 外联回 user 表。当时我加了一个 status 字段用于上下架,默认 1 上架:
CREATE TABLE `course` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL, `cover` varchar(255) DEFAULT NULL, `description` text, `teacher_id` int(11) NOT NULL, `status` tinyint(4) NOT NULL DEFAULT 1, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;章节表则要存视频地址和课件地址,视频地址我直接存相对路径,比如/file/video/chapter1.m3u8,而不是存完整 URL。这样可以避免服务器地址变了导致老数据全失效。
CREATE TABLE `chapter` ( `id` int(11) NOT NULL AUTO_INCREMENT, `course_id` int(11) NOT NULL, `title` varchar(100) NOT NULL, `video_path` varchar(255) DEFAULT NULL, `courseware_path` varchar(255) DEFAULT NULL, `sort` int(11) DEFAULT 0, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3.3 homework、submission 和 announcement:状态值设计
作业表的核心字段是 title、content、deadline,以及关联的 course_id。提交表 submission 则是整个系统的"数据流转枢纽"——学生提交后,教师批改,成绩写在这张表里:
CREATE TABLE `submission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `homework_id` int(11) NOT NULL, `student_id` int(11) NOT NULL, `content` text, `file_path` varchar(255) DEFAULT NULL, `score` int(11) DEFAULT NULL, `comment` varchar(255) DEFAULT NULL, `submit_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_homework_student` (`homework_id`, `student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;我加了一个唯一联合索引 (homework_id, student_id),作用是让同一份作业一个学生只能提交一次。后端接口里再做"第二次提交即覆盖"的逻辑,前端判断一下就行。这个设计细节很加分。
至于 announcement 表,字段就是 title、content、publisher_id、create_time,非常简单,但别漏掉。
4. 后端落地:接口设计、权限控制与文件存储
后端部分我不打算贴完整代码,那是源代码该干的事。这里讲的是思路和关键写法,以及为什么这样写。
4.1 项目目录结构:按功能分包还是按层分包
按层分包(controller/service/mapper)适合小项目,但教学系统功能一多,你会发现 controller 下面堆了十几个类,维护起来难受。我按功能分包,目录长这样:
com.example.teachingsystem ├── config ├── controller │ ├── admin │ ├── teacher │ └── student ├── entity ├── mapper ├── service │ └── impl ├── common │ ├── Result │ ├── JwtUtil │ └── GlobalExceptionHandler统一返回体 Result 是我自己封装的,包含 code、message、data 三个字段。所有接口统一返回它,前端 axios 拦截器只处理这个格式,联调会顺畅很多。
public class Result<T> { private Integer code; private String message; private T data; // 静态方法 success() error() ... }4.2 登录鉴权:拦截器 + JWT,别引 Security
Spring Security 不是不能用,但学习成本高、配置繁琐,在毕设阶段容易把时间耗在过滤器链和权限表达式上。我自己用 JWT + HandlerInterceptor 做了个轻量方案,效果足够:
- 登录成功后签发 token,有效期为 24 小时;
- 前端把 token 放在请求头
Authorization里; - 拦截器解析 token,把用户 id 和角色放入 request attribute;
- 再写一个角色校验方法,教师接口校验 role == 1,管理员校验 role == 2。
这个方案的核心代码其实就是 JwtUtil 工具类加一个拦截器注册:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); // 注意我的前端会带 Bearer 前缀 // 解析失败直接返回 401 // 解析成功把 userId 塞进 request.setAttribute("userId", ...) return true; } }需要注册到 WebMvcConfigurer 中,并设置放行路径(比如登录接口、注册接口、静态资源路径)。"拦截器 + 注解"的方式,写起来比 Security 直观,答辩时你可以说"考虑到项目规模,我用轻量级的 JWT 方案替代了完整的安全框架,后续如果要引入更全面的权限体系可以平滑迁移"——这句话非常稳。
4.3 文件上传与视频播放的现实方案
视频播放是这种系统里最容易卡壳的地方。我的经验是:不要在毕设里做真正的视频转码和流媒体分发。你只需要:
- 用
MultipartFile.transferTo()把视频存到本地某个目录,比如/upload/video/; - 在 Spring Boot 里配置虚拟路径映射,让
http://ip:8080/file/**能直接访问本地磁盘文件; - 视频格式直接用浏览器能播放的 mp4 格式,或者转成 hls(m3u8)后用 hls.js 播放。
m3u8 播放是网上搜索的热点,我也做了一版。做法很简单:用 FFmpeg 把 mp4 转成 m3u8 + ts 切片放到同一个目录,前端引入 hls.js,当视频地址以.m3u8结尾时先判断Hls.isSupported(),然后交给 hls 实例加载。这个方案的好处是切片文件是流式的,可以手动控制码率,并且浏览器兼容性比直接播放 mp4 更稳。
文件类型校验别只在前端做,后端也要做。我当时只校验了扩展名,结果有学生把 .exe 改名成 .mp4 传上来了。后来再加了文件头魔数校验,比如 mp4 文件开头通常是ftyp。这个安全细节讲讲也是加分项。
5. 前端落地:Vue 路由、用户界面与 axios 封装
Vue 这边我用了 Vue 3 + Element Plus。如果你对 Vue 2 更熟,用 Vue 2 也没问题,但新项目我还是建议 Vue 3,毕竟面试和后续学习都用得上。
5.1 项目目录和路由设计
src ├── api │ ├── course.js │ ├── homework.js │ └── auth.js ├── router │ └── index.js ├── store │ └── user.js ├── views │ ├── student │ ├── teacher │ ├── admin │ └── login.vue ├── components └── utils └── request.js路由设计上,我按角色写了三个独立的路由模块:student.js、teacher.js、admin.js,根路由/动态判断用户角色后重定向到对应首页。这里有个点必须先想清楚:路由守卫里不要只判断是否登录,还要判断角色和路由 meta.role 是否匹配。不然学生直接改地址栏访问教师后台,页面会白屏或报一堆权限错误。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else if (token && to.meta.role && to.meta.role !== store.state.user.role) { ElMessage.error('无权访问') next(false) } else { next() } })5.2 axios 封装:统一处理 token 和错误码
request.js 里我用 axios 实例,请求拦截器统一加Authorization: Bearer ${token},响应拦截器统一处理 code:
service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => Promise.reject(error) )一个值得注意的点是:文件流下载的接口不能走这个统一的响应拦截器。当时我下载作业附件时发现文件被存成了 JSON,排查半天,最后是给下载接口单独发了一个不带拦截器的 axios 实例才解决。你写的时候也可以提前把这种情况考虑进去。
5.3 课程卡片、视频播放和分页筛选
课程列表页我用 Element Plus 的卡片栅格布局,每个卡片展示封面、课程名、教师名,点击进入课程详情,里面左侧是章节列表,右侧是视频播放器。这一步的实现难度不大,但要注意视频加载失败时的用户提示——我加了 error 事件监听,播放失败就显示一个"请下载课件学习"占位组件,至少不会黑屏僵在那。
分页筛选我后端用 MyBatis Plus 的 Page 分页插件,前端封装了一个 Pagination 组件,统一接收 total、page、size,然后触发查询。记住一点:条件筛选不要靠前端 filter,要拼在后端查询条件里。有的同学把全部课程拉到前端再 filter,课程一多页面就卡。
6. 前后端联调与打包部署:几个常见的坑
这部分是全网搜索热词的集中区域。我把自己实际踩过的坑挑几个高频的写出来,每一条都能省你半天时间。
6.1 Vue 打包放进 Spring Boot 的三种姿势
第一种,也是我推荐的方式:前端执行npm run build生成 dist 目录,然后把里面的文件复制到 Spring Boot 项目的src/main/resources/static下,重新打包启动。访问同一个端口就能同时打开页面和调接口。
第二种,直接在前端项目里装 Vite 插件,打出 jar 包时自动拷贝 dist 到 target/classes/static,省得手动复制。这个适合你想让"一键构建"更完整的情况。
第三种,前端的 dist 部署到 Nginx,后端单独跑一个 8080 端口,Nginx 里配置反向代理/api到后端。这种方式更接近生产环境,但毕设答辩现场网络环境不确定,我建议还是用第一种最保守。
这里有个经典坑:Vue 路由用 history 模式时,页面刷新会 404。解决方法是改回 hash 模式,或者在后端写一个转发规则,把非 /api 的请求转发到 index.html。我图省事,直接用的是 hash 模式。别觉得 hash 丑,它最稳。
6.2 MySQL 连接:三个高频报错和对应解法
网上搜"mysql ssl连接错误"的人特别多,我也遇到了。Spring Boot 连接 MySQL 8 时,报错大概是:
SSL connection error解决:JDBC URL 加?useSSL=falseThe server time zone value报错 解决:加&serverTimezone=Asia/Shanghai- 驱动类加载不到:确认用的
com.mysql.cj.jdbc.Driver,同时数据库驱动依赖里不要排除mysql-connector-java
我当时把完整的 JDBC URL 统一写成了这样:
jdbc:mysql://localhost:3306/teaching_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true注意allowPublicKeyRetrieval=true,MySQL 8 的 caching_sha2_password 认证模式下,没有这个参数会偶发连接失败。这也是一个能帮别人省时间的细节。
6.3 版本过高引起的问题:Maven 依赖冲突与 Spring Boot 启动失败
如果你坚持用了 Spring Boot 3.x,大概率会遇到这种问题:原来 2.x 下正常的 MyBatis Plus 版本在 3.x 下启动直接报ClassNotFoundError。这是因为高版本 Spring Boot 用了 Jakarta EE,而某些 starter 还是旧规范。
应对方案有二:一是换掉不兼容的依赖,二是整体降级。我给的建议是后者。毕设周期就那么长,别把时间浪费在"让一个老starter适配新框架"上。降级到 2.7.x 以后所有问题迎刃而解。
再有一个常见问题是Spring Boot 端口被占用,启动报Port 8080 was already in use。解决方式很简单:启动命令换成mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8081,或者改配置文件。这个虽然不高级,但现场演示时经常救急。
7. 最终体会与复盘——如何让答辩组长多看一眼
整个项目做完,我最大的感触是:毕设考察的不是你用了多炫的技术,而是你有没有能力把一个模糊的题目拆成可落地的功能,并且把它们串起来。这个系统里的每条链路——从建表到后端接口再到前端页面——都有不少边界情况需要考虑,你可以把这些作为答辩时主动展示的点。
我当时答辩时主动提了三个细节,老师们反应都很好:
- 提交作业的覆盖策略:用唯一索引拦截重复提交,同时提供"再次提交则覆盖原文件"的能力,业务上更贴近真实教学场景;
- 视频播放方案:用 hls.js 播放 m3u8 流,解释清楚了为什么用切片流而不是直接播放一个几百 MB 的 mp4 文件;
- 权限控制:自己封装了拦截器实现 JWT 校验,说明白了和直接用 Security 的取舍。
另外一个小技巧:答辩演示前,准备一份干净的测试数据,把课程视频时长控制在 30 秒以内,避免现场等进度条。这个细节看似小,但能让整个演示节奏顺畅非常多。
最后再分享一个实际测试中的经验:不要只在自己电脑上跑,换一台没有开发环境的机器测试前端打包后的 jar 包。这一步能提前发现很多路径写死、依赖缺失、上传文件目录不存在的问题。我第一次打包后在室友电脑上跑,就发现上传目录在我代码里写的是绝对路径D:/upload,换机器就崩了。后来改成从配置文件读取上传根路径,才算真正可移植。你提前做一次,就能省去演示现场的手忙脚乱。