在线课程管理系统这类项目,在Java Web方向的项目集里一直是高频常客。差不多每隔一段时间就会有同学拿同一个题目来问,但用的技术栈版本五花八门。手里这套基于SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0的在线课程管理系统源码,算是把前后端分离的主流搭配都集齐了,还附带完整文档和初始化SQL脚本,拿来练手、做毕业设计、或者作为企业内部培训系统改造的原型,都比较合适。
这套系统本身的定位很清晰:面向管理员、教师、学生三类角色,提供课程信息管理、课时内容发布、在线选课、成绩录入、公告通知等一整套在线教学管理的基本闭环。从实际复现的角度看,它不是一个“只可远观”的showcase,而是能直接跑起来、能改、能部署的真实项目。这篇文章我会从技术选型为什么会用这套组合开始讲,再把数据库设计、后端接口实现、前端页面联调、部署过程常踩的坑逐层拆开,最后分享几个我实际跑项目时遇到的报错和解决办法。不管你是准备拿这套源码做二次开发的开发者,还是想把类似系统从零搭起来的初学者,这篇文章都值得先收藏再慢慢看。
1. 项目概览与技术选型思路
1.1 这个项目到底解决了什么问题
很多人第一次接触“在线课程管理系统”这个名字,会觉得这不就是个普通的增删改查吗?想法没错,但在真实项目里,课程管理涉及的角色权限、选课规则、成绩状态流转、课程与教师学生的关联关系,这些业务点组合起来后,复杂度会比想象中高不少。这项目源码里最值得看的,倒不是某一个单独功能有多炫,而是它把一套完整业务流程从数据库到接口再到页面整体串了起来。
实际打开源码你会发现,它把系统分为管理员端、教师端、学生端三个入口。管理员这一侧,重点负责用户管理、课程审核、数据统计这类基础底座功能;教师端核心是课程资源维护、课时安排、选课名单查看、成绩录入;学生端则是浏览课程库、选择课程、查看已选课程与成绩。整条链路从“开课”到“选课”再到“出成绩”,都在系统内闭环完成,而不是各自散落的孤立页面。
我拿到源码后第一件事,是先把初始化SQL导入MySQL数据库,然后把后端项目用IDEA打开,前端用VS Code或者WebStorm打开,分别启动后发现能直接登录操作。这种“开箱即用”的体验对二次开发非常关键,省去了从零配环境的折腾,也说明这套源码在可持续性上是用心整理过的,不是那种缺胳膊少腿的半成品。
1.2 这套技术栈为什么是标准答案
技术选型方面,SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0这套组合,在当前Web开发领域确实属于主流中的主流。SpringBoot2自不用说,它在简化Spring配置、内嵌Tomcat、快速构建微服务方面有着非常成熟的生态沉淀。很多刚入行的开发者直接学SpringBoot3,实际上去了不少公司还在维护SpringBoot2的老项目,所以这套源码用SpringBoot2反而有很高的学习兼容性。
Vue3前端框架,加上Element Plus组件库,既能做出像样的后台管理界面,也符合目前大多数公司前端招聘的技能要求。MyBatis-Plus则是MyBatis的增强工具,最大的好处是单表CRUD几乎不用手写SQL,内置的IService和BaseMapper提供了通用方法,开发效率比原生MyBatis高出一截。MySQL8.0就更不用多说,作为开源关系型数据库的标杆,在这套系统里负责所有业务数据的持久化。
比较常见的问题,是有同学问“为什么不用SpringCloud?为什么不用Redis做缓存?”之类。实际这种规模的课程管理系统,单体应用完全足够,硬上微服务只会把事务一致性、部署复杂度拉高一大截。项目里也用了JWT做登录状态管理,很多接口有分页查询,这些都属于在合理成本内提升性能的手段,不会为了炫技而引入过度设计。
2. 系统设计与数据库建模
2.1 角色权限模型的设计精髓
在线课程管理系统最核心的底层支撑,是“用户-角色-权限”这套模型。通常的简单做法是给用户表加一个role字段,值为admin、teacher或者student,然后前端根据角色去显示不同菜单。这套源码做得更规范一些,虽然主体还是角色判断,但权限控制点不止存在于菜单层面,后端接口也通过拦截器做了保护,避免出现“学生直接调后端接口把自己变成管理员”这种低级别安全问题。
在后端实现里,Controller层的方法没有简单放行,而是配合自定义拦截器(HandlerInterceptor)对请求路径做角色校验。比如/admin/**路径下的接口,必须登录用户角色为管理员才能访问;/teacher/**路径下的接口,至少得是教师或管理员。这样即使有人绕过前端页面,直接拿着URL去请求接口,也会被拦截下来。
这里我特别建议你读一下源码里的JWT工具类和拦截器逻辑。JWT令牌在用户登录成功后生成,把用户ID、角色、过期时间等信息编码进token里,后续每次请求在请求头里携带token,后端拦截器解析校验。这个过程中有几个容易出错的地方,比如token过期时间设置过短、密钥硬编码在配置文件中,或者没处理token解析失败的异常。源码里这些点多数都做了兜底,但你在二次开发时如果新增了模块,记得要同步考虑接口权限配置。
2.2 数据库表结构设计细节
在线课程管理系统数据库设计,表数量通常在十张到二十张之间。以这套源码为例,核心表包括用户表、课程表、课时表、选课记录表、成绩表、公告表、课程分类表等。MySQL8.0在字符集上默认就是utf8mb4,对中文内容和emoji符号都能友好存储,建表时要注意表引擎用InnoDB,事务支持和外键约束都靠它支撑。
用户表字段一般包含id、username、password、real_name、role、phone、email、avatar、status、create_time等。密码字段必须加密存储,源码里用的是BCrypt加密,这一点做得很扎实。课程表则包含course_name、course_code、credit、teacher_id、category_id、classroom、start_time、end_time、max_student、current_student、status等字段。课时表用于保存每节课的详细安排,比如课时主题、上课时间、上课地点、教学内容简介。
选课记录表是关联学生和课程的一张中间表,字段会有student_id、course_id、select_time、status等。成绩表则记录学生的最终成绩,需要关联选课记录表的id,这样能保证一份选课记录对应一条成绩记录,避免同一名学生重复出成绩。整套表结构在逻辑上比较规矩,没有明显的冗余。对要做课程类管理系统的同学来说,这个库表设计可以直接拿去做新项目的参考模板。
2.3 表关系与关键索引设计
表之间的关联关系,本质上是通过外键逻辑关联实现。我就拿课程表和教师举例:课程表里的teacher_id字段关联用户表的id,查询课程列表时需要把用户的real_name字段JOIN出来,显示成“授课教师”姓名。选课记录表同时关联学生和课程,查询某个学生的选课列表时,先查选课记录表拿到course_id集合,再关联课程表取回课程详情数据。
关于索引,源码在常用的查询字段上已经建好了索引,比如用户表的username、课程表的course_name、选课记录表的student_id和course_id。这里有个实际开发中经常被忽略的问题:如果表的数据量从几千条增长到几十万条,没有索引的查询会明显变慢。你可以自己用EXPLAIN命令查看一下SELECT语句的执行计划,看看是否走了索引。如果发现选课查询出现了全表扫描,就应该考虑加复合索引,比如(student_id, course_id)这样的组合索引。
从数据库设计的角度来看,还有一个地方值得注意:物理外键在许多互联网项目中会被有意去除,而用代码层面去保证数据一致性。这主要是因为物理外键在高并发插入、更新时会有锁竞争问题,也会让表结构变得僵硬。这套源码里设计了逻辑关联关系,但没有到处加FOREIGN KEY物理约束,属于行业里常见的做法。如果你要在此基础上扩展,一定要记得在删除课程之前,检查该课程是否已有选课记录,避免产生孤儿数据。
3. 后端核心实现与细节拆解
3.1 SpringBoot项目结构与分层思想
后端项目采用的是标准的Controller-Service-Mapper三层架构。项目包结构一般分为controller、service、mapper、entity、dto、config、common、utils等目录。这个分层看起来简单,实际上是我建议所有初学者认真琢磨的地方。Controller层只负责接收请求和返回结果,不写任何业务逻辑;Service层处理业务规则、事务边界;Mapper层通过MyBatis-Plus的BaseMapper接口完成持久化操作。
源码里Service层普遍继承了MyBatis-Plus的IService接口,实现类继承ServiceImpl。这样做的好处是:像save、updateById、removeById、getById这些通用方法不需要手写SQL,直接调用即可。你自定义的业务方法则写在Service接口里,由实现类去具体实现。这套模式在多数Java后端项目里是通用的,学会了之后,无论你后续接触到微服务还是中台项目,这套分层觉悟都能直接复用。
Controller类返回值方面,源码封装了统一响应结构Result,内部包含code、message、data三个字段。前端拿到响应后根据code判断请求是否成功。比如code为200表示成功,500表示业务异常,401表示未登录或token失效。这种统一封装最大的好处是,前端可以对响应做统一拦截处理,比如遇到401自动跳转回登录页,而不是在每个请求回调里都重复写错误判断。我在前后端联调初期,就经常因为某些接口直接返回了裸数据,导致前端处理逻辑被迫写两套,那时才体会到统一返回结构的好处。
3.2 用MyBatis-Plus少写一半SQL
MyBatis-Plus在项目里的存在感非常强。基础的增删改查、分页查询、条件构造,几乎都被它包揽了。以管理员管理用户列表为例,直接创建一个QueryWrapper对象,传入用户实体,设置需要过滤的条件,再配合Page对象调用分页查询方法,即可返回分页数据,无需手写XML。
再比如课程列表的分页搜索,可能是根据课程名模糊搜索、按分类ID过滤、按教师ID过滤、还要按开课时间排序。标准的实现方式是使用LambdaQueryWrapper,构造like条件、eq条件和orderByDesc排序条件,然后封装进Page请求参数中。LambdaQueryWrapper的好处是直接用实体字段引用,不会出现字符串写错导致查询异常的问题,同时IntelliJ IDEA里面还能自动补全。
不过有一点也要提醒,MyBatis-Plus适合单表操作和简单的多表查询。如果涉及到复杂报表JOIN、子查询、动态SQL片段复用,建议还是新建XML映射文件,把SQL手写成明确的SQL语句。这套源码在管理后台统计模块的部分地方就用了手写SQL,比如统计每门课程的选课人数、统计教师开课数量,这些都是多表聚合操作,MyBatis-Plus通用Mapper很难覆盖所有场景。
还有一点值得学:MyBatis-Plus的字段自动填充功能。实体类里的create_time和update_time字段,在插入和更新时往往不想在每个业务代码里手动set当前时间。源码通过实现MetaObjectHandler接口,在插入时自动填入当前时间、更新时自动修改更新时间。这一招在实际项目里非常省事,也避免了开发人员忘set时间字段导致数据不准确的情况。
3.3 JWT认证与原理解析
JWT认证是很多Web项目的标配,这套源码也采用了类似方案。登录接口接收用户名和密码,先通过MyBatis-Plus根据username查询用户记录,然后用BCrypt对密码进行校验。校验通过后,使用JWT工具类生成token,token中会写入当前用户的ID、角色以及过期时间戳。
客户端拿到token之后,存放在本地存储里,之后每一个需要认证的API请求,都会在请求头中添加Authorization: Bearer token这样的格式。后端拦截器会从请求头中取出token,调用工具类解析并验证其合法性以及是否过期。如果解析失败,直接返回未认证的状态信息。
这里有个实际使用中值得注意的细节:token无状态性使得服务端无法主动让某个token失效。所以如果要实现“修改密码后所有登录态失效”或者“管理员封禁某个用户”时,单靠token机制是不够的。常规的解决办法是引入Redis黑名单或者把token版本号存到数据库里做比对。我修改这套源码时,就在用户密码修改时额外生成了一对新的token,而不是复用旧token,这个思路你可以参考。
3.4 业务功能的关键实现逻辑
从业务功能来拆解,课程发布流程和选课流程最值得你通读一遍。教师在教师端创建课程时,系统会往课程表插入一条记录,默认状态为待审核或者立即上架,具体看产品配置。学生端展示的课程通常需要状态为上架,并且当前选课人数尚未达到上限。
选课操作实现时,需要注意并发超选问题。比如某门课程限选50人,如果同时有60个学生在最后一刻提交选课请求,假如代码逻辑是“先查当前人数,不够50就插入选课记录,并把人数加一”,这会出现并发问题。真正稳妥的做法是在事务里给课程记录加悲观锁,使用SELECT ... FOR UPDATE锁定课程行,再检查人数并更新;或者使用乐观锁,在更新课程人数时加上版本号判断。这套源码中部分模块用了简单判断,如果你要把系统改造上生产环境,这里建议按乐观锁思路再加固一层。
成绩发布功能关注的数据一致性同样不低。教师在成绩表插入成绩记录时,需要确保对应的选课记录存在,并且教师与该课程的关联关系无误。否则可能出现往一个不属于自己课程的选课记录上录入成绩的情况。源码在保存成绩的Service方法里做了二次校验,核心思路是构造查询条件,先查询选课记录是否存在,然后再执行插入或更新。
3.5 文件上传与静态资源处理
在线课程管理系统里,教师上传课件、上传课程封面图是常见需求。源码里包含文件上传接口,用MultipartFile接收前端传来的文件,然后保存到本地指定目录,或者返回对应的访问URL给前端。为了便于管理,文件上传路径一般会在配置文件里设置一个基础目录,然后按日期目录继续分层,防止单个文件夹内文件过多。
这部分常见的坑点是:本地开发时上传的文件路径是绝对的,比如Windows下的D:/upload/course/xxx.jpg,但Nginx或者打包部署到Linux服务器后,文件路径可能变成/usr/local/course/upload/xxx.jpg。如果你直接把绝对路径存到数据库里,页面上能访问,但迁移环境就要改数据。更推荐的做法是数据库里只保存相对路径,比如/upload/course/2024/09/01/10001.jpg,再由服务器配置静态资源映射到对应目录。源码里也基本是按这个思路处理的,你在二次开发时如果新增了文件上传功能,务必沿用这个约定。
4. 前端Vue3实现细节
4.1 前端工程结构与组件化思路
前端部分用的是Vue3标准工程结构,入口文件main.js负责创建应用实例并挂载Router、Pinia、ElementPlus等核心依赖。src目录下通常分为api、router、store、views、components、utils等层级。api目录里按模块封装axios请求函数,每个页面只管调用API函数,而不是直接散落请求地址。router目录配置动态路由和路由守卫,utils目录里放token存取、日期格式化等工具函数。
Element Plus组件库的使用是这套系统前端体验提升的关键所在。表格用el-table、表单用el-form、弹出确认框用el-messageBox、表单校验规则用el-form的rules属性,这些组件的组合使用能快速实现中后台系统中高频出现的信息管理和交互场景。路由守卫也是重点,前端在跳转到每个页面之前,会先检查当前路由meta信息中要求的角色,如果不是目标角色则跳转到登录页或不可访问页。
我个人比较推荐的阅读源码顺序是:先看api目录下的request.js,理解axios实例如何封装拦截器;然后看router目录下的index.js,理解路由守卫逻辑;接着选一个简单模块从api函数到views页面再到Component组件通读一遍。不要把时间浪费在一开始就从头读到尾,那样效率不高,容易迷失在细节里。读懂了前端与后端的联调方式,后面自己加新功能会顺很多。
4.2 核心页面与交互流程拆解
登录页是整个系统前端流程的起点。登录接口调用成功后,会把token和用户基本信息保存到localStorage或者Pinia store中,然后根据用户角色跳转到对应的首页布局。管理员、教师、学生三端的布局结构里,侧边栏菜单菜单项都是按角色动态生成的,这主要借助路由meta中配置的角色权限字段,结合前端菜单渲染时的权限判断。
课程列表页是学生端的核心页面。页面顶部是搜索栏,支持按课程名称关键词、课程分类、开课学期等条件进行搜索。列表主体用el-table展示课程信息,包括课程名称、授课教师、学分、已选人数/限选人数、上课时间、操作按钮等。点击选课按钮后,前端会弹出二次确认框,确认后调用选课接口。针对已选课程状态,操作按钮要动态变为“已选”或禁用状态,这一般由后端返回的数据字段给出状态。
教师端课程管理页面不同于学生端,需要展示自己名下创建的所有课程,并且支持新增、编辑、上下架操作。新增课程时使用el-dialog弹出完整表单,表单里包含课程编码、课程名称、学分、分类、限选人数等输入项。课程分类选择可以使用el-select下拉框,分类数据从后端接口动态加载,而不是写死在前端代码里。
4.3 前端与后端联调的关键细节
前后端联调容易出问题的地方,第一个就是跨域。开发环境下,前端运行在localhost:5173这类Vite默认端口,后端运行在localhost:8080,两者端口不同,会产生跨域问题。源码后端配置类里对跨域做了处理,允许来自前端的请求携带认证信息。如果你在实际部署时把前端打包后的dist目录放在Nginx里,同时把后端接口通过Nginx反向代理转发,那就应该首选Nginx配置代理的方式解决跨域,后端配置相应的CORS规则反而可能出现重复允许的问题。
第二个常见问题是axios请求超时时间设置过短。某些接口因为涉及复杂的数据库关联查询,响应时间可能超过500毫秒甚至达到1秒以上。如果request.js里把timeout设置为1000毫秒,不定期就会出现“请求超时”的误报。在实际排错中,我看到过的案例不在少数,所以建议把timeout调整到10秒左右,给慢SQL和网络波动留足余地。
第三个细节是文件上传时的表单请求格式。普通JSON接口的Content-Type是application/json,但文件上传接口必须使用multipart/form-data格式。axios在post文件时需要把文件放入FormData对象中,不要把文件对象直接扔到JSON字段里去传,否则后端接收到的会是空值。这个错误我自己在初期开发时也犯过,调试了挺久才找到原因。
5. 环境配置与部署实战
5.1 本地开发环境准备
先明确一下本地跑通这套源码所需的基础环境:JDK 1.8及以上,Maven 3.6及以上,MySQL 8.0,Node.js 14以上(推荐16或18),以及一个顺手的前端包管理器npm或yarn。SpringBoot2.7.x版本对JDK8和JDK11都兼容,所以不需要把JDK升到17或更高,直接沿用稳定版本最省事。
拿到源码后,先修改后端配置文件里的数据库连接信息。我习惯把配置拆成application.yml和application-dev.yml,开发环境单独一份,生产环境单独一份,避免在切换环境时来回改配置。数据库账号、密码、URL中的数据库名要替换成你自己的。初始化SQL脚本里如果包含建库语句,注意先执行CREATE DATABASE再切换进去执行剩余脚本,如果用Navicat直接导入整个SQL文件,通常也能一次成功。
前端方面,在项目根目录执行npm install安装依赖。这一步经常出现网络慢或依赖版本冲突的问题。如果npm安装失败,可以尝试设置国内镜像源,或者使用cnpm、pnpm等工具。依赖安装成功后,执行npm run dev启动前端开发服务。前端默认访问端口如果是5173或3000,你可以结合后端配置文件中允许跨域的源地址,保持一致。
5.2 数据库初始化与账号说明
初始化SQL脚本一般会插入管理员账号、测试教师账号、测试学生账号的初始数据。密码字段在SQL里是BCrypt加密后的字符串,所以不能直接用明文去数据库里改密码。我之前见过有人图方便,直接在数据库里把密码改成明文,结果登录接口校验时通过不了。BCrypt加密逻辑会把明文密码和数据库中的密文进行比对,而你直接改成明文,加密验证自然匹配不上。
推荐的做法是:用源码里已有的初始账号登录,如果你要修改密码,就通过系统自带的修改密码功能操作,或者写一个简单的测试类调用BCrypt的encode方法生成密文,再替换数据库里的值。我个人常用后一种方式,快速且不会破坏原有逻辑。默认的管理员账号密码在项目文档里一般都有说明,如果没有,直接查user表里role字段为admin的记录,然后把初始密码重置成你想要的密文即可。
还有一个更隐蔽的坑需要注意:MySQL8.0的密码加密方式默认是caching_sha2_password,而某些老版本的JDBC驱动不一定支持这种认证方式。源码里使用了MySQL8.0,对应版本的数据库驱动通常没问题,但如果你在连接数据库时遇到Public Key Retrieval is not allowed之类的报错,在JDBC连接参数里加上allowPublicKeyRetrieval=true&useSSL=false基本就能解决。
5.3 部署到服务器的完整步骤
生产部署其实并不复杂。后端通常在服务器上先执行Maven打包命令,跳过测试,生成jar包。这个jar包本身就是内嵌Tomcat的独立运行单元,在服务器上执行nohup java -jar 项目名.jar --spring.profiles.active=prod > app.log 2>&1 &就能后台运行。打包之前有一个关键检查点,就是确保配置文件里的数据库连接用的是生产库地址和账号,而不是本地地址。
前端部署则要先执行npm run build,把源码编译成dist目录下的纯静态文件。然后在Nginx的配置中,把根路径指向dist目录,再把接口请求代理到后端服务地址。一个典型的Nginx配置会包含两个location块,一个用于处理静态页面,一个用于将/api/前缀的请求转发到后端。部署完成后需要清理浏览器缓存并刷新页面,不然可能出现JS、CSS资源更新后但浏览器加载的还是旧版本的问题。
端口开放和防火墙规则也是新人容易吃亏的地方。服务器如果开启了防火墙,记得放行前端站点端口以及后端服务端口。如果想用域名访问,在Nginx配置server_name里填写你的域名,并保证域名已经解析到服务器公网IP。如果后端要对外提供文件访问能力,还要配置静态资源映射块,并确保上传目录对运行Nginx的用户有读取权限。
6. 常见问题与排查技巧实录
6.1 启动阶段高频报错
这套源码我在不同机器上跑过很多次,最常见的启动报错有两个。第一个是端口占用。SpringBoot默认端口一般为8080,如果你本机已经跑了其他项目,启动时会直接提示端口被占用无法启动。解决办法很简单,杀掉占用该端口的进程,或者在后端配置里换个端口。第二个是数据库连接失败,检查配置文件里的数据库URL、账号、密码是否完全正确,特别是密码中包含@或#这类特殊字符,在YAML配置文件中需要进行转义处理。
还有一个容易被忽略的问题:如果你用的是JDK17而项目是SpringBoot2.7以前的版本,可能在启动时遇到一些模块反射相关的报错。这是因为SpringBoot2早期版本为了兼容JDK8和JDK11做的适配在JDK17上不够完善。遇到这种情况,要么把JDK版本换回8或11,要么升级SpringBoot版本并同步检查相关依赖兼容性。我自己更推荐直接用JDK8跑这套源码,最稳,不用做额外的兼容返工。
6.2 联调阶段常见接口异常
前后端联调时有个高频问题:请求发送过去后后端返回403。这个一般是Spring Security或者拦截器配置的权限控制规则生效了。如果当前操作是管理员模块,请求头的token中角色却是学生,就会触发权限拒绝。遇到403先不要急着改后端代码,先打开浏览器开发者工具检查当前登录用户的角色和请求路径,确认访问的资源是否远超自身权限。
另一个比较多见的问题是Invalid bound statement (not found),翻译过来就是MyBatis找不到Mapper对应的SQL语句。排查方向首先是确保Mapper接口能被Spring扫描到,其次检查对应的Mapper XML文件路径是否和Mapper接口在同一个命名空间下。使用MyBatis-Plus时,如果只是调用BaseMapper内置的通用方法,一般不会出现这个问题,但如果你在Mapper接口里新增了自定义方法,却没有在XML里编写对应SQL,启动或调用时就会出现该报错。解决办法是在XML中添加相同方法Id的SQL语句,或者使用注解方式在接口方法上直接编写@Select等SQL标签。
6.3 运行阶段逻辑问题与性能思考
业务逻辑层面的问题往往比技术报错更难排查。比如学生端选课按钮点击后没有任何提示,后端日志也没有异常,这种情况大概率是前端提交的课程ID为null或者没有正确传给后端。还有一种情况是,数据库里的课程状态是“草稿”或“未审核”,学生端查询时做了过滤,但教师端还能对未审核课程做操作,这本身是正常的产品逻辑分支。排查这类问题,我建议先看接口返回的data字段内容,确认实际返回了哪些数据和状态字段,再看前端代码判断时用的是什么条件。
性能层面,这套系统虽然初期数据量不大,但如果使用过程中积累了较多学生,选课记录和日志类数据会快速增长。可以在数据库层面给高频查询字段设置合理索引,在Mapper查询中避免无谓的select *,只查询需要的字段。前端方面,如果课程列表数据量大,考虑换用el-table的远程排序、懒加载等功能;如果接口响应中有大量重复访问的静态配置数据,可以在内存里维护一份缓存Map,而不是每次请求都查数据库。这些都是低成本、容易落地的性能优化手段。
6.4 一套排查思路速查表
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 前端访问接口跨域报错 | 后端CORS未配置或Nginx代理未生效 | 检查后端配置类和F12网络请求头中Origin字段 |
| 登录成功但跳转回登录页 | 路由守卫判断逻辑有误或token存储key不一致 | 查看前端router beforeEach中角色判断条件 |
| 上传文件后图片无法显示 | 静态资源映射未配置或路径存储错误 | 尝试在浏览器直接访问文件URL,检查磁盘目录权限 |
| 接口响应慢 | 查询全表扫描、未走索引 | 用EXPLAIN分析SQL执行计划,检查索引设计 |
| 数据库导入报字符集错 | SQL文件编码与数据库字符集不一致 | 统一用utf8mb4,执行前确认脚本编码格式 |
| 修改密码后原token仍有效 | JWT无状态特性决定无法主动失效 | 在修改密码逻辑中主动退出登录并清除前端token |
7. 项目复盘与扩展建议
这套基于SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0的在线课程管理系统源码,整体完整度在众多课程类项目里是比较高的。数据库建模规范、后端分层清晰、前端页面功能齐全,最难得的是它还配了文档和初始化数据,任何人都能在最短时间内把系统跑起来,然后在此基础上做定制化改造。
对我来说,这类项目最有价值的地方不在于“跑通”,而在于“读懂”。跑通只花费半天时间,但只要认真阅读了JWT拦截器、统一返回结构、文件上传、MyBatis-Plus分页查询、前端路由守卫这些核心实现,你在Web开发方面获得的实践成长,远比空看几十遍教程有用得多。
如果后续要往生产级别靠近,我建议从几个方向扩展。一是引入Redis缓存课程列表和验证码,减少数据库查询压力;二是把文件存储从本地目录替换为对象存储服务,实现真正的服务器无状态化;三是在选课逻辑里加入悲观锁或乐观锁策略,防止并发超选;四是把前端构建后的资源全部挂到CDN上,提高静态资源访问速度。每次扩展都对应了真实业务场景里的问题,做了就懂,懂了就值回票价。