
简介这是一套面向计算机专业本科生与毕业设计学生的在线学习平台完整实现方案基于Spring Boot主流技术栈构建聚焦视频教学、课件管理与多角色协同学习场景。项目采用B/S架构支持管理员、教师、学员三类用户权限体系涵盖课程/视频/课件/测试/评论等核心模块的CRUD功能及业务流程闭环可直接用于课程设计、毕设开发或教学系统二次开发。压缩包共2000个文件含70个Java后端控制器与服务类如CourseController、StudentServiceImpl、476个HTML前端页面、726个JS交互脚本、292个CSS样式文件及302个GIF动效资源辅以SQL建表脚本、配置文件与静态素材整体达691.26MB结构清晰、模块解耦度高。已有2235人学习下载提供开箱即用的可运行源码、完整数据库设计及典型业务逻辑实现便于快速理解Spring BootMyBatisMySQL全栈整合实践路径。 做在线学习平台这件事很多人第一反应是“去找一套现成的系统改一改”。但自己亲手搭过一次之后你会发现真正值钱的不是那几个页面而是课程管理、用户权限、学习进度、视频资源分发这些模块怎么组织。这篇文章写的是一套我从零开始设计并实现的基于 Spring Boot 的在线学习平台代码量不大但覆盖了一个真实学习平台该有的完整脉络登录注册、课程分类、视频课件上传与存储、学习进度记录、后台统计分析以及 Swagger 接口文档、Redis 缓存、全局异常处理这些工程化设施。适合正在做毕业设计、或者想上手 Spring Boot 做完整项目的 Java 开发者拿去参考。文章会从需求拆解一直讲到部署把关键代码的思路和容易踩坑的地方一起梳理清楚。1. 项目整体设计与技术选型思路1.1 在线学习平台的需求到底拆成了什么很多人一上来就急着建表、写接口结果做着做着发现业务边界混乱改来改去。我动手前习惯先做一次“业务拆解”把在线学习平台划成三个核心域。第一个是课程域。课程本身有分类、标题、封面、简介、价格、上下架状态课程内部又有章节和课时课时下挂视频、课件等资源。这个域是内容生产的核心也是数据关系最复杂的地方。第二个是用户域包含学生、教师、管理员三种角色。学生要能注册登录、选课、看课教师负责创建课程和上传学习资料管理员负责审核、统计和系统配置。第三个是学习行为域记录用户选了什么课、课程学到哪一节课、视频看到第几分钟以及最终的完成状态。这个域表面上不起眼但它是学习平台和普通视频网站的关键区别。用大白话举个例子课程就像超市货架上的商品章节和课时就是商品的规格和生产批次用户注册登录相当于办会员卡选课就是下单学习进度则是你买回去用了一段时间后平台帮你记住“这东西用到哪一步了”。想清楚这个类比后面设计接口和数据库时就会顺很多。1.2 技术栈选型与版本选型的讲究这套项目我用的核心框架是 Spring Boot。对比手写 Servlet、SSH 那种老方案Spring Boot 最大的价值是“约定优于配置”它把大量模板化配置吞掉了让你可以像搭积木一样引入 starter 依赖快速把 Web、数据访问、缓存、消息等能力装配起来。对个人开发者来说调试成本低、部署方式简单、社区资料丰富找任何问题都有现成案例。再加上国内企业级项目大量使用 Spring Boot这个选型不管是用在实际工作还是面试答辩里都非常有说服力。版本选择上我建议别盲目追新。目前有两条主路线一条是 Spring Boot 2.7.x 搭配 JDK 8适合老环境、旧教程资料多另一条是 Spring Boot 3.x 搭配 JDK 17命名空间从javax.*换成了jakarta.*底层 Servlet 容器也升级了。我在这个项目里选择的是 Spring Boot 3.2.5原因很简单它已经是稳定版本能兼容当前的 MySQL 驱动、MyBatis-Plus、SpringDoc 等生态组件而且长期维护更有保障。如果你实际运行时发现“springboot版本太高”导致各种兼容性问题通常不是 Spring Boot 本身的问题而是 JDK 版本没跟上、或者某个第三方 starter 的版本太老。数据库我用了 MySQL 8.0ORM 层选了 MyBatis-Plus缓存用 Redis接口文档用 SpringDocSwagger 3。文件存储先落地到本地磁盘通过资源映射对外提供访问链接生产环境可以平滑替换成 MinIO 或阿里云 OSS不影响业务代码。这套选型组合比较务实每个组件都是主流方案既能快速跑通又有充足的扩展空间。2. 核心功能模块的核心实现解析2.1 用户登录、权限控制与安全防绕过设计用户模块是整个平台的地基。我的用户表设计大概是id、username、password、real_name、role、avatar、status、create_time。其中密码绝对不能明文存储我统一用BCryptPasswordEncoder做加密每次登录时用相同算法校验。就算数据库泄露攻击者拿到的也是一堆不可逆的哈希值。登录流程上主流做法是 Spring Security JWT但我这套项目采用了更轻量的“拦截器 Redis Token”方案核心逻辑是用户登录成功后生成一个 UUID token存到 Redis 里key 类似login:token:{userId}同时把 token 返回给前端。前端把 token 放到请求头里后端拦截器统一校验。这样做的好处是服务端可以随时让 token 失效比如管理员封禁用户、用户修改密码后踢掉旧登录态都比纯 JWT 方案更好控制。如果你的简历里写了熟悉 Spring Security面试时把两套方案的优缺点讲清楚是很大的加分项。安全方面有一个容易犯迷糊的细节热词里常有人问“已有 token 验证怎么避开 token 验证使用 APIKey 安全对接”。注意这不是让你绕过登录校验而是要设计一套独立于用户体系的对外接口鉴权方式。我的做法是单独开一条/api/external/**的路径用专门的过滤器校验请求头里的X-Api-Key、时间戳和签名和前端用户的 token 校验完全分开。内部接口走用户 token外部合作方走 APIKey两条通道互不干扰既避免安全漏洞也让对接双方都清晰。2.2 课程与资源管理的实现细节课程模块的表结构我会拆成四张核心表分类表category、课程表course、章节表chapter、课时表lesson另外再加一个独立的资源表resource存视频、课件等文件的元信息。课程表里有关键字段teacher_id关联教师用户price表示价格status控制上下架课时表里video_url指向视频文件duration记录视频时长。为什么要把资源表单独拆出来因为一个课时可能同时有视频和 PDF 课件而一个 PDF 也可能被多个课程引用。如果把文件路径直接写在课时表里后期做资源复用和管理会很痛苦。资源表记录文件的原始文件名、存储路径、大小、上传人、上传时间业务表只存资源的 id这样课程被删除时不会误删共享文件需要迁移文件时只需要更新资源表里的路径。这里要特别提醒一个操作习惯上传文件时数据库里只存“相对路径”或“文件标识”不要把文件本身塞进数据库也不要把完整绝对路径写死。我在项目里用的是upload/2025/课程序号/文件名这种结构化目录后来维护起来非常省心。2.3 学习进度跟踪与统计报表的落地学习进度如果做成“用户学完一节课就标记完成”逻辑很简单但实际体验会很差。学生看视频看到一半中途退出下次进来应该从上次的位置继续。我的方案是建一张study_progress表字段包含user_id、course_id、lesson_id、position_seconds、completed、update_time。前端播放器每隔 10 秒上报一次播放位置后端只更新当前课时对应的记录不做太多额外计算。学完的判断我放在了后端如果上报的位置超过视频总时长的 95%就认为这一节完成并更新completed为 true。课程维度完成率由 SQL 聚合算出来某用户在某课程下已完成课时数除以总课时数得到百分比。前端进入课程详情页时调用接口查出用户的历史进度自动定位到最后一次播放位置体验就非常自然了。统计报表部分我提供了几个接口课程报名人数趋势按天统计新选课人数、最热课程 Top 10按学习人数排序、用户学习时长汇总。这些都靠几条 SQL 配合聚合函数解决不需要额外引入大数据组件。我的经验是先保证报表数据准确再考虑图表好看。报表接口返回结构化 JSON前端用 ECharts 渲染视觉效果同样在线。3. Spring Boot 开发实操关键环节的完整实现3.1 初始化项目与依赖配置老生常谈的版本坑创建项目的标准流程是在 IDEA 里选 Spring Initializr填好 groupId 和 artifactId勾选依赖后生成项目。但这里有个很常见的现象很多人发现在 IDEA 新建项目时没有 springboot 3.4.3 这个选项甚至列表里只有 3.1.x、3.2.x。原因有二一是 IDEA 内置的 Spring Initializr 列表不一定同步最新版本二是网络环境可能导致元数据拉取不全。解决办法很简单把 Server URL 换成阿里云的 Spring Initializr 镜像或者先选任意版本生成再手工在pom.xml里修改 Spring Boot 版本号保存后 Maven 会自动重新加载。一套可跑通的pom.xml核心依赖大概是这样的parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.5.0/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意如果你用的是 Spring Boot 3.xSwagger 就不能再引入老的springfox依赖了那套已经基本停止维护对jakarta.*支持也不好正确做法是用springdoc-openapi。我用 3.5.7 版本的 MyBatis-Plus 是因为它对 Spring Boot 3 兼容做得比较稳低于这个版本大概率启动报ClassNotFoundException。3.2 全局异常、缓存与资源映射的编码实践在线学习平台的接口数量不少如果每个接口都写 try-catch代码会膨胀得很严重而且容易漏。我的做法是统一用RestControllerAdvice做全局异常处理给异常分了几个等级业务异常BusinessException、参数校验异常MethodArgumentNotValidException、兜底的Exception。三个处理器分别返回对应的错误码和提示信息前端只要统一处理 JSON 结构就行。Redis 在项目里的用法很直接一是存登录 token设置过期时间比如 24 小时二是做缓存比如首页热门课程列表。我直接基于StringRedisTemplate写了一个简单的缓存工具类查询时先读 Redis没有再查数据库并回填缓存。这样比只在方法上加Cacheable更灵活也更容易控制各种缓存更新场景。资源映射是个非常容易踩坑的地方。视频和课件存在本地磁盘后要给前端一个可访问的 URL。我在WebConfig里这样配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }uploadPath是配置文件里定义的绝对路径比如D:/online-learning/upload/。这里最关键的坑是addResourceLocations的路径末尾必须带一个/否则 Spring 会返回 302 重定向浏览器最终拿不到文件。我第一次写的时候漏了排查了半天才发现是路径格式问题。3.3 事件机制与流程整合的工程化处理Spring Boot 的事件机制是很多人忽略的好东西用好了能让代码解耦很多。我在“课程发布”这个场景里应用了它教师点击发布课程后主流程只需要把课程状态改成“已发布”然后发布一个CoursePublishedEvent。监听器分别处理两件事给所有关注该分类的学生发系统通知、更新首页的热门分类缓存。实现上定义一个事件类比如CoursePublishedEvent里面带上courseId。发布处applicationEventPublisher.publishEvent(new CoursePublishedEvent(this, courseId));监听器EventListener public void handleCoursePublished(CoursePublishedEvent event) { Long courseId event.getCourseId(); // 发送站内通知 notifyService.sendCoursePublishNotify(courseId); // 清理相关缓存 cacheService.clearCourseListCache(); }如果后面要加“给教师发送短信提醒”之类的需求只需要新增一个监听器不用改动发布逻辑维护起来很舒服。另外如果你想进一步加“课程审核流程”可以把 Flowable 集成进来把教师提交课程到管理员审核再到发布的过程做成 BPMN 流程定义。这里我不详细展开但提一句Flowable 整合 Spring Boot 的思路就是引入flowable-spring-boot-starter在resources/processes目录下放 BPMN 文件然后通过RuntimeService启停流程实例。3.4 大文件上传下载的实现方案在线学习平台绕不开视频上传而视频文件动辄几百 MBSpring Boot 默认上传大小限制是 1MB不配置肯定会失败。你需要先改配置文件spring: servlet: multipart: max-file-size: 2GB max-request-size: 2GB但如果前端一次上传几百 MB 的视频网络稍微波动就会失败而且失败后要整个重传体验很差。这里我的做法是“分片上传”前端把文件切成 5MB 左右的分片逐片传给后端全部传完后再触发合并。后端接收每个分片时携带fileMd5、chunkIndex、chunkTotal把分片存到以fileMd5命名的临时目录。合并方法用 Java 的 FileChannel 完成public void mergeChunks(String fileMd5, String fileName, int chunkTotal) { String chunkDir uploadDir / fileMd5; String targetFile uploadDir / fileName; try (FileChannel out FileChannel.open(Paths.get(targetFile), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { for (int i 0; i chunkTotal; i) { try (FileChannel in FileChannel.open(Paths.get(chunkDir, i .part))) { in.transferTo(0, in.size(), out); } } } }合并时使用CREATE_NEW是为了防止重复合并导致文件被二次叠加如果有秒传需求可以在上传第一片前先查一下这个文件是否已经存在。文件下载和播放方面我也踩过一个不小的坑尽量不要用 Controller 把视频文件读成byte[]后返回因为这样会丢掉 Range 请求头浏览器播放器就没法拖进度条。正确做法是直接用本地文件file:路径的静态资源映射Spring 会自动响应 Range 请求实现播放器拖动和断点续传。4. 常见问题速查与排障实录4.1 启动即挂版本与依赖兼容问题我在开发这套项目的过程中遇到最多的就是启动异常而且绝大多数和版本兼容有关。整理几个高频场景。第一类java.lang.UnsupportedClassVersionError。这说明你用的 JDK 版本太老Spring Boot 3.x 要求 JDK 17 及以上。如果你机器上同时装了多个 JDK检查 IDEA 的 Project Structure 和运行环境里的 JDK 是否一致。第二类HTTP 端口被占用启动日志里出现Port 8080 was already in use。要么关掉占用的进程要么在application.yml里改成server.port: 8081。第三类Maven 依赖下载慢或者下载失败。国内环境建议在settings.xml里配置阿里云 Maven 镜像下载速度能快一个量级也能避免很多奇奇怪怪的“包找不到”问题。第四类Redis 相关依赖引入后每次启动都因为连不上 Redis 报错。开发环境下如果暂时不需要 Redis可以先把spring-boot-starter-data-redis注释掉或者把自动配置排除掉否则启动流程会尝试建立连接。4.2 文件与存储的坑文件功能是学习平台的重灾区我把几个典型问题列成下面的速查表方便你遇到时直接对照现象大概率原因处理办法访问上传文件 404资源映射路径的addResourceLocations末尾少了/补上末尾的/确认磁盘绝对路径存在上传大视频失败Nginx 的client_max_body_size未调大在 nginx.conf 的 server 块里设为client_max_body_size 2g;中文文件名打开乱码前端没有对文件名做 URL 编码转码后再存储和拼 URL例如URLEncoder.encode(name, UTF-8)视频无法拖动进度Controller 直接返回byte[]丢了 Range 支持改用静态资源映射或自定义支持 Range 的接口合并后的文件播放损坏分片顺序错乱或重复合并合并前校验chunkTotal使用CREATE_NEW防止覆盖4.3 部署上线jar 包与 Docker 两种方式开发完成后我通常直接打包运行。Spring Boot 项目打成 jar 包很简洁命令是mvn clean package在服务器上运行nohup java -jar online-learning.jar app.log 21 日志会输出到app.log后台运行关掉终端也不会停。这个方法适合快速验证和生产初版。Docker 部署也是现在的主流方式。我的 Dockerfile 很简单FROM openjdk:17-jdk-alpine WORKDIR /app COPY target/online-learning.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]如果数据库和 Redis 也要容器化我会写一个docker-compose.yml把 app、mysql、redis 三个服务编排在一起加上depends_on和健康检查控制启动顺序。注意一点MySQL 容器首次启动需要时间初始化Spring Boot 服务如果启动太快会连接失败所以要么在 Compose 里配置服务健康检查要么在 Spring Boot 里设置数据库连接的重试机制。4.4 接口安全与监控的若干建议安全是学习平台这类带用户资料和付费功能的系统必须重视的部分。除了前面说的密码加密和 APIKey 鉴权还有几个细节一是登录接口要做频率限制防止暴力破解二是所有涉及用户信息的接口都必须校验当前登录人是否有权限访问三是响应里不要把密码、token 等敏感字段原样返回用 VO 对象隔离实体类。监控方面我引入了spring-boot-starter-actuator把/actuator/health当作健康检查接口配合 Spring Boot Admin 可以直观看到内存、线程、接口调用情况。日志方面用 Logback设置不同包的日志级别比如 SQL 日志只在开发环境打印。排查安全异常时我会在拦截器里记录违规请求的 IP、URL、请求参数方便事后复盘。5. 几次踩坑后我留下的一些实操心得项目做完回头看有几个教训特别深刻。第一版本锁定比功能开发更重要。Spring Boot、JDK、MyBatis-Plus 三者版本不一致时报错信息往往很绕而且搜索引擎里未必找得到一模一样的答案。我的做法是每次新建项目前先去官网看一眼各组件推荐的版本组合把这些版本号写进一个README.md里后续克隆到新电脑也能快速复现环境。第二先做核心闭环再谈扩展。我最初规划时想过加入积分系统、在线考试、直播模块后来发现如果不先把“课程—学习—统计”这条主线打通所有扩展都是空中楼阁。实际开发两个月后真正稳定上线的仍然是核心链路其他功能只留了表结构。第三调试工具一定要配齐。Swagger 接口文档、Postman 环境变量、日志级别切换这几个工具能显著缩短排错时间。我自己在工程里加了 springdoc 之后很多接口不用再前后端联调时临时抓包直接打开 Swagger 页面就能自测。第四数据库设计的优先级高于代码设计。表字段命名不统一、缺少唯一索引、没有合理的外键约束这些隐患会随着业务迭代越放越大。如果你也想拿这个项目面试可以把“数据库模型如何支撑业务扩展”作为主要切入点去讲效果往往比单纯讲代码实现更好。本文还有配套的精品资源点击获取