ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

在线学习平台设计与实现:从数据库建模到视频链路

在线学习平台设计与实现:从数据库建模到视频链路 简介一套基于Spring Boot与Vue的在线学习平台完整项目面向计算机相关专业毕业设计或课程作业场景聚焦学生、教师、管理员三类角色涵盖用户注册登录、课程发布与分类、学习资源管理、个性化推荐、在线测评自动评分、讨论区互动及后台管理等模块功能设计较完整适合快速理解在线教育业务模型。资源包共365个文件以94个Java后端源码和81个Vue前端页面为核心另含CSS样式、静态图片、JSON/XML配置、SQL脚本及项目说明文档等压缩后约12.04MB前后端分离的项目结构便于按模块导入与二次开发。已有46人参与学习下载拿到后可直接对照源码走通用户、课程、测评等核心流程可用于课程设计、毕业设计或毕业答辩演示省去从零搭建环境的时间同时也有助于巩固Spring Boot与Vue全栈实践能力。1. 在线学习平台的工程边界与设计起点在线学习平台的需求这两年变化很快课程从图文讲义转向高清视频、直播串讲和练习题库用户规模从几百同时在线涨到几万。最常见的认知是把它当作内容网站的加壳实际交付时最先出问题的往往不是页面而是视频防盗链、断点续播、学习进度准确性和热门课程的并发访问。我见过课程团队用一套普通内容管理系统的代码来改上线第一周就被脚本循环拉走付费课程的全部视频源文件。这个标题里的“设计与实现”核心在于账号权限、课程内容、视频播放、学习记录四条链路能否在并发下稳定协作。本文按工程落地顺序展开先定数据模型与技术选型再实现核心接口最后处理视频链路与性能验证。2. 在线学习平台的技术选型与数据库建模做在线学习平台的第一个决定不是选框架而是划清楚边界团队几个人运维、预期多少日活、视频放在哪。常见做法是前后端分离后端用 Spring Boot前端用 Vue 3数据库用 MySQL 8缓存用 Redis视频文件放对象存储并通过 CDN 分发。这套组合能支撑一万级日活并且每一层组件都有成熟运维经验排错时能够快速搜索到答案。这个阶段不建议引入微服务和消息队列在线学习平台的主要瓶颈在视频带宽和数据库查询用缓存与 CDN 就能解决没有必要为演示项目增加分布式复杂度。2.1 在线学习平台的分层架构与组件职责一个可交付的在线学习平台至少包含四层接入层、应用层、数据层、内容分发层。接入层用 Nginx 处理 TLS 终结、静态资源和 gzip 压缩应用层负责课程、订单、学习记录接口数据层由 MySQL 存结构化数据、Redis 保存会话和热点数据内容分发层管视频文件对象存储作为源站CDN 负责边缘加速。层级组件用途说明接入层NginxTLS 终结、静态资源、反向代理应用层Spring Boot 服务课程、订单、学习进度接口数据层MySQL 与 Redis结构化数据存储与热点缓存内容分发层对象存储与 CDN视频源文件存储、边缘缓存加速各层之间的接口一旦明确替换组件不会影响业务模块。例如视频播放地址由应用层生成签名 URL应用层不直接读取视频文件未来更换存储厂商时业务代码基本不用改。表结构设计应围绕课程内容域与用户学习域两条主线展开把权限、订单、学习记录分别建模而不是塞进一张大宽表。2.2 在线学习平台的核心表结构与字段设计课程内容域至少四张表course 存课程基本信息和售卖状态catalog 存章与节的树形结构section 存每一节的标题、排序和可播放内容的基础描述media_asset 存视频文件元数据、时长与转码状态。用户学习域至少三张表user 存登录账号与密码摘要study_record 存学习进度purchase_order 存支付相关的订单记录。核心字段定义如下CREATE TABLE course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, cover_url VARCHAR(512) DEFAULT , price_cents INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0 COMMENT 0未上架 1已上架 2下架, category_id INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category_status (category_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE study_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, course_id BIGINT NOT NULL, section_id BIGINT NOT NULL, progress_seconds INT NOT NULL DEFAULT 0, duration_seconds INT NOT NULL DEFAULT 0, finished TINYINT NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_section (user_id, section_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段层面的两个约定直接决定后续开发效率。一是所有业务表必须有 created_at 和 updated_at并且用数据库默认值维护避免应用层漏写时间字段。二是状态字段用 TINYINT 而不是 VARCHAR例如 course.status 用 0/1/2 表示未上架、已上架、下架避免业务代码里到处散落状态字符串。study_record 的用户与章节唯一索引是进度查询走索引更新的基础也是防止同一章节出现多条学习记录的兜底。2.3 在线学习平台的表关系与权限边界课程与章节之间建议用 catalog 表维护树形结构而不是在 section 表里直接加 parent_id 字段。在线学习平台的章和节通常是两层但课程以后可能扩展为单元、章、节三层结构直接用 parent_id 做查询会出现递归或多次自连接。常见做法是 catalog 表保存 course_id、section_id、parent_id、sort_order 四个必要字段查询某一课程时一次性取出整棵目录树在内存里组装成树形 JSON。权限边界在数据模型阶段就要考虑清楚user 表不直接放 role 字段而是拆出 role 表与 user_role 关联表。这样运营端和学员端共用同一张 user 表讲师入驻或人工审核时不需要迁移表结构。订单表中必须保存金额快照而不是联表查课程价格课程后来调价历史订单金额不会跟着变化。这样设计之后权限判断和账务核对都只需要查当前表不用额外拼接多张表的实时数据。3. 在线学习平台的课程管理与用户认证实现课程管理和用户认证是后台最核心的两个模块。这里用 Spring Boot 的代码片段展示常规实现方式重点放在判定逻辑和参数语义上。3.1 用户认证与最小权限的在线学习平台实现在线学习平台的用户角色按学员、讲师、运营、管理员四类设计认证方式一般用 JWT 加 Redis 的登录态管理。JWT 承担无状态认证Redis 保存当前有效 token实现会话可控。PostMapping(/api/auth/login) public ResponseEntity? login(RequestBody LoginRequest req) { User user userMapper.findByUsername(req.getUsername()); if (user null || !encoder.matches(req.getPassword(), user.getPasswordHash())) { return ResponseEntity.status(401).body(账号或密码错误); } String token jwtUtil.generateToken(user.getId(), user.getRoleIds()); redisTemplate.opsForValue().set( login:token: user.getId(), token, Duration.ofHours(12) ); return ResponseEntity.ok(new LoginResponse(token, user.getDisplayName())); }这段代码里有三个值得注意的点。passwordHash 字段存储的是 BCrypt 摘要不要用 MD5 或 SHA-1这两种算法在数据库泄露后就等于明文。Redis 里以用户 id 为 key 保存当前有效 token可以实现一个账号只允许一个会话也能在后台主动踢人下线。JWT 的过期时间与 Redis 的过期时间必须保持一致否则会出现 token 本身有效但登录态已被清除的不一致状态。权限上再补一层切面控制。运营端需要区分“能上架课程”和“能编辑课程”只靠一个角色字段判断扩展性不足。我一般定义一个 RequireRole({operator}) 注解用 AOP 在校验器里解析 JWT 携带的角色列表再决定放行或返回 403。注意管理员调整角色后旧 token 里携带的角色声明最长还要活到过期所以切面里优先从 Redis 读取用户最新角色而不是直接信任 token 里的旧声明。3.2 在线学习平台课程发布的状态机设计课程编辑不能直接改正式表而是先落草稿再发布。课程至少包含 DRAFT、PUBLISHED、OFFLINE 三种状态对应 course.status 字段。讲师修改课程时修改内容先写入草稿区域通过发布动作把草稿覆盖到正式字段。采用草稿与正式内容分离的方式可以避免学员正在学习时课程被部分更新。发布操作涉及正式表更新和修订记录插入必须放到同一个事务里START TRANSACTION; UPDATE course SET title ?, cover_url ?, status 1, updated_at NOW() WHERE id ?; INSERT INTO course_revision (course_id, title, cover_url, editor_id, created_at) VALUES (?, ?, ?, ?, NOW()); COMMIT;course_revision 是每次发布的修订记录用于运营回滚误操作也保留了“谁在什么时间发布了什么内容”的审计痕迹。事务同时包住正式表更新和修订记录插入否则会出现修订历史缺失但内容已变更的情况。章节的批量排序也属于高频操作前端一次提交整个章节顺序数组后端按传入顺序循环更新 sort_order。这里要对全部更新语句使用同一个事务并且在更新前比对原列表与新列表内容是否一致不一致直接拒绝避免两个运营同时编辑导致互相覆盖。3.3 视频资源与课程状态的联动校验课程上架时不能只改状态字段必须校验该课程下所有章节都已经关联可播放的视频文件否则用户购买课程后会发现某一章没有视频。这个校验放在发布动作内部而不是依赖前端按钮触发因为运营可能通过批量接口发布课程前端校验覆盖不到所有入口。public void publishCourse(Long courseId) { int videoCount sectionMapper.countSectionsWithoutMedia(courseId); if (videoCount 0) { throw new BusinessException(存在 videoCount 个章节未关联视频); } courseMapper.updateStatus(courseId, CourseStatus.PUBLISHED); }countSectionsWithoutMedia 使用聚合查询而不是在 Java 层循环判断避免课程上百个章节时产生 N 次查询。下架操作同样需要联动处理课程下架后已生成的播放签名应该尽快失效做法是在下架时清理该课程对应的所有 Redis 缓存 key同时把 media_asset 的 status 标记为不可用这样用户端即使拿到旧播放地址应用层也会在签名校验阶段拒绝服务。4. 在线学习平台的视频播放链路与进度上报视频服务的实现方式决定了在线学习平台的付费转化与版权安全。这一章不展开流媒体服务器搭建而是说明从课程章节到用户看到画面的完整链路以及如何保证学习进度准确落库。4.1 在线学习平台视频转码与HLS切片选择录播课程建议使用 HLS 作为交付格式。HLS 把视频切成若干 ts 分片配合 m3u8 索引文件支持拖动播放和码率自适应。原始 MP4 上传后用 ffmpeg 转码常见做法是生成 1080p、720p、480p 三档码率同时切片ffmpeg -i input.mp4 \ -vf scale1920:1080 -c:v h264 -b:v 4000k \ -hls_time 6 -hls_playlist_type vod \ -hls_segment_filename 1080p_%03d.ts 1080p.m3u8hls_time 参数表示每个分片的秒数设为 6 秒可以减少首屏加载时间但分片数量会相应增加。vod 模式生成的 m3u8 是完整列表适合录播课程直播场景应该用 event 或 live 模式。转码完成后ts 分片与 m3u8 文件一起上传到对象存储播放时由 CDN 分发。上传后要检查 m3u8 内部分片路径是否为相对路径一旦写成了绝对地址换存储桶后整段视频都会播放失败。4.2 签名播放地址与防盗链实现直接暴露对象存储文件 URL课程视频很快会被脚本抓走。常见做法是应用层生成带签名和过期时间的播放地址对象存储在收到请求时校验签名是否有效。不同云厂商 API 参数大同小异下面是阿里云 OSS 的示例import oss2 auth oss2.Auth(access_key_id, access_key_secret) bucket oss2.Bucket(auth, endpoint, bucket_name) url bucket.sign_url( GET, course/1080p.m3u8, 1800, params{x-oss-process: hls/sign} )签名有效期 1800 秒短于一个完整视频的播放时长用户播放到中途需要向应用层刷新地址。这种续期机制不只是为安全也保证了播放地址被分享后十分钟内自动失效。HLS 的 ts 分片地址同样需要签名否则破解者拿到一个 m3u8 就能拼接出全部片段。给分片加签名的常用做法是在 CDN 上配置 Referer 白名单加时间戳鉴权两层叠加后抓取成本会高到运营可以接受的程度。提示签名 URL 里携带的时间戳必须使用服务器时间容器环境常见问题是宿主机时区未同步导致生成地址与对象存储服务器时间偏移超过 1 分钟播放请求一直返回 403。4.3 学习进度上报的去重与幂等处理学习进度上报是典型的高频小请求学员每节课会产生几十次心跳后端必须确保写入不污染统计数据。前端每次上报携带一个 eventId 作为唯一标识POST /api/study/progress { courseId: 1024, sectionId: 58021, progressSeconds: 746, durationSeconds: 1210, eventId: a3f2c9e1-7d4b-4e1e-9b2f }后端先通过 Redis 的 setnx 判断 eventId 是否已经处理过已存在的直接返回成功。progressSeconds 使用“只增不减”的更新策略只有新值大于旧值时才更新 study_record 表。这样用户向前拖动播放进度不会被误判为学习进度。finished 字段的置位由 progressSeconds 与 durationSeconds 的比例决定建议阈值是 0.95不必等到最后一秒才标记完成课程结尾的演职员表会拖慢用户完成度。断点续播的查询接口体量小但命中率极高用户继续播放时查询到的学习记录应该放进 Redis 并设置 5 分钟过期而不是每次读 MySQL。同一用户短时间内切换章节学习Redis 命中率可以稳定超过 90%数据库的压力主要落在首次查询时。5. 在线学习平台的高并发优化与部署验证最后一步解决性能验证与交付问题。热点课程详情页、播放签名接口都在高并发路径上需要有可复现的压测手段与部署验证流程。5.1 热点课程缓存与缓存穿透处理课程详情页是典型的读多写少场景接口返回内容可以直接用 Redis 缓存整个对象。缓存 key 按业务区分例如 course:detail:1024。更新策略采用“更新数据库后删除缓存”而不是先更新缓存因为并发场景下先更新缓存容易造成两个线程互相覆盖旧值。删除缓存后下一次请求会重新回源查库并回填逻辑最简单也最可靠。对于限时抢课活动同一时刻大量请求会集中到少数课程 id 上缓存穿透会打到数据库。常用做法是课程 id 查询前加一层布隆过滤器拦截不存在的 id或者对查询不到的结果做 60 秒空值缓存。60 秒是一个折中值既能挡住刷接口的穿透流量又不会让新上架课程在 1 分钟内出现查不到内容的状态。5.2 服务端接口压测与视频链路验证上线前用 ab 压测接口是成本最低的验证方式。以课程详情页为例ab -n 2000 -c 100 -H Authorization: Bearer $TOKEN \ -H Accept-Encoding: gzip \ https://stage.example.com/api/course/1024-c 100 表示 100 并发这个规模足够发现死锁和慢 SQL。压测结果重点看 Requests per second 与 Time per request如果 100 并发下 P95 超过 300ms优先排查 SQL 是否走索引以及响应体是否返回了大字段。课程描述这种文本应该用列表接口裁剪字段详情接口再返回完整内容。视频播放链路的验证使用 curl -I 检查响应头curl -I https://cdn.example.com/course/1080p.m3u8?sign...返回 200 且 Content-Type 为 application/vnd.apple.mpegurl 说明播放链路正常返回 403 时先对比本地服务器时间与签名时间时间偏移是容器部署里最常见问题。5.3 Docker Compose 本地部署的最小验证环境项目交付时能做到一条命令拉起全部依赖能省掉大量联调时间。把 MySQL、Redis、Nginx、应用服务组合在一个 docker-compose 文件里MySQL 表结构用 init.sql 挂载初始化服务启动后通过健康检查接口确认应用已经注册到 Nginx 上游。每次代码变更后执行一组真实流程回归注册账号、登录拿 token、创建课程、关联视频、发布课程、播放视频、上报进度、查询进度。把这组流程脚本化后放进 CI比开发者在浏览器里手工点击一遍可靠得多也能在发布前暴露数据库迁移或接口参数不兼容的问题。本文还有配套的精品资源点击获取
返回列表