
“在线课程管理系统”这个题目我在各种课设和毕设清单里见过太多次了。很多同学拿到题目的第一反应是“我该用哪个框架”“代码从哪复制”然后一头扎进环境配置的泥潭里配完 Java 配 Node装完 MySQL 装 Redis最后项目没跑起来人先崩了。我这个过来人的建议是先别碰代码把这个系统到底要“管什么”想清楚再谈技术选型最后才是动手写。这篇文章就把我从需求拆解、数据库设计、前后端实现到部署答辩的完整思路写出来全部基于 SpringBoot Vue 这套主流组合你要做课设、做毕设或者想系统学一下全栈开发都能照着落地。1. 这个系统到底要管什么先画清楚边界再动手1.1 用户身份与权限别一上来就设计五张角色表我见过不少同学做管理系统第一件事就是设计 RBAC 权限模型搞 user_role、role_permission、permission 三张表然后把自己绕晕。在线课程管理系统这个题目绝大多数场景下就两类用户学生和管理员。如果需要教师角色最多再加一类。分类越清晰后端的鉴权逻辑就越简单。我建议的角色模型是这样的角色核心能力说明学生浏览课程、搜索、查看详情、播放视频、收藏课程、下载资料、评论系统的主要使用者管理员课程管理、章节管理、视频上传、用户管理、数据统计内容维护者不需要走前台播放流程教师可选管理自己名下的课程和章节如果题目有“教师开课”的需求才加这里的要点是不要为了“看起来高级”而引入 Spring Security 复杂的权限配置。一个role字段1 或 2配合后端拦截器判断接口权限已经完全够用。你做的是课程管理系统不是企业级 SaaS 平台功能边界划得太宽只会给自己挖坑。1.2 功能清单从用户视角倒推模块我习惯的做法是把自己当成用户从头到尾走一遍使用流程然后根据流程反推功能。一个学生进来他需要看到什么先看到课程列表点进去看到课程详情简介和章节列表点某个章节开始播放视频觉得不错可以收藏遇到问题可以评论提问。管理员呢要能发布新课程、给课程添加章节、上传视频资源和配套资料、把不合适的用户禁用掉。整理下来核心模块就是这几个用户模块注册、登录、个人信息、密码修改课程模块课程列表、课程搜索、课程详情、课程分类章节与视频模块章节列表、视频播放、视频进度记录加分项收藏与评论模块收藏课程、发表评论、查看评论后台管理模块课程 CRUD、章节 CRUD、用户管理这些功能组合起来就是一个能演示、能答辩、逻辑完整的系统。我给学生的建议是如果时间紧先把前四个模块做扎实后台管理甚至可以只保留课程和章节的增删改查。与其做十个半成品模块不如把五个模块做到“老师一问就能答上细节”。1.3 数据流从页面到数据库的完整路径很多新手写代码容易“东写一块西写一块”本质上是对数据流不清晰。我建议在动手之前先在纸上画一条完整链路用户打开课程列表页Vue 发请求到/api/course/listSpringBoot 的 Controller 接收参数后交给 ServiceService 调用 Mapper 查数据库数据库返回课程记录列表Mapper 封装成实体类Service 做业务处理Controller 包装成统一返回体前端拿到 JSON 渲染成卡片。这条链路你每写一个接口前都默念一遍就不容易写乱。我额外强调一点前端页面传参、后端接收参数、数据库字段这三者的“命名和类型”一定要统一。我见过最耗时的 bug 就是前端传的是courseId后端实体用的是id前端传 String后端定义 Long结果数据死活查不出来花了半小时排查最后发现就是字段名对不上。这个习惯越早养成越好。2. 技术选型为什么这套组合成了课设标配2.1 SpringBoot 版本新手最容易踩的坑打开 Spring Initializr默认给你的是 SpringBoot 3.x很多老教程用的却是 SpringBoot 2.x两者的依赖坐标、配置方式有差异这是新手配置环境时最大的一个隐性坑。我的建议很简单看你手头的教程用什么版本你就跟着用什么版本。如果教程是 SpringBoot 2.7 JDK8你就别去开 3.x 的新项目反之如果用 3.x就确保本机 JDK 是 17。SpringBoot 3.x 的javax包名换成了jakarta很多旧代码直接复制过来是编译不过的。另外 3.x 要求 JDK17CentOS 服务器上如果默认 JDK8你还得先装新版本。版本选择上我给一个保守但稳定的组合SpringBoot 2.7.x JDK8 MyBatis-Plus 3.5.x。这个组合的教程量大、问题多、老师也熟悉遇到问题基本搜索一下就有答案。等你自己做商业项目时再用 3.x 不迟。2.2 前端Vue3 Vite Element Plus 是目前的主流前端选型同样存在版本纠结Vue2 Element UI 还是 Vue3 Element Plus我现在的建议是直接 Vue3。Vue3 发布好几年了生态早就成熟Element Plus 的组件足够覆盖课设需要的表格、表单、弹窗、分页。Vue3 的组合式 APIsetup 语法糖写起来比 Options API 更简洁而且现在答辩时老师更认可新技术。Vite 作为构建工具比 Webpack 快得多npm run dev启动几乎是秒开调试体验好很多。这里提醒一个常见问题Vite 构建的项目默认base是/部署到服务器非根路径时页面会空白需要改成./相对路径或具体子路径。这个我后面部署部分再展开。2.3 MyBatis-Plus为什么课设几乎都在用它如果用原生 MyBatis每个方法都要写 XML 映射课设那种 CRUD 场景会写得想哭。MyBatis-Plus 的好处是单表操作直接继承了BaseMapperselectById、selectPage、insert、updateById这些方法开箱即用省掉了 80% 的 SQL 编写。复杂查询可以手写Select注解或 XML灵活度也够。相比之下Spring Data JPA 虽然也可以但国内课设氛围下 MyBatis-Plus 的资料更多遇到问题更容易搜到答案。技术没有绝对好坏适合你当前阶段的就是最好的。2.4 鉴权方案Session、JWT 还是 Sa-Token登录鉴权是每个管理系统绕不开的环节。三个方案我做个直白的对比Session传统方案简单但前后端分离时处理跨域比较麻烦很难撑起集群JWT无状态前端存 token后端解析即可是目前前后端分离项目的主流Sa-Token封装好的轻量鉴权框架内置登录、权限、踢人下线功能对新手非常友好我给课设的建议是如果不想在认证上花太多时间就用 Sa-Token它能帮你省掉自己写拦截器、解析令牌、处理过期这些重复工作。如果你想在答辩时展示自己理解得深一点就手写 JWT 拦截器控制在几个小时也能写完。不要自己从零实现 Session Redis 的登录态性价比太低。3. 数据库设计课程系统的地基怎么打3.1 核心表拆解五张表加一张可选的表我设计的表结构如下你可以直接借鉴用户表sys_user字段名类型说明idbigint主键自增或雪花usernamevarchar(50)用户名唯一passwordvarchar(100)BCrypt 加密后的密码nicknamevarchar(50)昵称avatarvarchar(255)头像地址roletinyint角色1 学生2 管理员statustinyint状态0 正常1 禁用create_timedatetime创建时间update_timedatetime更新时间deletedtinyint逻辑删除标志课程表course字段名类型说明idbigint主键titlevarchar(100)课程名称covervarchar(255)封面图introtext课程简介categoryvarchar(50)分类Java、前端、数据库等teachervarchar(50)授课老师student_countint学习人数统计用statustinyint上下架状态create_time / update_time / deleted-通用字段章节表course_chapter字段名类型说明idbigint主键course_idbigint所属课程titlevarchar(100)章节名称video_urlvarchar(255)视频地址m3u8 或 mp4durationint视频时长秒可选sortint排序号create_time / update_time / deleted-通用字段收藏表course_favoriteid、user_id、course_id、create_time加唯一索引(user_id, course_id)防止重复收藏。评论表course_commentid、course_id、user_id、content、parent_id支持回复、create_time。这里要注意评论是在课程维度还是章节维度根据题目要求调整我建议课程维度就够了。学习进度表course_progress可选id、user_id、course_id、chapter_id、progress百分比、update_time。这个表是加分项能做到“下次进来接着上次位置播放”答辩时很有亮点。3.2 为什么每张表都要带deleted和create_time初学者常犯的一个错误是表结构尽可能精简字段能省则省。但实际做项目时我发现两个字段至关重要deleted和create_time。deleted是逻辑删除标志配合 MyBatis-Plus 的TableLogic注解删除操作变成更新操作数据不会真的消失管理员误删课程还能恢复。答辩时被问到“如果删错课程怎么办”你可以理直气壮地说有逻辑删除保障。create_time字段是排障的重要线索。很多数据异常问题都需要通过创建时间来排查而且列表页按时间倒序排列也依赖它。虽然 MyBatis-Plus 有自动填充功能但我建议数据库层也不要偷懒DEFAULT CURRENT_TIMESTAMP写上去双保险。3.3 初始化数据怎么准备数据库设计完之后很多人会忽略初始化数据。不要在答辩现场才临时往数据库里录课程提前准备好一批“看起来真实”的数据5 门不同分类的课程、每门课程 5~10 个章节、几条学生评论。封面图用真实的课程图片视频能放几个真实的小片段就放真实的。这里有个小技巧SQL 文件单独维护一个init_data.sql和建表语句分开。部署到服务器时先执行建表 SQL再执行初始化数据 SQL方便快捷也方便复试时重新初始化。4. SpringBoot后端把接口写稳的几条链路4.1 项目分层与统一返回体后端项目目录我习惯这样分controller接收请求参数校验返回结果service业务逻辑处理事务控制mapper数据访问层继承 BaseMapperentity数据库实体dto前端交互的数据对象config配置类CORS、拦截器、云存储等common统一返回体、异常处理、工具类先写一个ResultT统一返回类包含code、message、data三个字段。所有接口返回这个包装类前端统一处理代码会清爽很多。如果不做统一返回每个接口返回类型不统一前端 axios 的拦截器就没法统一处理错误码后面会非常痛苦。4.2 登录认证的完整链路从拦截器到用户上下文以 JWT 方案为例我会把链路设计成这样的用户提交用户名密码Service 层用 BCrypt 校验密码校验通过后用工具类生成 tokentoken 里存用户 id 和角色前端把 token 存在 localStorage每次请求通过 axios 拦截器加到请求头Authorization里后端写一个AuthInterceptor拦截所有/api/**请求放行/api/auth/login、/api/course/list这些公开接口解析 token把用户信息存到 ThreadLocal 中Controller 需要获取当前用户时从 ThreadLocal 里取这里我特别强调 ThreadLocal 的用法。很多新手把用户信息放在参数里一路传下去又麻烦又容易漏。用 ThreadLocal 可以实现在任何地方获取当前登录用户。但要注意每次请求结束必须调用remove()清理否则线程池复用线程时会串数据。这是内存泄漏的高危区答辩时老师经常问。4.3 课程分页查询MyBatis-Plus 的 Page 用法课程列表是前端第一个加载的功能实现几乎都是套路接受当前页码和每页条数按创建时间倒序返回分页数据。public PageCourse getCoursePage(int pageNum, int pageSize, String keyword) { PageCourse page new Page(pageNum, pageSize); LambdaQueryWrapperCourse wrapper new LambdaQueryWrapper(); wrapper.eq(Course::getStatus, 1); if (StringUtils.hasText(keyword)) { wrapper.like(Course::getTitle, keyword); } wrapper.orderByDesc(Course::getCreateTime); return courseMapper.selectPage(page, wrapper); }这里你注意两点。第一LambdaQueryWrapper比普通QueryWrapper安全字段名是编译期检查的不会写错字符串。第二分页需要配置MybatisPlusInterceptor分页插件很多人忘了配这个结果发现selectPage分页不生效查出来的是全量数据。这类问题我建议遇到一次就记住了。4.4 视频地址返回与文件下载的两个坑课程系统的核心是视频。如果你是本地存储视频文件那么接口返回给前端的是一个视频地址比如云端存储的 HTTP 地址或者服务器上的静态资源路径。如果用 m3u8 切片流返回的是.m3u8的索引文件地址。文件下载功能是另一个高频坑。很多同学用普通方法输出文件流结果下载的文件名中文乱码或者文件打不开。正确写法是设置两个响应头response.setContentType(application/octet-stream); response.setHeader(Content-Disposition, attachment; filename URLEncoder.encode(fileName, UTF-8));这里编码方式必须注意URLEncoder编码后生成的filename还需要浏览器端兼容所以一般写成attachment;filename 编码后的文件名 。如果不编码中文文件名大概率变成一串乱码这个细节答辩时很能体现你的经验。4.5 全局异常处理别让报错信息直接甩给前端系统里所有的异常都不应该直接以堆栈形式返回给前端。写一个RestControllerAdvice全局异常处理器业务异常返回code500, message具体提示参数校验异常返回code400未知异常返回统一兜底提示“系统繁忙请稍后再试”。这样做不只是为了好看更重要的是不暴露内部细节。你在部署到服务器后控制台打印完整堆栈日志用于排查用户看到的是友好提示。尤其是数据库相关的异常直接把 SQL 错误返回给前端是很低级的失误。我的习惯是自定义一个BusinessExceptionService 层的业务判断错误统一抛这个异常全局处理器捕获后返回对应错误信息。比如“该课程不存在”“请勿重复收藏”“视频资源更新中”这些提示。把业务规则集中在异常体系里代码的可读性会高很多。5. Vue前端页面怎么组织视频怎么播5.1 路由与页面骨架前端页面结构推荐这样组织/login登录页/register注册页/course课程列表页/course/:id课程详情页包含章节列表、视频播放、评论/admin/course管理员课程管理页/admin/chapter管理员章节管理页/profile个人中心使用 Vue Router 时一定要配置路由守卫。前端路由守卫有两个作用一是未登录用户访问/admin时重定向到登录页二是已经登录的用户访问/login时跳回到首页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })5.2 axios 封装与统一拦截我建议前端统一封装 axios 实例设置基础路径baseURL: /api然后写请求拦截器和响应拦截器。请求拦截器里读 localStorage 的 token 并加到请求头响应拦截器里判断code字段非 200 时弹出错误提示401 时跳转登录页并清除本地 token。这个封装层是前端项目的骨架封装好了之后每个业务页面的代码会非常干净。视图组件只关心调哪个接口、拿到数据渲染什么不再处理错误逻辑。特别是“登录过期”这种全局逻辑如果没有统一拦截器每个页面都要单独写一遍想想就头疼。5.3 课程列表页与详情页的数据绑定课程列表页直接用 Element Plus 的el-card网格展示遍历courseList数据点击跳转到详情页。详情页通过路由参数id调用后端接口获取课程信息和章节列表。这里有个前后端协作的小细节路由参数拿回来是字符串请求参数转成 Number 再发给后端避免后端类型转换报错。评论功能我建议用el-input加一个提交按钮提交前判断用户是否登录未登录则弹出登录框。评论列表用el-timeline时间线组件展示视觉效果好代码也少。5.4 视频播放m3u8 的播放方案课程视频播放是这个系统最容易“翻车”的部分热搜词里“vue播放m3u8”出现频率非常高说明大家都在这里卡过。我来详细说一下。如果你拿到的是 MP4 直链直接用video标签加src就能播没什么难度。但更多在线课程场景用的是 m3u8 格式的切片流原生video标签不支持需要引入hls.js库npm install hls.js然后在视频播放组件里判断资源类型如果是 m3u8 且浏览器支持 HLS用 hls.js 加载视频源if (videoUrl.endsWith(.m3u8)) { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(videoUrl) hls.attachMedia(videoElement) } }这里有几个实际会遇到的问题。第一个是视频地址跨域时m3u8 里面的分片地址是相对路径hls.js 会自动拼接但前提是你后端的服务端要允许对应跨域请求头。第二个是 Vite 开发时前端运行在 5173 端口后端在 8080 端口需要后端配置 CORS 允许跨域。第三个是如果你在本地没有 nginx 或者静态资源服务来支持 Range 请求MP4 文件的拖动播放可能会出问题m3u8 切片流天然支持按分片加载反而更流畅。这也是我建议在线课程优先考虑 m3u8 方案的原因。5.5 打包之后的布局异常和路由 404前端完成部署后最容易遇到两个问题样式错乱和路由刷新 404。很多同学本地运行一切正常npm run build部署到服务器就白屏十有八九是base路径配置问题。如果你把前端部署在http://ip:8080/根路径那base: /没问题如果部署在http://ip:8080/course/子路径就必须在vite.config.js里设置base: ./这样资源才会按相对路径引用。路由刷新 404 的问题则和 Nginx 配置有关。前端使用 History 模式时访问/course/1这个路径Nginx 会尝试去找服务器上的真实文件找不到就 404。需要在 Nginx 配置里加上 location 规则把所有非真实文件请求都重定向到index.htmllocation / { try_files $uri $uri/ /index.html; }这个配置我几乎每次部署都要反复确认你可以直接抄走。6. 部署与答辩让系统能跑、也能讲6.1 本地跑通的整体环境清单很多人代码写完了以为就万事大吉了结果换一台电脑部署各种环境问题接踵而来。我建议从一开始就把环境要求写清楚放进项目 README组件版本建议用途JDK8 或 17与 SpringBoot 版本匹配后端运行Maven3.6后端依赖管理Node.js16前端构建MySQL5.7 或 8.0数据存储Nginx1.20静态托管与反向代理部署服务器时我会先把 MySQL 装好建一个专用数据库用户不要用 root然后把 SQL 文件导入。云服务器的安全组一定要放行 3306、8080、80 等对应端口不然前端访问不到后端、后端连不上数据库这种“玄学问题”会让你排查很久很久。6.2 后端打包与启动SpringBoot 后端用 Maven 打包mvn clean package -DskipTests打完的 jar 包在target/目录下用java -jar xxx.jar启动。生产环境建议后台运行nohup java -jar xxx.jar --spring.profiles.activeprod app.log 21 这里的--spring.profiles.activeprod是激活生产环境配置。我习惯把数据库连接等信息放在application-prod.yml中避免把测试环境的数据库配置带到服务器上。MySQL 8.0 的驱动和时区设置需要注意serverTimezoneAsia/Shanghai要显式配置否则会出现时间差 8 小时这种经典问题。6.3 Nginx 配置一个文件搞定前后端前端构建完会生成dist目录把这个目录上传到服务器比如/opt/course/dist然后配置 Nginxserver { listen 80; server_name _; root /opt/course/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } }前端请求/api/xxxNginx 会转发到后端服务的 8080 端口。这解决了跨域问题因为浏览器的视角里请求的是同一个域。视频静态资源如果单独存放还需要配置一个/video/的 location 指向视频目录让 Nginx 直接提供文件访问能力这样视频加载会快很多也省了后端读文件转发的资源开销。6.4 答辩现场老师最常问的问题怎么答最后一部分讲讲答辩。题目是“程序数据库报告部署教程答辩指导”说明这是一个完整交付型项目答辩表现往往比代码本身更能影响成绩。我不建议死记硬背而是把核心逻辑讲透让老师觉得这确实是你自己做的。我把老师最爱问的问题按踩坑频率排个序1. 登录是怎么实现的Token 存在哪里过期了怎么办这个问题答得好不好直接决定老师对你项目的初始印象。你要讲清楚前端输入账号密码后端 BCrypt 校验密码生成 JWT或 Sa-Token前端存 localStorage请求时放进请求头后端拦截器校验有效性。再补充一句“我设置了 24 小时过期时间过期后前端捕获 401 状态码自动跳转登录页”说明你考虑过完整闭环。2. 数据库为什么这么设计为什么用逻辑删除讲清楚“用户-课程-章节-视频”的主线关系说明收藏表用联合唯一索引防止重复评论表为什么冗余课程 id。逻辑删除的理由是“避免误删数据保留操作痕迹”一句话就能体现你的工程意识。3. 视频播放是怎么实现的m3u8 和 MP4 有什么区别先区分两者MP4 是整段文件m3u8 是切片流将视频切成 ts 分片通过索引文件按需加载。在线教育场景下 m3u8 更适合拖动快、加载快、便于防盗链前端用 hls.js 播放。如果老师接着问“怎么做防盗链”你可以提“云存储签名 URL 或后端带 token 校验”这是加分项但不要展开太深免得引到不擅长的领域。4. 项目哪些地方可以再优化这个问题不是在刁难你而是在给你机会展示思考深度。我的标准答案是当前本地存储视频可以迁移到云存储当前单机部署可以引入 Redis 缓存热点课程当前没做视频进度记录可以加学习进度表和断点续播。提前准备一到两个扩展点比临场编造强一万倍。5. 项目里你遇到最大的困难是什么这是最主观的问题也是很多人的失分点。不要说什么“环境配置了一天”这种话太减分。你可以讲“最开始视频播放总是不出来我排查发现是跨域问题m3u8 分片请求被拦截了后来通过 Nginx 反向代理统一了前端和后端的域名解决。”这是一个真实的、有技术细节的故事老师一听就知道你是亲自做过项目的。6.5 答辩前要准备的物事清单在答辩前一周我会把这几样东西准备好一份一两页的部署文档写明从零到一启动系统的每个步骤一份项目结构说明标出每个模块对应哪些代码文件一份 SQL 脚本归档建表语句和初始化数据分离一份测试账号列表比如管理员账号、学生账号的密码都写在 PPT 旁注里。这些材料看着琐碎但它们是“程序数据库报告部署教程答辩指导”这个交付物完整性的体现。好的交付不只是能跑的项目还是别人能接手、能复现的项目。你把这些准备好即使代码有一些小瑕疵老师也会觉得你的工程素养到位。我个人带项目这些年最大的感受是在线课程管理系统这个题目本身不难难的是把每一步都做得规范、讲得清楚。数据库设计的时候多想一步后端接口写的时候多考虑异常前端播放视频的时候多测几种格式部署的时候把环境记录下来答辩的时候把故事讲顺。这些习惯培养起来你做的不只是一个课设而是给自己建立了一套完整的全栈开发框架。后面无论是找工作还是做自己的小项目这套东西都能直接复用。希望这篇内容能帮你把这个题目做得从容一些。