
先说结论企业级在线教育系统这套东西市面上号称“完整版”的源码不少但绝大多数要么是阉割版要么是堆了一堆用不上的功能。我实际拿这套SpringBootVueMyBatisMySQL的源码跑了一遍完整流程从编译、初始化、部署到二次开发把中间遇到的坑、值得参考的设计、还有它真正“企业级”在哪些地方都梳理了一遍。这篇东西直接按我的实操路径来写适合拿到源码但不知道怎么下手的同学也适合准备做在线教育类项目、想参考一套成熟架构做二次开发的团队。1. 项目整体设计与技术栈选型思路1.1 为什么是SpringBootVue这套组合在线教育系统本质上是典型的业务管理系统核心链路就是用户管理、课程管理、订单交易、学习进度追踪、后台运营管理这么几条线。只要想清楚这一点技术选型其实没什么可纠结的。SpringBoot负责后端接口层Vue负责前端交互层MyBatis管数据库访问MySQL存数据。这套组合在企业级项目里的地位有点像装修界的“瓷砖水泥防水涂料”不是最花哨的但绝对是最成熟、最不缺人维护、踩坑成本最低的组合。我拿到这套源码后专门看了它的工程结构确实是标准的前后端分离后端一个SpringBoot工程前端一个Vue工程。后端工程用Maven做依赖管理jdk版本用的1.8SpringBoot版本是2.x系列结合MyBatis和MySQL驱动还整合了Lombok减少实体类的样板代码。前端用的是Vue2 ElementUI Axios Vue Router后台管理界面最常见的组合。一个容易被忽略但很重要的点这套源码里的数据库脚本是单独放在sql目录下的不是SpringBoot启动时自动建表的模式。这个设计对我这种喜欢“先把库看明白再动代码”的人非常友好。很多项目直接把建表语句嵌在代码里或启动时自动执行一旦表结构有问题很难排查。这套项目选择脚本初始化实际上更适合做二次开发你接手的时候很清楚当前数据库长什么样。1.2 “企业级”到底体现在哪里标题里最扎眼的词是“企业级”这个定义其实很宽泛。我拆解了这套源码的模块设计后发现它并不是挂羊头卖狗肉。首先权限管理走的是RBAC模型用户、角色、菜单三级。后端用Spring Security JWT做认证授权前端用Vue Router的守卫去控制页面访问。这是目前企业级系统最主流的权限做法。其次订单和课程管理的业务闭环是齐全的。前台有课程展示、课程详情、下单、模拟支付后台有课程分类管理、课程上下架、讲师管理、订单列表、学员列表、数据统计。该有的业务都画在了闭环里不是只有一堆CRUD接口。第三文件上传、视频播放、富文本编辑这些在线教育的“老大难”需求都有对应的实现。视频这块用的是阿里云OSS配合点播服务的思路富文本用的UEditor图片上传走的本地存储方案。虽然有些可能就是Demo级别的但起码把路给你蹚出了一条。1.3 这套架构的设计亮点和潜在短板先夸一下亮点整套代码的命名规范比较统一Controller-Service-Mapper三层分包清晰目录结构一眼就能看懂。后端接口的返回格式是统一的Result对象包含code、message、data三个字段这一点非常关键很多新手的项目就是每个接口自己造返回值前端对接一个接口一个写法乱成一锅粥。再说不好的地方这套源码里的事务控制基本依赖于Spring的声明式事务注解Transactional大部分业务方法上都加了但订单创建、支付回调、课程上下架这种多步操作有的方法里事务粒度偏大在高并发场景下会放大锁的持有时间。当然作为学习参考和中小规模部署完全够用真到了要扛大流量那一步本来也不是一个开源项目能解决的事。2. 数据库设计与核心业务表解析2.1 数据库初始化实操这套系统需要手动创建一个名为online_edu的数据库然后导入项目里sql目录下的online_edu.sql文件。建议直接在命令行里搞定比起图形化工具能少很多字符集乱码的困扰。mysql -uroot -p --default-character-setutf8mb4 create database online_edu default character set utf8mb4 collate utf8mb4_general_ci; use online_edu; source /你的项目路径/sql/online_edu.sql;注意这里用了utf8mb4而不是utf8。原因很简单utf8在MySQL里最多只支持3个字节的字符现在学员昵称里各种emoji表情一存就报错utf8mb4才是MySQL真正的“全量字符集”。这个细节非常小但影响面很大属于一个字符集引发的血案。导入完成后使用show tables;命令可以看到主要的数据表。我整理了一下核心表大致可以分为四类用户与权限、课程相关、订单与交易、运营配置。2.2 核心表结构拆解用户权限这一块是典型的三张主表sys_user、sys_role、sys_menu还有sys_user_role、sys_role_menu两张关联表。这套表结构的核心思路就是“用户不直接绑权限而是通过角色间接绑”。新来的实习生问为什么搞这么麻烦我一般举个特别生活化的例子如果公司100个人每个人单独配门禁卡权限离职一个人你就要改一次门禁系统但如果按部门配权限走一个人只调整部门人员的增减就行门禁规则根本不用动。课程业务这块edu_course是课程主表edu_course_description存课程详情edu_video存课时视频信息edu_subject代表课程分类。主表和详情表拆开是很多内容型系统的标配做法。因为列表页只需要课程封面、标题、价格这些轻量字段如果详情这类大字段全放一张表里列表查询会非常慢光IO就扛不住。订单交易表的核心是edu_order。设计上有一个值得学习的小细节订单表里除了存user_id还冗余存了user_name用户昵称除了存course_id还冗余存了course_title、course_cover。为什么这样设计因为订单生成后用户昵称和课程标题一旦发生变化订单表的历史快照不能跟着变。比如课程改名了你买课时候的订单记录得保留当时买的是“Java架构师进阶课”而不是变成后来改过的“Java架构师就业班”否则对账和售后都会出大问题。2.3 数据库设计的优化场景索引方面系统在edu_course的subject_id、edu_order的user_id和course_id这些高频查询字段上建了普通索引整体没什么大问题。但如果有二开需求我建议在edu_order上加一个create_time索引因为后台订单列表基本都会按时间倒序分页查询。没有这个索引数据量三两万的时候感觉不明显到几十万的时候就该急得挠头了。再有一个字段设计的亮点是价格字段采用了decimal(10,2)类型而不是直接存浮点型。浮点型在MySQL里算金额会出现0.10.2不等于0.3的经典问题用decimal能精确保存小数运算。这是所有涉及钱的项目里最基本的底线设计。3. 后端SpringBootMyBatis核心实现3.1 后端工程结构与启动流程后端工程命名为online_edu_parent使用Maven多模块结构。其中online_edu-common放公共类online_edu-service放业务逻辑online_edu-web放Controller和启动类。这种拆法在企业里很常见把通用东西抽出来给多个服务共用避免每个微服务都写一套工具类属于非常标准的工程化思维。启动之前一定要改的配置文件是application.yml。数据库账号密码、Redis地址、JWT的加密密钥等都在这里面。尤其JWT的密钥千万别用源码自带的默认值上线被人伪造token是分分钟的事。server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/online_edu?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.onlineedu.entity jwt: secret: 自己换一串足够长的随机字符串 expire: 604800配置完成后启动类OnlineEduApplication直接运行即可。后端启动后默认端口是8081访问Swagger文档地址是http://localhost:8081/swagger-ui.html里面能看到所有后端接口的定义和参数说明这对前后端联调来说是一个救命工具。3.2 Controller-Service-Mapper三层的交互逻辑后端严格遵循三层结构。Controller只接收参数、调用服务、返回结果不写任何业务逻辑。Service层负责具体业务流程Mapper层只管数据库读写。这个分层规范看起来简单但我见过太多项目里的Controller里硬塞业务代码最后改一个需求要翻遍几个文件才能确认影响范围。以课程发布为例流程是管理员在后台提交课程表单Controller接收请求参数后交给CourseServiceCourseService里先对参数做合法性校验比如课程价格不能是负数、标题不能为空然后通过CourseMapper插入课程主表数据再通过CourseDescriptionMapper插入课程详情同时通过SubjectMapper查出课程分类信息做关联。事务控制就是在Service层的Transactional注解上实现的任何一步失败都会整体回滚。注意Transactional在没有指定rollbackFor时默认只有遇到RuntimeException才会回滚。如果代码里抛的是Exception自定义异常事务不会自动回滚这也是很多项目数据“半截写入”的重要原因。安全起见建议统一指定rollbackFor Exception.class。3.3 MyBatis在项目里的高级应用这套源码的MyBatis使用属于比较规范的那种XML文件全在mapper目录下一个表对应一个Mapper接口和一个XML文件。复杂SQL写在XML里简单SQL直接用注解。这里挑三个比较值得二开时参考的实战点来聊。第一动态SQL。课程列表支持多条件组合筛选比如分类、价格区间、关键字、上架状态。如果为每一种组合都写一个SQL代码量会爆炸。MyBatis的 标签可以拼出动态条件核心逻辑类似这样select idselectCoursePage resultTypecom.onlineedu.entity.Course select * from edu_course where if testtitle ! null and title ! and title like concat(%, #{title}, %) /if if testsubjectId ! null and subject_id #{subjectId} /if if teststatus ! null and status #{status} /if /where order by create_time desc /select第二分页查询。项目用的是PageHelper插件这是MyBatis生态里最常用的分页插件。底层原理是基于拦截器在执行SQL之前把原SQL拦截住自动拼接limit语句再查一次count计算总数。用法非常简单PageHelper.startPage(pageNum, pageSize); ListCourse list courseMapper.selectCoursePage(course); PageInfoCourse pageInfo new PageInfo(list);这里特别注意一点PageHelper.startPage方法必须紧接着在下一行调用Mapper方法中间如果执行了其他数据库操作分页会串到那条SQL上。这是我踩过最频繁的坑没有之一。第三连表查询。课程列表页需要展示讲师名称、分类名称而这两类信息分别存在edu_teacher和edu_subject表里。MyBatis里用 标签做对象嵌套映射一次性把关联对象查出来而不是在Service里挨个查表再组装能大幅减少数据库的查询次数。3.4 登录认证与权限拦截的实现方案这套系统的登录认证方案是JWTSpring Security。用户登录成功时后端生成一个包含用户ID、用户名、角色信息的JWT token返回给前端前端存在localStorage里。之后每次请求都在header里带上Authorization: Bearer token字符串后端过滤器解析token拿到用户身份信息再判断是否有权限访问指定接口。具体的权限判断规则是后端每个接口用PreAuthorize注解声明所需权限比如课程管理接口需要“课程管理”权限学员列表接口需要“学员管理”权限。Spring Security的注解式鉴权会在接口被调用前进行校验权限不足直接返回403。前端则在路由守卫里配合操作路由meta里两个比较重要的属性{ path: /course/list, name: CourseList, component: () import(/views/course/list.vue), meta: { title: 课程列表, roles: [admin, teacher] } }Vue Router的beforeEach钩子里会取出用户角色和路由要求的roles做比对不匹配就重定向到401页面。这种“前端防君子后端防小人”的双层设计是前后端分离项目权限控制的通用模板。4. 前端Vue工程搭建与核心页面实现4.1 前端工程初始化和依赖安装前端目录名是online_edu_front基于Vue2工程化脚手架搭建。初次运行前需要先安装依赖这一步千万要有耐心。推荐用npm或者yarn我第一次跑的时候在依赖安装上浪费了大半天时间后来总结出三条经验直接用Node.js 14版本而不是追求最新版npm镜像源换到国内镜像删除node_modules和package-lock.json后重新安装能解决绝大多数玄学报错。npm config set registry https://registry.npmmirror.com npm install npm run serve前端启动后访问localhost:9528这是项目里配置的默认端口。第一次看到的应该是登录页系统预置了管理员的账号密码登录后进入后台管理界面。界面布局是经典的左侧菜单栏加右侧内容区顶部的顶部栏右侧是用户信息和使用说明。4.2 前端路由与权限控制这套前端项目的路由文件放在了src/router/index.js里页面组件在src/views目录下。路由设计上把登录页、404页作为公共页面其余页面都放在Layout布局组件之下这样所有后台页面自动继承整体框架不需要每个页面再写一遍左侧菜单和顶部栏。权限控制方面路由守卫的文件是src/permission.js。核心逻辑不复杂但在线教育这种后台系统里很实用判断是否有token没有token就强行跳转登录页有token且已经拿到了用户角色就按路由元信息的roles字段做匹配。如果后端返回的角色信息拉取失败会调用一次获取用户信息的接口比如router.beforeEach(async (to, from, next) { const token getToken() if (!token) { if (to.path /login) { next() } else { next(/login?redirect${to.path}) } } else { const userInfo store.getters.userInfo if (userInfo) { next() } else { try { await store.dispatch(user/getInfo) next({ ...to, replace: true }) } catch (error) { await store.dispatch(user/resetToken) next(/login?redirect${to.path}) } } } })4.3 请求封装与API管理前端所有的接口请求都统一走src/utils/request.js这个封装模块。它基于Axios做了二次封装干了三件事统一设置baseURL从localStorage里拿token并加到请求头处理后端返回code不等于20000的异常情况比如token过期就自动退出登录。这套封装的价值在于前端代码里永远不需要关心token怎么带、错误怎么弹所有接口只管用就行。API管理也是统一在src/api/目录下按业务模块拆分文件的比如course.js、user.js、order.js。每个文件导出一个函数函数内部调用request方法把请求地址和请求方式设置好。组件里只需要import对应的接口函数即可这种“模块化API”的维护成本非常低后端改个地址只动一个文件。4.4 在线视频播放与富文本处理在线教育系统的核心体验就是看课学习。这套系统里的课程详情页视频播放基于Video.js播放器支持mp4格式的播放。如果你们业务里有m3u8格式的直播流或点播流需要额外引入hls.js或videojs-contrib-hls插件来支持。代码上其实就是在video标签的source里指一个m3u8地址video refvideoPlayer classvideo-js vjs-big-play-centered source :srcvideoUrl typeapplication/x-mpegURL / /video富文本这块课程详情页用的是UEditor编辑器后台编辑课程的时候可以插入图片、富文本段落。对在线教育场景来说这个基本够用了如果二开想换成目前更主流的Markdown编辑器或者支持协同编辑的富文本组件注意对数据库里存储的HTML格式做兼容就行。4.5 播放进度记录与续播实现说实话这块是容易被忽略但非常影响体验的功能。用户课看到一半关掉了下次进来从头开始放这种体验放在在线教育里是很劝退的。这套系统里课程学习进度是简化处理的但预留了play_record表和对应的记录接口思路。最简单的记录方案是前端每隔5秒上报一次当前播放时间后端存到一张记录表。用户再次打开课程详情页时前端向后端请求历史播放进度拿到秒数后直接设置视频播放器的currentTime。需要注意的点是记录上报接口要做节流不能在video的timeupdate事件里每个几百毫秒就发一次请求数据量太大会把后端接口打爆。我建议前端做一层10秒间隔的定时上报后端再做一次时间段校验基本就够用了。5. 系统安全、缓存与性能优化解析5.1 XSS攻击防护全局过滤器方案在线教育系统里讲师可以在富文本里写课程详情学员可以用昵称和评论这些场景都是XSS攻击的高发区。如果攻击者在课程简介里塞了一段恶意脚本其他学员一打开详情页就中招后果非常严重。这套源码里采用了一种比较务实的方案后端加一个全局的XSS过滤器。该过滤器拦截所有请求把请求体里的HTML标签和敏感关键字做转义处理比如把