Java学习平台管理系统毕设实战:从表设计到进度追踪全解析

发布时间:2026/9/15 5:49:44
Java学习平台管理系统毕设实战:从表设计到进度追踪全解析 简介基于Java的学习平台管理系统毕业设计源码面向计算机相关专业学生与需要快速搭建在线学习平台的开发者覆盖课程发布、学习进度跟踪与考试评估三大核心业务模块。资源包共46个文件包括Java源代码、pom.xml构建配置、properties环境配置、README说明文档以及41张jpg格式的界面运行截图压缩包整体大小仅3.95MB目录结构层次清晰便于对照部署与二次开发。目前已有174人学习下载适合用于毕业设计参考、Java Web实战练习或在线教育平台原型搭建。借助源码可以熟悉学习管理系统中的数据库设计、前后端交互逻辑、随机组卷与自动评分流程截图和说明文档则能帮助快速理解系统功能节省整理原型与撰写设计文档的时间。1. 用 Java 学习平台管理系统作为毕设第一步先定边界很多人的 Java 毕设从“注册、登录、增删改查”开始最后交上去被评委一句“这不就是一个后台模板”噎住。基于 Java 的学习平台管理系统真正值钱的地方在于把“学习”这件事的闭环做完整学生注册后能浏览课程、选择课程、按章节观看视频并留下学习进度教师能维护课程内容与视频管理员能审批上下架。这个题目很适合毕业设计因为它边界清楚、技术栈常见却需要你认真设计表关系、权限和接口状态。哪怕你只有两个月用 Spring Boot MySQL Vue 这套组合也能完整跑通。下面直接拆开讲刚好覆盖从表设计到答辩演示的全过程。2. 先画业务图Java 学习平台的三种角色和两套核心流程做管理系统最怕一上来就写代码角色没分清接口就会越写越乱。学习平台的用户至少有三类学生、教师、管理员。教师建课、建章节、传视频学生选课后按章节学习系统记录进度管理员负责审核课程、管理用户状态。这样划分之后表结构、接口、页面都能对号入座。2.1 学习平台的角色权限怎么划分学生负责“学”浏览课程、选课/退课、观看视频、查看自己的学习进度。教师负责“教”维护课程信息、维护章节、上传视频、查看选课人数。管理员负责“管”审核课程上下架、禁用违规用户、看整体数据。三条权限线不能混在一起否则学生接口会被拿去调用教师上传接口。权限方案上Spring Security 对毕设来说偏重我一般用“拦截器 角色枚举”就够了。登录成功后签发 JWTtoken 里放 userId 和 role拦截器校验每个请求头里的 token。角色判断可以简单落在路径上比如/api/admin/**要求必须是 ADMIN/api/teacher/**要求必须是 TEACHER。你也可以用自定义注解但毕设用路径约束已经足够清晰。提示权限模型优先做“接口级别”的角色校验不要只在前端隐藏按钮。否则用 Postman 绕开按钮直接调接口后台就会被裸奔。还有一点容易被忽略用户状态。平台要允许管理员禁用某个用户而不是直接 delete。所以用户表里要有一个 status 字段登录和每次请求都要检查。禁用用户即使带着旧 token 也不能访问充许以内的数据这个细节在答辩时提一句明显比“登录能通就行”高一个级别。2.2 最小功能集合与加分项毕设最忌讳功能贪多做了一个论坛、一个答题系统、一个弹幕模块最后没有一个功能是完整的。按我习惯的划分方式下面这些模块才是必须站得住的。模块必做加分项说明用户认证注册、登录、退出验证码、找回密码token 有效期与刷新机制课程模块课程列表、详情、上下架课程封面、分类筛选状态字段控制显示章节与视频章节 CRUD、视频上传播放分片上传、日志记录视频大小限制是重点选课管理选课/退课、重复选课校验选课审核唯一索引防止重复选课学习记录保存播放位置、学习状态课程完成度统计多端并发更新是难点功能边界定了以后工期基本就能估出来。如果只做必做项一个月左右能把后端主干写完加上加分项再缓冲两周做前端和联调时间上比较舒服。2.3 前后端分离后接口约定先于开发现在毕设基本都走前后端分离后端只返回 JSON前端用 Vue 或者原生 HTML 都行。为了不让前端等接口第一件事就是约定统一返回结构。我常用的结构很简单{ code: 200, message: success, data: { userId: 1, role: STUDENT } }这个结构里code200 表示成功其他值表示业务错误message 给前端弹提示用data 只放业务数据。分页的时候再把 data 包一层比如data.page、data.records。前端 axios 拦截器统一判断 code不是 200 就跳登录页或者弹错误信息。这样后端接口无论怎么增加前端处理逻辑都不用大变。后端接口定义的顺序建议是登录 → 课程列表 → 课程详情 → 选课 → 章节列表 → 上报进度 → 个人进度。这条链路通了系统的主干就已经完成剩下的都是分支功能。3. Java 学习平台管理系统表设计用户、课程、章节和选课记录怎么落表先把表关系理清楚再写 DDL。用户与课程是多对多选课表做中间表课程与章节是一对多章节与视频是一对多学习记录和用户、视频都关联。所有外键都只存逻辑 id不建物理外键原因是毕设后期经常要改数据物理外键会在删除课程时制造很多限制。3.1 表结构设计的三个习惯主键一律用自增 BIGINT业务编号如果需要展示再单独加一个字段。状态字段统一用 TINYINT0 表示待定/未发布1 表示正常/已发布2 表示禁用/下架每张表都要加注释。时间字段用 DATETIME不要图省事存 VARCHAR否则排序、统计、前端格式化都会很痛苦。密码字段长度要给够BCrypt 加密后的字符串是 60 个字符左右password 字段至少 VARCHAR(100)。如果你用 SHA-256 直接加密虽然也能跑但答辩时容易被追问“密码为什么不用盐”。BCrypt 自带盐是相对稳妥的选择。3.2 核心表 DDL可以直接复制微调下面是我会给出的最小表结构覆盖了用户、课程、章节、视频、选课、学习记录六张核心表。CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(50) DEFAULT COMMENT 真实姓名, role VARCHAR(20) NOT NULL COMMENT STUDENT/TEACHER/ADMIN, status TINYINT DEFAULT 1 COMMENT 1有效 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) COMMENT用户表; CREATE TABLE course ( id BIGINT AUTO_INCREMENT PRIMARY KEY, teacher_id BIGINT NOT NULL COMMENT 教师用户id, title VARCHAR(100) NOT NULL, summary VARCHAR(500) DEFAULT , cover_url VARCHAR(200) DEFAULT , status TINYINT DEFAULT 0 COMMENT 0未发布 1已发布 2下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_teacher (teacher_id) ) COMMENT课程表; CREATE TABLE course_chapter ( id BIGINT AUTO_INCREMENT PRIMARY KEY, course_id BIGINT NOT NULL, title VARCHAR(100) NOT NULL, sort_no INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_course (course_id) ) COMMENT章节表; CREATE TABLE course_video ( id BIGINT AUTO_INCREMENT PRIMARY KEY, chapter_id BIGINT NOT NULL, video_name VARCHAR(200) NOT NULL, video_url VARCHAR(300) NOT NULL, duration INT DEFAULT 0 COMMENT 视频总时长/秒, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_chapter (chapter_id) ) COMMENT视频表; CREATE TABLE user_course ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, course_id BIGINT NOT NULL, status TINYINT DEFAULT 1 COMMENT 1学习中 0已退课, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_course (user_id, course_id) ) COMMENT选课记录表; CREATE TABLE study_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, course_id BIGINT NOT NULL, video_id BIGINT NOT NULL, last_position INT DEFAULT 0 COMMENT 最后播放位置/秒, progress TINYINT DEFAULT 0 COMMENT 完成度0-100, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_user_video (user_id, video_id) ) COMMENT学习记录表;这里故意不建物理外键。course_chapter.course_id只是逻辑关联删除课程时由 service 层控制先删子表再删课程MyBatis-Plus 的批量删除也不会被数据库约束卡住。course_video挂在章节下面好处是前端按章节取视频列表时只需要一个 chapter_idSQL 写起来非常顺。核心表关系可以用这张表概括表关联字段关系courseteacher_id → sys_user.id教师一对多课程course_chaptercourse_id → course.id课程一对多章节course_videochapter_id → course_chapter.id章节一对多视频user_courseuser_id、course_id用户与课程多对多study_recorduser_id、video_id用户与视频学习记录3.3 选课和学习记录的坑唯一索引与原子更新选课最容易出现前端连点两次后端插入两条记录。user_course表加(user_id, course_id)唯一索引后数据库层面就能挡住重复选课。学习记录表也同理(user_id, video_id)保持唯一这样一条视频只对应一条进度记录。上报学习进度时会遇到“先查再更新”的并发问题两个请求同时查发现 record 不存在然后各自 insert结果唯一索引直接报错。正确做法是把插入和更新合并成一条 SQL利用 MySQL 的ON DUPLICATE KEY UPDATE。INSERT INTO study_record (user_id, course_id, video_id, last_position, progress) VALUES (#{userId}, #{courseId}, #{videoId}, #{position}, #{progress}) ON DUPLICATE KEY UPDATE last_position IF(VALUES(last_position) last_position, VALUES(last_position), last_position), progress GREATEST(VALUES(progress), progress), update_time CURRENT_TIMESTAMP;这段 SQL 的逻辑是如果记录不存在就插入存在就更新。更新时last_position只允许变大不允许变小progress取两边最大值。这能解决学生同时在手机和电脑上播放、旧请求晚到导致进度回退的问题。把这条 SQL 写进 mapper 里比先 select 再 update 少了至少两次数据库往返。4. 用 Spring Boot 搭出 Java 学习平台后端最小骨架统一返回体、JWT 登录与拦截器毕设后端没必要一上来就按阿里规范拆五层我习惯先保证接口能通再按controller / service / mapper / entity / common分包。骨架搭好后后面每加一个功能都只是往这个结构里填代码的重复劳动。4.1 pom.xml 依赖与启动类Spring Boot 版本建议用 2.7.x 或者 3.x我用 2.7.18 是因为它和 MyBatis-Plus、JWT 生态兼容最稳。3.x 要注意javax改成了jakarta很多旧教程代码不能直接复制。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency /dependenciesmybatis-plus 的作用是简化单表 CRUD写登录、查询课程列表时不需要自己拼基础 SQL。如果不用它只用 MyBatis 原生写法也可以但每个 mapper 都要写 XML代码量会多三分之一。4.2 统一返回体 Result 与异常处理所有 Controller 都返回ResultT前端解析逻辑就统一了。Data public class ResultT { private int code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 500; r.message message; return r; } }这个类里没有复杂逻辑。code 约定成 200 成功、401 未登录、403 无权限业务校验失败可以统一返回 500。你不需要为每个错误场景单独定义异常码毕设阶段能准确区分“用户不存在”和“密码错误”就够了。再加上一个RestControllerAdvice全局异常处理器把校验异常、业务异常统一转成Result.errorController 里就不用层层 try-catch。4.3 JWT 签发与登录接口JWT 的 secret 要足够长至少 32 字节否则 HS256 算法会直接抛异常。密钥不要写死在代码里放到application.yml启动时读取。public class JwtUtil { private static final String SECRET your-secret-key-must-be-at-least-32-bytes; public static String createToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes())) .build() .parseClaimsJws(token) .getBody(); } }登录接口调用 userMapper 根据 username 查出用户再用BCrypt.match(password, user.password)校验。校验成功后签发 token并把用户基本信息一起返回给前端。token 有效期设 24 小时快过期时由前端重新登录这个策略足够支撑答辩演示。4.4 拦截器校验请求所有需要登录的接口都要经过拦截器拦截器只做一件事解析 token把用户信息塞进请求上下文。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } Claims claims JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, Long.parseLong(claims.getSubject())); request.setAttribute(role, claims.get(role)); return true; } }注册拦截器时要放行登录接口、注册接口、课程列表和视频播放等公开资源。否则前端还没登录就被 401 拦住。角色校验可以在拦截器里按路径前缀做也可以在 Controller 方法上用注解 AOP毕设用前者更直观。拦截器执行完后Token 里的信息只存在 request attribute 里业务代码拿起来并不方便。我会再写一个UserContext用 ThreadLocal 存当前用户 ID 和角色请求结束后必须 remove防止线程池里的线程串了用户数据。5. Java 学习平台的视频上传、断点续传与学习进度记录实现技巧视频是学习平台最脆弱的点。很多毕设挂在“视频传不上去”“只支持小文件”“进度记录存成多条”。这里给出一套能跑通的最小方案能满足演示也足够你在答辩时解释清楚。5.1 视频保存到本地还是对象存储存储方案选错后期会浪费大量时间。本地磁盘最适合毕设演示MinIO 适合想体现容器化能力对象存储则要申请密钥和考虑费用。方案优点缺点毕设推荐度本地磁盘部署简单不需要额外服务大文件占磁盘备份靠复制高MinIO支持分片上传有桶权限需要单独安装和维护服务中阿里云 OSS上传快有 CDN 能力需要密钥可能产生费用低选择本地磁盘时要把 upload 目录映射成/uploads/**静态资源。后端 Controller 返回/uploads/xxx.mp4前端video标签直接播放这个 URL。整个链路不依赖外网答辩现场即使断网也能演示。5.2 上传接口的最小实现上传接口的核心就是MultipartFile。校验文件非空之后再校验扩展名最后用 UUID 生成文件名防止中文文件名乱码。PostMapping(/api/teacher/video/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件为空); } String original file.getOriginalFilename(); String ext original.substring(original.lastIndexOf(.)).toLowerCase(); if (!Arrays.asList(.mp4, .avi, .flv, .mov).contains(ext)) { return Result.error(不支持的视频格式); } String filename UUID.randomUUID().toString().replace(-, ) ext; File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, filename)); return Result.ok(/uploads/ filename); }上传接口的路径是/api/teacher/video/upload也就是说该接口受角色保护学生不能调用。uploadPath放在配置里application.yml里指定一个绝对路径避免打包后找不到相对路径。文件大小限制也要同步调整Spring Boot 默认只有 1MB不调大上传必失败。spring: servlet: multipart: max-file-size: 2GB max-request-size: 2GBmax-file-size限制单个文件max-request-size限制一次请求的总大小。把两个都调到 2GB 是为了本地演示大视频。如果上传条件不好也可以把限制调小到 200MB并告诉评委“真实环境会做转码和分片”。5.3 学习进度上报接口防止旧数据覆盖新数据前端 HTML5 的video标签有timeupdate事件5 秒上报一次当前播放秒数。接口收到后更新study_record表。关键更新逻辑不能用简单赋值要用“只增不减”的方式。Update( UPDATE study_record SET last_position GREATEST(last_position, #{position}), progress GREATEST(progress, #{progress}), update_time NOW() WHERE user_id #{userId} AND video_id #{videoId} ) int updateProgress(Long userId, Long videoId, int position, int progress);GREATEST(a, b)返回两个值里较大的那个。这个 SQL 保证了进度永远只前进不后退。如果学生在两个设备同时播放不同设备上报的位置可能会晚到但只要 MySQL 层面做了这个判断就不会出现“看了一大半下次打开又从头开始”的情况。如果记录不存在则要用第 3.3 节那条INSERT ... ON DUPLICATE KEY UPDATE。这里不重复贴 SQL它在 mybatis 的 mapper 里可以直接复用。上报接口的 Service 层需要加Transactional先更新 study_record再更新课程完成人数两个操作要么都成功要么都失败。5.4 课程完成度统计给 course 表加一个finished_count字段每当学习记录 progress 达到 95 以上且之前不到 95 时这个字段加一。判断逻辑不要放在前端前端可以伪造上报数据只信任后端计算。具体实现时在report方法里先查当前记录原来的 progress再判断是否跨越 95 这个阈值。这条逻辑不难但答辩时能讲清楚“如何避免一条视频重复计算完成次数”属于一个很实在的细节亮点。6. Java 学习平台毕设答辩前的数据准备与验证清单6.1 用 CommandLineRunner 预置演示账号和课程演示最怕现场数据库是空的临时录数据又浪费时间。我一般会写一个CommandLineRunner在项目启动时检查用户表如果没数据就插入预设账号和一门示例课程。Component public class DataInitializer implements CommandLineRunner { Autowired private UserMapper userMapper; Override public void run(String... args) { if (userMapper.selectCount(null) 0) { return; } // 插入 admin / teacher / student 三个演示用户 // 密码统一用 BCrypt 加密后写入 } }这个组件只在表为空时执行重复启动项目不会产生脏数据。演示密码不要用明文存储数据库中保存 BCrypt 加密串登录逻辑本身也是拿 BCrypt 去校验这样能体现你理解密码存储的基本要求。6.2 用 Postman 冒烟测试关键路径答辩前不要等前端页面全部完成先用 Postman 把后端接口跑一遍确保核心链路是通的。下面是一份冒烟测试清单测试场景方法请求路径预期结果登录POST/api/auth/login返回 code200 且带 token课程列表GET/api/course/list?page1size10返回分页数据上传视频POST/api/teacher/video/upload返回 /uploads/xxx.mp4上报进度POST/api/student/progress/report数据库 last_position 更新越权访问GET/api/teacher/course/list返回 401 或 403测试时重点关注越权访问。用学生 token 去访问教师接口如果返回 200说明拦截器配置有问题必须立即修复。6.3 打包部署与快速验证后端验证没问题后打 jar 包在服务器上跑。最小命令只需要两行mvn clean package -DskipTests java -jar target/learning-platform-0.0.1-SNAPSHOT.jar --server.port8080启动后先用 curl 验证登录接口是否通curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123}curl 返回的 JSON 里如果出现 token说明应用已经正常起来了。后续接口再带上Authorization: Bearer xxx即可。如果你嫌 curl 参数多用 Postman 已经验证过的集合再点一遍也行。6.4 答辩展示时的一个必杀技在演示环节不要从头到尾点页面那是给评委看你用没用心。准备一个固定脚本登录后 30 秒内完成“选课 → 播放视频 → 暂停 → 刷新页面 → 继续播放”重点让评委看到视频进度没有从零开始。这时顺便打开数据库客户端展示study_record表里的last_position和progress字段在实时变化。最后再切到教师账号展示该课程人数增加了 1。整套流程下来评委对你的理解程度基本不需要多问。如果被追问“系统能不能支撑高并发”不要慌先用“表里加了唯一索引和进度乐观更新”解释当前设计再说真实环境会把视频访问拆分到独立资源服务器。把这两句话讲清楚就已经超过大多数毕设水平。本文还有配套的精品资源点击获取