资讯动态

SpringBoot+Vue+MyBatis+MySQL课程管理系统源码复盘

发布时间:2026/9/7 18:59:43 来源:尧图企业网站定制
前后端分离的在线教育项目我平时接触不少但能让人放心拿去二次开发的完整源码其实不多。最近花了两个周末把一套自己维护的课程管理系统重新梳理了一遍技术栈就是标题里那套SpringBootVueMyBatisMySQL。从数据库设计到权限控制从视频点播到订单流程该有的模块基本都齐了。这篇博客就当一次完整的源码复盘把架构思路、核心实现、还有我在开发过程中踩过的坑一并记录下来想用这套源码起步做毕业设计或者企业内部培训平台的朋友可以直接照着落地。整个系统设计为三类角色学员、讲师、管理员。学员端主要完成选课、学习、记录进度、考试讲师端负责课程创建、章节维护、题库管理管理员则管用户、管分类、管订单。围绕这三大角色系统拆解出课程模块、用户模块、订单模块、学习记录模块、考试模块、数据统计模块。下面先把模块划分和设计取舍讲清楚然后逐层拆开讲后端和前端的关键实现最后把部署细节和排坑经验一次性说完。1. 课程系统的核心需求与模块划分1.1 企业在线课程管理到底在管什么很多刚入门的朋友对课程管理系统的认知停留在上传视频、展示课程列表这个层面实际做起来才发现完全不是那么回事。一个能真正用于企业培训或在线教育的系统核心要解决的是这样几件事课程内容的结构化组织、学员学习行为的过程记录、教学效果的评估反馈、以及围绕课程产生的商业或管理流程比如订单、权限。课程本身需要有多级分类可以挂载章、节视频、文档、测验都要能附在课程结构中。学员学习完一章后系统要记录完成状态这样下次登录才能续学。讲师要能看到自己名下课程的学习数据管理员要能控制整个平台的运行状态。这套关系捋清楚之后模块划分自然就清晰了。1.2 三类用户角色的功能边界用户模块我用了一张sys_user表承载所有登录账号用角色字段区分身份。学员端的功能集中在课程浏览、课程详情、学习页、个人中心讲师端有课程管理、章节管理、学生列表、数据统计管理员端则是用户管理、课程审核、分类管理、订单管理。前端通过动态路由控制页面权限后端通过拦截器校验接口权限双端都做了控制避免有人绕开前端直接调接口。这里我特意没有引入太重的权限框架像 Shiro、Spring Security 都没用而是自己用拦截器配合 JWT 做的方案。原因在后面讲认证的时候详说。角色权限的划分不能只停留在能不能进某个页面这个级别更细的粒度要落到数据层面。比如讲师 A 登录后只能看到自己创建的课程管理员能看到全部。这个需求听起来简单实际设计时容易忽略。我在后端的查询逻辑里没有把所有课程都查出来再在前端过滤而是在 SQL 层就根据当前用户角色拼接了条件。管理员查全表讲师查询条件自动带上teacher_id这样既保证数据隔离也避免了一张大表全量查出来再筛选的性能浪费。1.3 课程模块的树形结构设计课程和章节我拆成了两张表course和course_section。课程表记录标题、封面、分类、价格、状态、讲师 ID章节表通过course_id关联主表再用parent_id自关联实现章和节的层级。这样的设计好处是课程详情页要展示左侧章节目录时只需要查一次course_section在内存中组装成树形结构返回前端数据库查询压力很小。刚开始我也考虑过直接用level字段区分章和节一张表维护但后来发现parent_id方案更灵活。如果以后要支持节下面再加小节这种更深的层次树形结构不需要改表。组装树的时候要注意排序我在表里加了sort字段同一层级下按照sort升序排列。前端 Element Plus 的 el-tree 组件直接吃树形数据组装逻辑放在后端 Java 代码里完成前端拿到直接渲染整体链路挺顺的。1.4 订单模块中的课程与交易订单功能我这边做成的是课程下单 模拟支付 订单状态流转这套逻辑。orders表记录订单号、课程 ID、用户 ID、金额、状态、创建时间。用户提交订单时先生成待支付状态调用支付接口后改为已支付同时往user_course表里插入一条购买关系记录。这个购买关系表很关键它是学员我的课程列表的数据来源也是判断用户有没有权限学习某门课程的依据。我在学习权限校验这块踩过不少坑。最开始设计时前端按钮是通过v-if判断课程是否免费来控制是否展示立即学习后来测试发现人只要知道课程详情接口的 URL就能直接绕过前端把学习页面的数据调出来。后来我在后端视频播放和课程详情接口里统一加了校验逻辑先查当前用户是否拥有该课程免费课程默认拥有没有购买记录直接返回 403。权限判断这事前端只是提升体验真正拦截必须放在后端接口层。2. 技术选型背后的思考为什么还是这套组合2.1 SpringBoot 在项目中的实际定位SpringBoot 在这套系统里承担的是应用框架的职责内嵌 Tomcat、自动配置、Starter 机制能把大量样板配置消掉。我选 SpringBoot 一个很直接的原因是版本生态稳定网上资料多团队协作时新成员上手成本低。我使用的是 2.7.x 版本没有直接冲 3.x主要原因在于 3.x 对 JDK 版本有硬性要求至少 JDK 17而当前很多企业内部服务器还停留在 JDK 8 环境。从部署兼容性考虑2.7.x 配 JDK 8 是最稳的组合。这个选择建议做企业项目的朋友重点关注技术选型不全是为了用新而是要保证交付环境能跑起来。2.2 Vue 在前端项目中到底解决了什么问题前端部分我选择的 Vue 2.7 Element UI 的组合。可能有人会问为什么不用 Vue 3原因很简单Vue 2.7 是 Vue 2 的最后一个版本它对 Composition API 有了部分支持同时保留了 Options API 的习惯Element UI 组件库又非常成熟项目启动快、踩坑少。对于课程管理这类中后台系统重要的不是框架本身多新而是开发效率、组件生态、资料丰富度。Vue 的响应式数据绑定和组件化开发模式非常适合课程列表、视频学习页、管理后台这类交互复杂的场景。前端的目录结构我按视图层、路由层、状态层来组织。view 目录下分student、teacher、admin三个子目录对应三种角色页面router 里配置动态路由表用户登录后根据角色信息只注册对应权限的路由Vuex 里维护用户信息、token、购物车等全局状态。Element UI 按需引入避免打包体积过大。Axios 封装了统一的请求实例请求头自动携带 token响应拦截器统一处理错误码。2.3 MyBatis 在数据访问层的优势选 MyBatis 而不是 JPA 的原因很实际这套系统里有大量的多表关联查询、动态条件筛选、统计报表 SQLMyBatis 允许我直接编写和控制 SQL 语句性能瓶颈出现时可以精确定位到某一条 SQL。JPA 虽然开发效率高但一旦遇到复杂查询要么拼 JPQL要么落到原生 SQL反而绕了一圈。MyBatis 的 XML 映射文件提供了很高的灵活性if、where、foreach这几个动态标签用得熟练之后多条件组合查询写起来非常顺手。MyBatis 的缓存机制我也单独配置过。本地缓存默认开启二级缓存我是在订单表和课程表这种读多写少的查询上开的。需要注意的是如果一张表涉及频繁的增删改操作开二级缓存反而容易出问题容易读到脏数据。我在course表的查询上开了二级缓存但learning_record表学习进度更新的场景里关闭了缓存每次都查数据库。这类微调表面上不起眼实际对生产环境的影响很大。2.4 MySQL 选型与基本配置要点数据库这块用的是 MySQL 8.0存储引擎 InnoDB字符集utf8mb4。现在很多教程还在用utf8我建议直接换成utf8mb4因为它完整支持 emoji 表情和生僻字。之前遇到过一个用户昵称带特殊符号导致插入失败的案例排查到最后就是字符集的问题。数据库连接池用的 Druid配置了初始连接数、最大活跃数、连接超时时间等参数。事务隔离级别保持默认的REPEATABLE_READ在订单创建的场景下配合悲观锁SELECT ... FOR UPDATE防止超卖。3. 数据库表设计与 MyBatis 落地细节3.1 核心表的物理结构建表时我遵循了几个原则主键统一用bigint自增业务唯一标识比如订单号、课程编号单独用varchar存不让主键承担业务含义。每张业务表都带上create_time、update_time两个时间字段后面做数据统计和问题排查都用得上。逻辑删除字段deleted设为tinyint默认 0删除时走 UPDATE 而不是 DELETE。课程表course的字段设计比较有代表性除了基本信息我放了status字段0 草稿、1 上架、2 下架cover_url存封面地址price用decimal(10,2)存价格teacher_id关联讲师用户 ID。为什么不用字符串存价格因为涉及到排序和统计数值类型才能用SUM、AVG这些聚合函数直接算。分类字段category_id关联分类表分类表用自关联结构支持二级分类。章节表course_section的字段包括course_id、parent_id、title、video_url、sort、duration。这里duration存视频时长单位是秒前端展示时再格式化避免在前端做复杂的换算。用户表sys_user里除了账号密码我额外设计了avatar、phone、status三个字段。status用于拉黑用户管理员后台可以将某个用户禁登录拦截器里每次请求都会校验这个状态字段。密码存储用的是 BCrypt 加密Spring 的BCryptPasswordEncoder可以直接用不要用 MD5 存密码这一点虽然老生常谈但我在很多毕设源码里还是看到 MD5 裸存的情况安全问题不要省。3.2 复杂查询中的动态 SQL 应用课程列表页是典型的多条件筛选场景按关键字模糊搜索、按分类筛选、按价格区间筛选、按上架状态筛选。这些条件组合起来非常多用 MyBatis 的动态 SQL 可以优雅解决。我在 XML 里写的查询大致如下select idselectCourseList resultTypecom.example.entity.Course SELECT * FROM course where if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR description LIKE CONCAT(%, #{keyword}, %)) /if if testcategoryId ! null AND category_id #{categoryId} /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /select这段 SQL 的精妙之处在于where标签会自动去掉第一个条件下多余的AND避免出现WHERE AND title这种语法错误。gt;和lt;是 XML 中的转义写法直接写会被 XML 解析器认为是标签结束符这个细节很容易忽略。学习记录表learning_record的查询还需要按用户、按课程、按章节做联合条件。我在 XML 里用resultMap做了自定义映射解决数据库字段名下划线course_id和 Java 属性驼峰命名courseId之间的转换问题。虽然 MyBatis 有全局的map-underscore-to-camel-case配置可以自动转换但使用 resultMap 时如果多个表联查字段冲突的场景还是要手动指定映射列更可控。3.3 批量插入与事务控制课程章节批量保存的功能我用到了 MyBatis 的foreach标签insert idbatchInsertSections INSERT INTO course_section (course_id, parent_id, title, video_url, sort) VALUES foreach collectionlist itemitem separator, (#{item.courseId}, #{item.parentId}, #{item.title}, #{item.videoUrl}, #{item.sort}) /foreach /insert批量插入时要注意 MySQL 对单条 SQL 语句长度有限制默认max_allowed_packet是 64MB一般业务数据量根本到不了上限但如果一次性导入上千个章节还是建议分批每批 500 条左右。批量操作的方法需要加Transactional事务注解一旦中间某条插入失败整个批次会回滚不会出现部分数据写入的脏状态。关于事务还有一点值得提我在这套系统里把事务边界控制在 Service 层不在 Controller 加事务注解。因为 Controller 做的是参数接收和响应封装真正涉及多表操作的业务逻辑都在 Service 层。如果你在 Controller 层直接调用多个 Mapper 方法且不加事务中途某个步骤出错时前面的写操作不会回滚数据一致性就崩了。3.4 统计报表的 SQL 实践管理后台的数据统计模块需要展示课程总数、学员总数、订单总金额、每日新增用户数等指标。多表联查的聚合 SQL 在这种场景里非常关键。比如统计每门课程的选课人数SELECT c.id, c.title, COUNT(uc.id) AS student_count FROM course c LEFT JOIN user_course uc ON c.id uc.course_id WHERE c.deleted 0 GROUP BY c.id, c.title ORDER BY student_count DESC这里用LEFT JOIN而不是INNER JOIN目的是让没被选过的课程也出现在结果里选课人数显示为 0。这种报表数据我用一个独立的StatisticMapper去查避免和业务 CRUD 混在一起。如果数据量大后续考虑单独建统计表定时任务去刷新汇总数据但现阶段直接查也没问题。4. 后端 SpringBoot 核心模块实现4.1 基于 JWT 的认证与拦截器权限控制认证方案我最终选择了 JWT 自定义拦截器而不是引入 Spring Security 全家桶。原因有两方面一是这个系统的角色模型比较固定只有三种角色用注解或拦截器判断角色编码即可Spring Security 的过滤器链在这里显得有些重二是 JWT 无状态后端不需要维护 session对部署多个实例做负载均衡的场景很友好。整体流程是这样用户提交账号密码后端校验通过后生成一个 token里面包含用户 ID、用户名、角色编码用 HMAC-SHA256 算法签名有效期设置 24 小时。登录接口返回 token前端把它存到 localStorage每次请求在 Axios 拦截器里把 token 放到请求头Authorization字段。后端拦截器解析 token把用户信息放入 ThreadLocal供后续 Controller 使用。自定义注解RequireRole(teacher)加在需要特定角色访问的 Controller 方法上拦截器用反射获取注解并校验角色。拦截器实现的代码核心是继承了HandlerInterceptor接口的preHandle方法但有几个细节必须注意。白名单路径要放行登录接口、注册接口、课程列表查询接口、课程详情接口、视频播放接口因为用户未登录也要能浏览课程。除了登录相关的接口静态资源路径也要放行否则前端打包后的 index.html 都访问不了。还有跨域问题如果前端项目和后端项目分离部署必须在 SpringBoot 里配置跨域过滤器开发环境我用CrossOrigin局部注解解决更省事。4.2 视频上传与播放方案m3u8 切片在线课程系统的重头戏在视频模块。课程视频的存储和播放我采用了本地文件存储 m3u8 切片播放的方案。这里有必要多说几句很多视频播放卡顿问题都是格式选择没做对。原视频文件不适合直接通过 HTTP 拉流播放尤其 mp4 格式需要先加载完整的 moov 元数据才能真正开始播放用户等待时间很长。我的做法是讲师上传视频后后端调用 FFmpeg 命令把视频转成 m3u8 格式生成多个.ts切片文件和一个.m3u8索引文件。播放时前端用 video.js 加载 m3u8 地址浏览器通过 HLS 协议逐个拉取切片实现边下边播拖动进度条时也能快速定位体验远好于直接播 mp4。同时我开发了一个定时任务扫描视频目录清理超过 30 天没有被访问的临时文件。FFmpeg 转码的命令行大致是这样的ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 -hls_segment_filename %03d.ts output.m3u8-hls_time 10表示每个切片 10 秒左右切片太短会导致请求频繁太长则拖动进度时加载数据量大。转出来的 m3u8 文件内部记录的切片路径是相对路径所以整个目录要一起部署到静态资源服务器。我在后端配置了虚拟路径映射把本地磁盘上的视频目录映射为/video/**访问路径。4.3 订单创建与支付状态流转订单模块我采用的是提前生成订单号、下单后校验库存、模拟支付回调更新状态的方案。生成订单号时我用SimpleDateFormat格式化当前时间到毫秒再加用户 ID 拼接出唯一订单号比如2025012115300012340001。虽然理论上存在并发冲突的概率但在课程购买场景下量级不大这个策略足够用。如果做的是高并发的秒杀系统要考虑用 Redis 生成自增序号。订单状态我用status字段管理0 表示待支付1 表示已支付2 表示已取消。创建订单和支付成功这两个关键操作都在独立的OrderService方法中完成。支付成功接口我模拟了支付宝/微信的回调机制用户点击支付后端不直接改状态而是生成支付记录然后模拟回调接口被调用回调里执行订单状态修改和课程授权发放。这样设计的好处是以后接真实第三方支付时只需替换回调处理逻辑整体流程不用改。这里有个特别容易出问题的点回调接口的幂等性。如果支付平台回调多次后端重复执行授权逻辑会导致user_course表插入多条重复记录。我在回调处理中加了判断逻辑先根据订单号查询订单状态如果已经是已支付直接 return不再重复操作。4.4 文件上传的路径处理与静态资源映射课程封面上传、讲师头像上传、视频上传这些功能都涉及文件操作。SpringBoot 默认的单文件大小限制是 1MB多文件限制是 10MB视频上传必须调大这两个参数。我在application.yml里配置spring: servlet: multipart: max-file-size: 1024MB max-request-size: 2048MB但光调大配置还不行上传后的文件保存路径也需要注意。我在配置类里实现了WebMvcConfigurer重写addResourceHandlers方法把/upload/**路径映射到本地磁盘的E:/edu_files/目录。这样前端访问http://ip:8080/upload/course.mp4就能直接读取到磁盘上的文件。路径写死这种方式在本地开发没问题部署到 Linux 服务器时要改成绝对路径并确认目录可写。我自己的服务器是把文件目录设在/data/edu-files/启动脚本里用环境变量注入。Windows 开发环境路径用的是反斜杠Linux 用正斜杠代码中统一用File.separator拼接避免跨平台出问题。5. 前端 Vue 实现的关键页面与交互5.1 项目初始化与目录规范前端项目我用 Vue CLI 4 脚手架初始化Node 版本要求 12 以上。整个 src 目录结构大概这样src/ ├── api/ # 接口请求封装 │ ├── course.js │ ├── user.js │ ├── order.js │ └── statistics.js ├── assets/ # 静态资源 ├── components/ # 公共组件 │ ├── CourseCard.vue │ ├── SectionTree.vue │ └── Pagination.vue ├── router/ # 路由配置 ├── store/ # Vuex 状态管理 ├── utils/ # 工具函数 │ ├── request.js # Axios 封装 │ └── auth.js # token 处理 ├── views/ # 页面视图 │ ├── admin/ │ ├── teacher/ │ └── student/ └── App.vue每新增一个功能模块在 api 目录下建一个 JS 文件统一 export 该模块的接口方法。比如课程相关的请求都放在course.js里组件只负责调用方法不直接写axios.get(/api/course)这种散落在各处的请求。这样维护起来非常清爽后端接口地址变了只需改一个文件。5.2 动态路由与权限控制的实现动态路由是很多前端项目中实现权限控制的通用方案。老师在后台登录后前端拿到该用户的角色信息通过router.addRoutes动态挂载对应角色的路由表。学生登录挂载学生端路由讲师登录挂载讲师端路由。我在permission.js路由守卫文件里处理了完整的登录跳转逻辑router.beforeEach((to, from, next) { const token getToken() if (token) { if (to.path /login) { next({ path: / }) } else { // 判断路由表是否已经加载 if (store.getters.roles.length 0) { store.dispatch(GetUserInfo).then(roles { // 根据角色生成可访问路由表 const accessRoutes generateRoutes(roles) router.addRoutes(accessRoutes) next({ ...to, replace: true }) }) } else { next() } } } else { if (whiteList.indexOf(to.path) ! -1) { next() } else { next(/login?redirect${to.path}) } } })这段代码解决了一个很常见的体验问题用户刷新页面时 Vuex 中的状态会丢失路由表需要重新加载。每次刷新后路由守卫会检查 Vuex 里是否有用户角色信息没有就重新调接口获取并挂载动态路由。如果不做这一步刷新后在课程管理页面会白屏因为路由已经不复存在了这个坑很多初学者踩过。5.3 视频播放器集成与学习进度记录视频学习页是学生核心场景我这里用的 video.js 播放器支持 HLS 播放。安装依赖后在组件中引入播放器样式和 JSimport videojs from video.js import video.js/dist/video-js.css组件挂载后初始化播放器监听timeupdate事件每隔一定时间向后端上报当前播放位置。上报接口设计得很轻只传courseId、sectionId、currentTime三个参数。后端收到后先查询有没有记录有则更新无则插入。这里我用了一个lastReportTime变量控制上报频率每 15 秒上报一次避免频繁请求把后端压垮。播放器进度恢复功能也很重要用户在 12 分 30 秒退出下次进来应该从 12 分 30 秒继续播放。实现方式是用video.currentTime(startTime)方法设置播放器的初始位置。如果播放的是 m3u8 流需要监听loadedmetadata事件后再跳转因为此时才能确定媒体时长。HLS 流播放还有一个坑如果 m3u8 切片文件中的分片没有正确编码video.js 可能无法播放。排查时要先用 VLC 播放器直接打开 m3u8 链接如果能正常播说明是前端播放器集成的问题如果 VLC 也播不了基本可以断定是 FFmpeg 转码出的切片本身有问题需要检查转码参数。5.4 管理后台的表格、表单与数据联动讲师端和管理员端大量使用 Element UI 的 el-table 组件。我封装了一个公共的分页组件统一处理页码变化、页容量变化事件表格数据请求都走分页参数 查询条件这同一套规范。el-table 的列配置里我习惯带prop字段而不是直接做slot-scope这样表格列的排序功能可以直接复用 Table 组件的sortable属性服务端配合 ORDER BY 字段排序。课程审核功能是管理后台里最能体现表单联动的部分。管理员查看待审核课程详情时需要看到讲师提交的完整课程信息封面图、简介、价格、课程目录树。这里我复用了学生端的课程详情组件只是外层包了一层审核操作栏通过isAuditprop 控制。这样的复用方式大大减少了代码量也保持了课程展示在不同场景下的一致性。管理后台的数据统计页我用 ECharts 做了图形化展示折线图展示近 7 天新增用户数趋势饼图展示课程分类占比柱状图展示课程销量排行。数据通过统计接口一次性返回前端交给 ECharts 渲染。做报表接口时要注意返回的字段结构应该直接对应图表需要的格式不要让前端再做复杂的数组转换否则接口设计就失败了。6. 源码部署、运行与排坑实录6.1 本地开发环境搭建想把这套源码跑起来本地环境要满足几个硬性条件。后端的 JDK 版本必须是 8 或 11Maven 版本 3.6 以上IDE 推荐 IntelliJ IDEA社区版就够用。MySQL 版本最好 8.05.7 也能跑但要注意utf8mb4的配置方式略有不同。前端 Node.js 版本 12 以上npm 或 yarn 都可以推荐 npm因为 lock 文件统一。拿到源码后第一件事是导入数据库脚本我提供了一份完整的edu.sql文件里面包含了建库、建表和初始化数据。数据库连接配置在application.yml里主要修改这几处spring: datasource: url: jdbc:mysql://localhost:3306/edu_system?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: your_password特别注意 MySQL 8.0 的驱动名称是com.mysql.cj.jdbc.Driver老版本是com.mysql.jdbc.Driver如果驱动写错启动时会报ClassNotFoundException。serverTimezone参数如果缺失连接时经常报时区错误统一指定Asia/Shanghai最省心。6.2 前后端联调环境的跨域配置前端开发服务器默认跑在8080端口后端接口在8080端口如果后端改成 8081那正好端口错开。本地前后端分离开发时跨域问题不可避免。我在 Vue 的vue.config.js里配置了代理module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样做的好处是前端代码里所有请求地址都写成/api/course/list这种相对路径开发环境由 webpack-dev-server 代理转发到后端部署环境通过 Nginx 将/api前缀转发到后端服务接口地址不需要做任何改动。如果不用代理而是直接在前端写http://localhost:8080部署时改域名或端口就要动一大堆代码非常痛苦。6.3 我遇到过的三个棘手问题及解决过程第一个问题视频上传时后端报错The temporary upload location ... is not valid。这个错误本质是 Tomcat 默认的临时目录被系统清理掉了我查了很久资料才确认。解决方案是在配置类里显式指定MultipartConfigElement的临时目录Bean public MultipartConfigElement multipartConfigElement() { MultipartConfigFactory factory new MultipartConfigFactory(); factory.setLocation(/data/tmp); return factory.createMultipartConfig(); }第二个问题前端 el-table 表格数据量大时渲染卡顿。课程章节多的课程可能会有上千条记录Element UI 在渲染大量行时有性能瓶颈。最后我通过懒加载树结构解决展开章节时才请求对应的小节数据而不是一次性把所有节点加载进来。配合后端在course_section查询接口中支持parent_id参数按需加载性能提升明显。第三个问题m3u8 播放时视频拖动有延迟。排查发现是因为切片时长设置太长我原本设置的是 30 秒一个切片用户拖到 20 分钟处需要等对应的切片加载完成才能播放。后来把-hls_time参数改为 5 秒切片数量变多但加载粒度更细拖动的响应速度明显改善。切片文件数量增加后要注意磁盘容量的规划1 小时的视频大约生成 600 兆的切片文件。6.4 生产环境部署的几点补充建议如果这套系统要部署到真实服务器我建议用 Nginx 托管前端静态文件后端 SpringBoot 打成 jar 包用nohup启动。Nginx 配置里需要有三块内容静态文件服务、API 反向代理、Gzip 压缩。视频切片文件如果体积大建议在 Nginx 中配置单独的缓存策略设置expires 7d因为视频切片是典型的一次生成、多次访问资源浏览器缓存能显著降低服务端带宽压力。数据库备份也不能忽略课程系统的数据虽然不是高频变化但订单数据和学习记录具有价值我在服务器上配了 crontab 任务每天凌晨 2 点执行mysqldump全量备份保留最近 7 天的备份文件。真出问题时恢复成本很低。这套系统的设计和编码过程我最大的感受是前后端职责边界要清晰数据权限要后端兜底视频播放这类体验敏感的功能要舍得花时间调优。如果你打算基于这套源码二次开发建议从课程模块入手熟悉整体结构再逐步深入订单和权限部分整个项目跑通之后你对 SpringBoot Vue 全栈开发的理解会上一个明显的台阶。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价