ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot+Vue的在线课程管理系统设计与实践解析

基于SpringBoot+Vue的在线课程管理系统设计与实践解析 最近好几个读者在后台问我毕设选了“在线课程管理系统”技术栈用的Java、Vue、SpringBoot不知道怎么把整个项目串起来答辩也不知道该准备哪些点。这题我太熟了这类管理系统可以说是Java后端入门最经典的项目形态之一前前后后我帮人改过不下几十个版本从表结构到权限控制从视频点播到部署上线该踩的坑基本都踩了一遍。今天就把这套基于SpringBoot Vue的在线课程管理系统拆开来聊从设计思路到核心代码实现再到部署和答辩一次讲透。先说清楚这个项目是什么它本质上是一个典型的“前台展示 后台管理”的双端Web系统前端用Vue负责页面交互后端用SpringBoot提供接口服务用MySQL存业务数据。学员端可以看课程、看视频、做作业、查成绩管理端可以管课程、管用户、管分类、管公告。它解决的是传统线下选课、面授教学中信息不透明、排课混乱、作业收集困难的问题把课程从发布、选课、学习到考核的完整流程搬到线上。这套组合之所以能成为毕设和课设里的绝对主力不是因为什么高深的技术而是因为它足够标准、足够稳定、也足够有代表性——SpringBoot负责业务逻辑Vue负责页面渲染MySQL负责数据持久化一套下来正好把Web开发的完整链路走了一遍答辩时每个模块都能清楚讲出来。1. 项目整体设计与技术选型思路1.1 为什么是SpringBoot Vue而不是其他组合很多人在选型时会纠结后端要不要用SSMSpring SpringMVC MyBatis前端能不能用JSP我的建议很直接如果是课程设计或毕业设计SpringBoot Vue是当前最优解。原因其实不复杂。第一SSM是上一代的技术组合配置繁琐光是Spring和MyBatis的XML配置就能劝退一堆新手而SpringBoot用自动配置把这些全部简化了内嵌Tomcat意味着不用单独部署WAR包一个java -jar就能跑起来。第二前后端分离已经是行业主流写法Vue负责数据渲染和交互SpringBoot只提供JSON格式的接口职责清晰后面扩展移动端或者小程序端只需要复用同一套后端接口。第三从答辩角度讲SpringBoot Vue代表了你掌握的是当前企业级开发的主流技能而不是停留在教科书时代的古董技术这也是评委老师非常看重的一点。当然如果只是想跑通Demo并且时间极其紧张用JSP SpringBoot一体式开发也可以。但我不推荐因为JSP在前后端调试、数据交互、权限管理上的体验确实比较痛苦而后端接口 前端渲染的方式不管是自己调试还是给老师演示都更加直观。1.2 系统功能模块拆解学员端和管理端各管哪些事在线课程管理系统的功能设计核心要抓住一条业务主线课程从哪儿来、用户怎么选课、选完之后怎么学、学完之后怎么考核。围绕这条主线把系统拆成两个端来看。学员端关注的是使用体验功能包括注册登录、浏览课程列表、按分类筛选课程、查看课程详情、在线观看课程视频、选课或退课、提交作业、查看作业成绩、浏览公告、修改个人资料。管理端关注的是运营效率功能包括管理员登录、用户管理学员和教师的增删改查、课程分类管理、课程信息管理新增课程、编辑内容、上下架、发布课程视频、布置和批改作业、公告管理、数据统计。这里有一个设计心得功能不要刻意追求多但一定要保证“业务闭环”。也就是说学员端的每一个操作在管理端都要有对应的管理入口管理端的每一项数据变更在学员端都要能看得见效果。比如学员端能提交作业那管理端就必须有作业批改和成绩回写的功能管理端能发布课程那学员端的课程列表就要实时刷新。这种一一对应的业务闭环评审老师一看就知道你确实把需求吃透了。1.3 项目目录结构规划前后端分离怎么组织代码项目结构这个事儿很多人不够重视结果代码写了一堆最后自己都找不到文件在哪。我的习惯是前后端严格分开后端按技术职责分包前端按业务模块分目录。后端Java包结构我用的是经典分层架构controller接口层接收请求并返回结果、service业务逻辑层处理核心业务规则、mapper数据访问层操作数据库、entity实体类对应数据库表结构、dto数据传输对象用于接口入参出参、config配置类放JWT拦截器、跨域配置等、common公共类放统一返回结果、异常处理、工具类。这种分层的核心思想是每一层只负责自己的事情controller不写SQLmapper不写业务逻辑这样出问题的时候定位非常快。前端Vue项目我用的是标准脚手架结构src/api放所有的接口请求封装src/router放路由配置src/store放全局状态管理Vuex或Piniasrc/views放页面组件按模块分子目录比如course、user、order、homesrc/components放公共组件比如导航栏、分页组件、上传组件。views按模块拆分的好处是后续加功能时只需要在对应模块下新增页面不会动到其他代码对后期维护和答辩演示都非常友好。2. 数据库设计与核心表结构详解2.1 数据库设计的第一原则一切围绕业务闭环数据库设计是整个系统的基础这个环节出了问题后面写再多代码都是白搭。我做这类型系统时始终遵循一个原则每一张表必须有明确的业务归属表与表之间的关联关系必须能完整支撑业务闭环。在线课程管理系统最少需要六张核心表用户表user包含学员和管理员用角色字段区分、课程分类表course_type、课程信息表course、选课记录表user_course记录哪个学员选了哪门课、作业表homework包含作业内容和提交状态、作业提交表homework_submit记录学员提交的作业内容和成绩。如果还需要公告功能就加一张公告表notice。一张表对应一个明确的业务对象这是最基本的要求。很多人设计表的时候喜欢把所有字段堆在一张表里看起来省事实际上后续扩展和查询都会非常痛苦。比如把选课记录直接写到用户表里一个用户选多门课怎么存存多个课程ID拼成的字符串那统计课程选课人数的时候你得先把所有用户表数据捞出来再逐条做字符串拆分——这种设计管理端数据统计功能根本写不下去。2.2 核心表结构说明字段设计与关联关系这里我挑最核心的几张表具体说说字段设计。用户表user包括id主键、username用户名唯一约束、password密码密文存储、real_name真实姓名、role角色0表示管理员1表示学员、phone手机号、email邮箱、avatar头像URL、create_time创建时间。角色字段非常关键整个系统的权限控制都依靠它来判断。课程信息表course包括id、course_name课程名称、course_type_id所属分类关联分类表主键、cover封面图URL、video_url课程视频地址、description课程简介、teacher_name授课教师、credit学分、status状态0下架1上架默认1、create_time。这里video_url的字段类型建议用VARCHAR存字符串。选课记录表user_course包括id、user_id学员ID、course_id课程ID、create_time选课时间。选课表是典型的关联表既关联用户又关联课程设计时两个外键字段都要建索引否则数据量大了以后查询性能会受影响。作业提交表homework_submit包括id、homework_id作业ID、student_id学员ID、submit_content提交内容、submit_file_url附件URL、score成绩、comment教师评语、status状态0未提交1已提交2已批改、submit_time提交时间。2.3 建表SQL的几个关键细节与避坑指南建表SQL本身不难但有几个细节我要特别提醒。第一字符集一定要用utf8mb4而不是utf8因为MySQL的utf8字符集不支持一些特殊字符比如emoji表情学员提交作业时万一内容里有表情符号直接报错或者存成乱码实测血泪教训。第二表名和字段名尽量用下划线命名法不要用驼峰因为MySQL在Linux环境下是区分大小写的虽然Windows下不区分但项目最终部署到Linux服务器时才暴露问题就很被动了。第三字段一定要设置合理的默认值比如状态字段默认值设为1正常状态创建时间字段默认值设为CURRENT_TIMESTAMP这样插入数据时少写很多冗余代码。典型的建表SQL参考如下CREATE TABLE course ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 课程ID, course_name varchar(100) NOT NULL COMMENT 课程名称, course_type_id int(11) DEFAULT NULL COMMENT 课程分类ID, cover varchar(255) DEFAULT NULL COMMENT 课程封面图URL, video_url varchar(500) DEFAULT NULL COMMENT 课程视频播放地址, description text COMMENT 课程简介, teacher_name varchar(50) DEFAULT NULL COMMENT 授课教师, credit int(11) DEFAULT 2 COMMENT 学分, status tinyint(4) DEFAULT 1 COMMENT 状态0下架 1上架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_type_id (course_type_id), KEY idx_status (status) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT课程信息表;注意ENGINEInnoDB这是支持事务的存储引擎课程管理会涉及选课、退课等操作必须保证事务一致性。idx_type_id和idx_status这两个索引特别关键因为课程列表页最常见的就是按分类查、按状态筛没有索引的话课程量大时接口响应会明显变慢。3. 后端SpringBoot核心模块实操3.1 项目初始化与依赖配置Maven怎么选、版本怎么避坑创建一个SpringBoot项目最省事的方式就是用Spring InitializrIDEA内置或start.spring.io网站选好JDK版本、语言、构建工具然后勾选需要的依赖项目骨架就自动生成好了。依赖配置方面我这个项目用的核心依赖有这些spring-boot-starter-webWeb开发基础、mybatis-plus-boot-starterMyBatis增强插件自带分页和条件构造器、mysql-connector-j数据库驱动、lombok自动生成Getter/Setter减少冗余代码、jjwt生成和解析JWT令牌用于登录认证、hutool工具类库处理日期、加密、随机数等。版本上有个重要经验SpringBoot的版本尽量选稳定的正式版不要追最新版本。GroupId为com.baomidou的MyBatis-Plus在旧版本和新版本的配置方式有差异版本选择尽量与其他依赖保持兼容选大家都在用且文档资料最多的稳定版最稳妥。3.2 JWT登录认证与权限控制为什么不用Session登录认证是几乎所有Web系统都绕不开的模块也是答辩时老师必问的一个点。我建议用JWTJSON Web Token而不是传统的Session方案理由有三个第一JWT是无状态的服务器不需要保存会话信息天然适合前后端分离架构第二JWT可以携带用户基本信息比如用户ID、用户名、角色后端接口直接解析令牌就能拿到当前用户身份不需要每次查数据库第三移动端或者第三方系统接入时JWT的适配性比Session好得多。JWT的流程是用户登录成功后后端生成一个包含用户信息的令牌返回给前端前端存储到localStorage或sessionStorage中每次请求时在请求头的Authorization字段带上这个令牌后端通过拦截器统一校验。SpringBoot实现JWT只需要三步写一个生成令牌的工具类写一个拦截器用来校验请求头中的令牌再把这个拦截器注册到WebMvcConfigurer配置类中。拦截器校验的核心代码大致是public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录和注册接口 if (request.getRequestURI().contains(/user/login) || request.getRequestURI().contains(/user/register)) { return true; } String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new RuntimeException(未登录请先登录); } // 解析令牌失败会抛出异常由全局异常处理器捕获 User user JwtUtils.parseToken(token); request.setAttribute(currentUser, user); return true; } }这里有个细节要注意request.getHeader(Authorization)取出来的令牌是前端自己拼接的字符串项目中约定前端统一传递完整令牌后端直接校验或解析这样前后端配合时不会出现不一致的问题。权限控制方面我用一个简单但有效的方案用户表的role字段区分管理员和普通学员管理员接口在业务逻辑层额外判断一次角色不是管理员直接返回“无权限”提示。这套方案的好处是代码简单、容易理解答辩时也能讲得清楚。如果项目里需要更细粒度的权限可以引入Spring Security或Sa-Token框架但对课程管理系统来说角色判断已经完全够用了。3.3 课程管理核心接口设计从增删改查到业务规则课程管理模块算是这个系统的业务中轴线接口设计好不好直接决定前端写起来顺不顺手。我按照资源管理的思路统一设计了一套RESTful接口GET /api/course/list获取课程列表支持分页、按分类筛选、按关键词搜索GET /api/course/detail/{id}获取课程详情POST /api/course/add新增课程PUT /api/course/update修改课程信息DELETE /api/course/delete/{id}删除课程PUT /api/course/changeStatus上架或下架课程。新增课程的后台处理逻辑我会强调一个点除了把前端传过来的表单字段保存到数据库以外还要顺手处理三个事情。一是对课程名称做非空和长度校验二是封面图和视频地址要做HTTP链接格式校验防止恶意数据入库三是新增课程时把状态默认置为上架方便前端马上能看到效果。这些看似细枝末节的处理其实是区分“会写代码”和“写得专业”的分水岭。3.4 MyBatis-Plus与分页查询实战现在写Java后端很少有人还去手写一大堆XML映射文件了MyBatis-Plus基本是标配。它对单表CRUD提供了现成的方法比如BaseMapper里面自带selectById、selectList、insert、deleteById等配合条件构造器LambdaQueryWrapper能搞定绝大多数查询场景。拿课程列表分页查询来做示例代码参考如下public PageResultCourse getCoursePage(int pageNum, int pageSize, String keyword, Integer typeId) { PageCourse page new Page(pageNum, pageSize); LambdaQueryWrapperCourse wrapper new LambdaQueryWrapper(); // 关键词模糊搜索课程名称 if (StringUtils.hasText(keyword)) { wrapper.like(Course::getCourseName, keyword); } // 按分类筛选 if (typeId ! null) { wrapper.eq(Course::getCourseTypeId, typeId); } // 状态为上架的课程才能被学员端看到 wrapper.eq(Course::getStatus, 1); // 按创建时间倒序最新的课程排前面 wrapper.orderByDesc(Course::getCreateTime); PageCourse result courseMapper.selectPage(page, wrapper); return new PageResult(result.getRecords(), result.getTotal()); }这里要重点解释一下orderByDesc(Course::getCreateTime)这个排序条件。列表页默认按创建时间倒序排这是用户最常见的预期——新课程排在前面。很多新手不写排序结果课程列表每次刷新顺序都不稳定因为MySQL的InnoDB引擎默认查询顺序并不保证按主键递增数据量大了以后同一个查询可能会返回不同的顺序。这是个很隐晦的坑建议从一开始就养成写排序条件的习惯。分页插件需要在配置类里注册MyBatis-Plus用MybatisPlusInterceptor配合PaginationInnerInterceptor来实现物理分页注册之后Page对象就会自动拼接LIMIT语句不需要手写分页SQL。4. 前端Vue核心功能实现4.1 Vue项目初始化和路由配置Vue这块我默认用的是Vue 2 Element UI组合为什么不用Vue 3说实话Vue 3 Element Plus确实是新趋势但Vue 2的资料量和使用人群仍然很大很多网上的教程和现成组件都是Vue 2写的毕设阶段遇到不会的问题时查资料方便比什么都强。当然这个选择不是绝对的如果你对Vue 3很熟悉直接用Vue 3 Element Plus也没问题。创建Vue项目用官方脚手架npm install -g vue/cli vue create course-frontend创建过程中会问你要不要装Router、Vuex选上CSS预处理器选Less或者直接不用也可以装完之后cd进项目目录先装Element UI和Axiosnpm install element-ui --save npm install axios --save路由配置是这个项目的核心。登录注册页要用独立路由不套任何布局系统内部页面统一走一个主布局组件里面放侧边导航栏和顶部栏子路由放在对应模块下。配置好以后用Vue Router的全局前置守卫做登录拦截——用户访问受保护的路由时检查localStorage里有没有token没有就跳转到登录页。这个拦截逻辑配合后端的JWT拦截器就是完整的前后端双重登录校验。4.2 视频播放与课程内容展示m3u8格式的处理经验在线课程管理系统里视频播放是最能体现项目亮点、也最容易出问题的功能。如果课程视频是普通的MP4文件直接用HTML5的video标签就能播放但实际开发中很多课程视频是m3u8格式的流媒体文件Vue原生video标签是播不了的——m3u8是HLS协议HTTP Live Streaming的索引文件浏览器原生不支持这种格式的解析。处理方案有两种主流选择。第一种是使用video.js配合videojs-contrib-hls插件兼容性好集成简单适合中小型项目。第二种是使用西瓜播放器西瓜视频开源的一款HTML5播放器对m3u8的支持做得很好API设计也比较简洁界面颜值高很适合课程系统这类需要良好视觉观感的场景。我用西瓜播放器举例核心代码大概是import Player from xgplayer; const player new Player({ id: video-container, url: courseInfo.videoUrl, height: 450, width: 100%, playsinline: true, autoplay: false });实际做的时候有两点提醒视频地址一定要让管理端上传时规范存储最好是完整可访问的URL本地演示如果视频文件很大要注意加载等待时间建议用相对小体积的视频做演示效果会好很多。另外如果是本地视频文件走相对路径要确认视频文件确实能被后端接口访问到最好配置一个静态资源映射目录。4.3 前后端联调与Axios封装前后端分离项目里前端每个页面都要发HTTP请求如果每次都手写一遍Axios逻辑代码会非常冗余所以必须对Axios做统一封装。我通常把封装放在src/api/request.js这个文件里核心做三件事。第一统一设置请求基础地址这样所有接口路径只需要写相对地址。第二请求拦截器里统一从localStorage取token加到请求头的Authorization字段。第三响应拦截器里统一处理错误——如果后端返回的code表示未登录比如401直接跳转登录页并给出提示如果是其他业务错误弹出对应的错误信息。封装好之后每个模块的接口单独放在一个文件里比如课程相关的接口放在src/api/course.jsimport request from ./request; export function getCourseList(params) { return request({ url: /course/list, method: get, params }); } export function addCourse(data) { return request({ url: /course/add, method: post, data }); }这样做的最大好处是页面组件里只需要调用getCourseList(params)这一个函数不需要关心HTTP细节。后端的接口有变动时只需要改course.js这一个文件不用全项目搜索改调用方式。这就是前后端分离里“API层集中管理”的典型实践答辩时讲出来也很加分。联调阶段最容易出的问题就是跨域CORS。Vue开发服务器默认跑在8080端口后端接口跑在8081端口前端直接发请求会被浏览器拦截。解决办法有两种最推荐的是在后端写一个全局跨域配置类实现WebMvcConfigurer接口重写addCorsMappings方法把允许的来源、方法、请求头都配置好。开发阶段也可以在Vue的vue.config.js里配置devServer代理把/api开头的请求转发到后端地址。两种方式可以都配置上这样不管是本地开发还是打包上线都不会有跨域问题。5. 部署、答辩与常见问题实操指南5.1 本地跑通到部署上线从Jar包到云服务器的完整流程项目写完之后很多时候是要给老师做演示的。有的同学只会用IDEA直接点运行换一台电脑或者打包部署到服务器就抓瞎这其实是答辩和验收时最大的硬伤。我建议至少要掌握一次完整的打包部署流程。后端的打包比较简单。在IDEA右侧Maven面板执行package命令就会在target目录下生成一个可执行的Jar包。在服务器上执行java -jar course-system.jar就能启动服务。如果不想手动敲命令还可以用nohup java -jar course-system.jar log.out 21 这个命令让服务在后台运行服务器断开连接服务也不会停。后端Jar包默认读取的application.yml数据库连接、端口号等配置都从这里面读取。上线时要把localhost改成云服务器的数据库地址还要确认服务器的3306端口对公网开放。前端部署稍微复杂一点。先在本地执行npm run build会生成一个dist目录里面是纯静态的HTML/CSS/JS文件。把这个目录下的内容放到Nginx的html目录下然后配置Nginx反向代理把/api开头的请求转发到后端Jar包所在的端口。Nginx配置里值得注意的核心配置段是location /api需要配置proxy_pass指向后端服务地址。部署环境这块队长建议在Windows本地先练手跑通再去买一台Linux服务器操作。Windows上装MySQL和JDK都比较简单Nginx也有Windows版本跑一遍流程理解了全貌再去Linux服务器上操作就不容易卡住了。另外服务器的安全组要确认开放相应端口不然浏览器访问不到这个问题排查起来非常折磨人。5.2 常见问题与排查技巧报错不要慌先看日志这个系统跑起来之后下面几个问题是出现频率最高的。启动报错Access denied for user rootlocalhost大概率是数据库密码配置错误优先检查application.yml里的数据库用户名和密码以及确认MySQL服务有没有启动。启动报错Port 8080 was already in use是端口被占用了要么改server.port配置要么找到占用进程并结束它。前端npm install报版本冲突或者权限错误可以考虑先删掉node_modules目录和package-lock.json文件再重新执行npm install顺便用npm cache clean --force清理一下缓存。接口请求返回Network Error排查跨域配置是否生效以及前后端是否都启动了。登录接口总是返回失败先确认密码存储方式——如果存的是密文登录逻辑里要对输入密码做同样的哈希处理后才能比对得出来。排查这些问题的通用心法只有一句先看日志。后端接口报错会把堆栈信息打印在控制台里前端请求报错会打印在浏览器开发者工具的Console和Network面板里。不要对着代码瞎猜让日志告诉你答案这是程序员最重要的基本功之一。5.3 答辩高频问题与应对策略从“做了什么”到“为什么这么做”答辩是整个毕设环节里最容易翻车也最容易加分的一关。很多同学项目做得不错但是紧张得讲不出来或者只知道“我做了什么”说不清“我为什么这么做”老师一问原理就卡壳。高频问题基本集中在几个方向。SpringBoot的自动配置原理是什么你只需要抓住核心点SpringBoot通过EnableAutoConfiguration注解配合META-INF/spring.factories文件里的配置类按条件注解比如ConditionalOnClass自动装配Bean省去了手动XML配置。这个问题答到这一层就已经超出绝大多数人的水平了。JWT为什么比Session更适合前后端分离从无状态这个角度回答服务端不需要保存会话信息每次请求在拦截器里解析令牌就能获取用户身份Session方案需要服务端保存会话记录前后端分离时跨域场景下处理比较复杂。MySQL索引为什么能提高查询效率可以类比字典的目录没有索引就是每一页从头翻到尾全表扫描有索引就是通过目录直接翻到目标页B树查找时间复杂度从O(n)降到O(log n)级别。MyBatis-Plus和MyBatis的区别是什么MyBatis-Plus是MyBatis的增强工具在MyBatis的基础上提供了通用的Mapper模板方法、条件构造器、分页插件等能力单表操作基本不用写SQL只做增强不做改变引入后不会影响原有功能。还有一个老师特别喜欢问的你的系统有哪些可以优化的地方这个问题答好了很加分可以从缓存、事务、性能、部署几个维度来谈。比如课程列表是访问频率最高的接口可以引入Redis做缓存把课程列表缓存到内存里管理员修改课程后主动更新缓存这样能大幅降低数据库压力。比如选课操作涉及用户选课记录创建和课程选课人数增加两个操作要加Transactional事务注解保证一致性。比如全站可以加统一的日志切面记录操作日志。回答这类问题的时候重点是体现出你思考过系统的不足并且知道业界通用的解法是什么。最后把项目报告和数据库设计说明文档整理好。报告里要包含选题背景和意义、需求分析、系统设计功能结构图、数据库ER图、接口设计、系统实现核心功能截图加说明、系统测试测试用例表加结果、总结与展望。数据库设计部分把每张表的字段含义、关联关系、索引设计讲清楚代码部分不要大段贴代码而是贴关键代码并配上创作思路说明。这个系统做完之后我最大的感受是它的难点不在于某个单一技术有多高深而在于怎么把前端、后端、数据库、部署运维这条全链路串起来。每一步单独拎出来可能都很基础但当你把它们组合成一个能跑、能用、能演示的完整系统时你对Web开发的理解会上一个台阶。做毕设的过程其实是磨心态的过程——接口调不通、依赖装不上、服务器部署失败都是家常便饭但每解决一个问题你对这套技术栈的掌控感就强一分。按照上面这套思路从数据库到后端从后端到前端从前端到部署一步步走下来你收获的绝对不仅仅是一个能过答辩的项目。
返回列表