在线课程管理系统开发全指南:从数据库设计到SpringBoot+Vue部署答辩
2026/9/14 23:29:59 网站建设 项目流程

“在线课程管理系统”这个题目,我在各种课设和毕设清单里见过太多次了。很多同学拿到题目的第一反应是“我该用哪个框架”“代码从哪复制”,然后一头扎进环境配置的泥潭里,配完 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/list,SpringBoot 的 Controller 接收参数后交给 Service,Service 调用 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 要求 JDK17,CentOS 服务器上如果默认 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 的组合式 API(setup 语法糖)写起来比 Options API 更简洁,而且现在答辩时老师更认可新技术。

Vite 作为构建工具,比 Webpack 快得多,npm run dev启动几乎是秒开,调试体验好很多。这里提醒一个常见问题:Vite 构建的项目默认base/,部署到服务器非根路径时页面会空白,需要改成./相对路径或具体子路径。这个我后面部署部分再展开。

2.3 MyBatis-Plus:为什么课设几乎都在用它

如果用原生 MyBatis,每个方法都要写 XML 映射,课设那种 CRUD 场景会写得想哭。MyBatis-Plus 的好处是单表操作直接继承了BaseMapperselectByIdselectPageinsertupdateById这些方法开箱即用,省掉了 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 或 mp4)
durationint视频时长(秒),可选
sortint排序号
create_time / update_time / deleted-通用字段

收藏表course_favorite:id、user_id、course_id、create_time,加唯一索引(user_id, course_id)防止重复收藏。

评论表course_comment:id、course_id、user_id、content、parent_id(支持回复)、create_time。这里要注意,评论是在课程维度还是章节维度,根据题目要求调整,我建议课程维度就够了。

学习进度表course_progress(可选):id、user_id、course_id、chapter_id、progress(百分比)、update_time。这个表是加分项,能做到“下次进来接着上次位置播放”,答辩时很有亮点。

3.2 为什么每张表都要带deletedcreate_time

初学者常犯的一个错误是表结构尽可能精简,字段能省则省。但实际做项目时我发现两个字段至关重要:deletedcreate_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:数据访问层,继承 BaseMapper
  • entity:数据库实体
  • dto:前端交互的数据对象
  • config:配置类(CORS、拦截器、云存储等)
  • common:统一返回体、异常处理、工具类

先写一个Result<T>统一返回类,包含codemessagedata三个字段。所有接口返回这个包装类,前端统一处理,代码会清爽很多。如果不做统一返回,每个接口返回类型不统一,前端 axios 的拦截器就没法统一处理错误码,后面会非常痛苦。

4.2 登录认证的完整链路:从拦截器到用户上下文

以 JWT 方案为例,我会把链路设计成这样的:

  1. 用户提交用户名密码,Service 层用 BCrypt 校验密码
  2. 校验通过后,用工具类生成 token,token 里存用户 id 和角色
  3. 前端把 token 存在 localStorage,每次请求通过 axios 拦截器加到请求头Authorization
  4. 后端写一个AuthInterceptor拦截所有/api/**请求(放行/api/auth/login/api/course/list这些公开接口),解析 token,把用户信息存到 ThreadLocal 中
  5. Controller 需要获取当前用户时,从 ThreadLocal 里取

这里我特别强调 ThreadLocal 的用法。很多新手把用户信息放在参数里一路传下去,又麻烦又容易漏。用 ThreadLocal 可以实现在任何地方获取当前登录用户。但要注意:每次请求结束必须调用remove()清理,否则线程池复用线程时会串数据。这是内存泄漏的高危区,答辩时老师经常问。

4.3 课程分页查询:MyBatis-Plus 的 Page 用法

课程列表是前端第一个加载的功能,实现几乎都是套路:接受当前页码和每页条数,按创建时间倒序,返回分页数据。

public Page<Course> getCoursePage(int pageNum, int pageSize, String keyword) { Page<Course> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Course> 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全局异常处理器,业务异常返回code=500, message=具体提示,参数校验异常返回code=400,未知异常返回统一兜底提示“系统繁忙,请稍后再试”。

这样做不只是为了好看,更重要的是不暴露内部细节。你在部署到服务器后,控制台打印完整堆栈日志用于排查,用户看到的是友好提示。尤其是数据库相关的异常,直接把 SQL 错误返回给前端,是很低级的失误。

我的习惯是自定义一个BusinessException,Service 层的业务判断错误统一抛这个异常,全局处理器捕获后返回对应错误信息。比如“该课程不存在”“请勿重复收藏”“视频资源更新中”这些提示。把业务规则集中在异常体系里,代码的可读性会高很多。

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.html

location / { 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.active=prod > app.log 2>&1 &

这里的--spring.profiles.active=prod是激活生产环境配置。我习惯把数据库连接等信息放在application-prod.yml中,避免把测试环境的数据库配置带到服务器上。MySQL 8.0 的驱动和时区设置需要注意,serverTimezone=Asia/Shanghai要显式配置,否则会出现时间差 8 小时这种经典问题。

6.3 Nginx 配置:一个文件搞定前后端

前端构建完会生成dist目录,把这个目录上传到服务器,比如/opt/course/dist,然后配置 Nginx:

server { 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/xxx,Nginx 会转发到后端服务的 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 旁注里。

这些材料看着琐碎,但它们是“程序+数据库+报告+部署教程+答辩指导”这个交付物完整性的体现。好的交付不只是能跑的项目,还是别人能接手、能复现的项目。你把这些准备好,即使代码有一些小瑕疵,老师也会觉得你的工程素养到位。

我个人带项目这些年最大的感受是:在线课程管理系统这个题目本身不难,难的是把每一步都做得规范、讲得清楚。数据库设计的时候多想一步,后端接口写的时候多考虑异常,前端播放视频的时候多测几种格式,部署的时候把环境记录下来,答辩的时候把故事讲顺。这些习惯培养起来,你做的不只是一个课设,而是给自己建立了一套完整的全栈开发框架。后面无论是找工作还是做自己的小项目,这套东西都能直接复用。希望这篇内容能帮你把这个题目做得从容一些。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询