# 学习进度判断逻辑说明 ## 📊 核心逻辑 **学习进度 = (累计学习时长 / 课程总时长) × 100%** ## 🔍 详细判断流程 ### 1. 课件类型分类 系统将课件分为以下类型: | 类型 | 说明 | 是否参与进度计算 | 计算方式 | |------|------|----------------|---------| | `video` | 视频课件 | ✅ **是** | 基于学习时长 | | `document` | 文档课件(PDF、Word、Excel等) | ✅ **是** | 基于完成状态 | | `image` | 图片课件 | ✅ **是** | 基于完成状态 | | `text` | 文本课件 | ❌ **否** | - | ### 2. 进度计算步骤(混合计算模式) 系统采用**混合计算模式**,不同类型课件使用不同的计算方式: #### 步骤1:查询课程的所有课件 ```java // 查询所有类型的课件 SELECT * FROM courseware WHERE course_id = ? ``` **课件分类**: - 视频课件(`type = 'video'`) - 图片课件(`type = 'image'`) - 文档课件(`type = 'document'`,包括PDF) #### 步骤2:分别计算各类型进度 **2.1 视频进度计算(基于时长)** ```java // 计算视频总时长 videoTotalDuration = SUM(video.duration) // 估算视频学习时长(从总学习时长中提取) videoLearningDuration = totalDuration × 0.7 // 假设视频占70% // 计算视频进度 videoProgress = (videoLearningDuration / videoTotalDuration) × 100 ``` **关键点**: - 视频课件的 `duration` 字段必须设置(单位:秒) - 如果所有视频的 `duration` 都未设置,视频进度 = 0% **2.2 图片和PDF进度计算(基于完成状态)** ```java // 统计图片和PDF总数 totalNonVideoCount = imageList.size() + documentList.size() // 查询学习详情记录,估算已查看的课件数 viewedCount = min(学习详情记录数, totalNonVideoCount) // 计算非视频进度 nonVideoProgress = (viewedCount / totalNonVideoCount) × 100 ``` **关键点**: - 通过查询学习详情记录来判断学生是否查看过课件 - 每个图片/PDF查看一次默认算30秒学习时长 - 如果学生有学习记录,认为至少查看过部分课件 #### 步骤3:计算权重并综合 ```java // 计算权重 videoWeight = (视频数量 / 总课件数量) × 100 nonVideoWeight = ((图片数量 + PDF数量) / 总课件数量) × 100 // 综合进度 courseProgress = (videoProgress × videoWeight + nonVideoProgress × nonVideoWeight) / 100 ``` **计算公式**: ``` 综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100 其中: - 视频权重 = 视频课件数 / 总课件数 × 100% - 非视频权重 = (图片数 + PDF数) / 总课件数 × 100% ``` #### 步骤4:限制范围 ```java if (progress > 100) progress = 100 if (progress < 0) progress = 0 ``` 进度限制在 **0-100%** 之间。 ## 📝 代码位置 **文件**: ``` Study-Vue-redis/ry-study-system/src/main/java/com/ddnai/system/service/impl/study/StudyLearningRecordServiceImpl.java ``` **方法**: ```java private BigDecimal calculateCourseProgress(Long studentId, Long courseId, ...) ``` ## ⚠️ 重要限制 ### 1. 只计算视频课件 **当前逻辑**: - ✅ 只有 `type = 'video'` 的课件参与进度计算 - ❌ 图片、PDF、文档等**不参与**进度计算 **影响**: - 如果课程只有图片或PDF,没有视频,进度会显示 **0%** - 即使学生学习了图片或PDF,也不会增加进度 ### 2. 视频时长必须设置 **要求**: - 每个视频课件的 `duration` 字段必须设置(单位:秒) - 如果 `duration` 为 NULL 或 0,该视频不计入课程总时长 **影响**: - 如果所有视频的 `duration` 都未设置,课程总时长 = 0 → 进度 = 0% - 即使学生学习了很长时间,进度也会显示 0% ### 3. 学习时长统计 **统计方式**: - 学生观看视频时,系统会记录学习时长 - 每次学习都会累加到 `total_duration` 字段 - 图片、PDF 等非视频课件的学习**不统计**学习时长 ## 📊 实际案例 ### 案例1:正常情况 **课程配置**: - 视频1:12分钟(720秒) - 视频2:10分钟(600秒) - 课程总时长:1320秒(22分钟) **学生学习**: - 累计学习:10分钟(600秒) **进度计算**: ``` 进度 = (600 / 1320) × 100 = 45.45% ``` ### 案例2:视频时长未设置 **课程配置**: - 视频1:duration = NULL - 视频2:duration = NULL - 课程总时长:0秒 **学生学习**: - 累计学习:10分钟(600秒) **进度计算**: ``` 进度 = (600 / 0) → 返回 0%(避免除以0) ``` ### 案例3:只有图片和PDF **课程配置**: - 图片1:xxx.jpg - PDF1:xxx.pdf - 视频:无 - 总课件数:2个 **学生学习**: - 查看了图片和PDF - 学习详情记录:2条(每个课件查看一次) - 累计学习:60秒(2个课件 × 30秒/个) **进度计算**: ``` 视频进度 = 0%(没有视频) 非视频进度 = (2个已查看 / 2个总数) × 100 = 100% 视频权重 = 0 / 2 × 100 = 0% 非视频权重 = 2 / 2 × 100 = 100% 综合进度 = (0 × 0% + 100% × 100%) / 100 = 100% ``` ### 案例4:混合类型(视频+图片+PDF) **课程配置**: - 视频1:12分钟(720秒) - 图片1:xxx.jpg - PDF1:xxx.pdf - 总课件数:3个 **学生学习**: - 视频学习:6分钟(360秒) - 查看了图片和PDF - 累计学习:420秒(360秒视频 + 60秒图片PDF) **进度计算**: ``` 视频进度 = (360 / 720) × 100 = 50% 非视频进度 = (2个已查看 / 2个总数) × 100 = 100% 视频权重 = 1 / 3 × 100 = 33.33% 非视频权重 = 2 / 3 × 100 = 66.67% 综合进度 = (50% × 33.33% + 100% × 66.67%) / 100 = 83.33% ``` ## 🔧 如何修复进度问题 ### 问题1:进度显示0%,但学生已学习 **原因**:视频课件的 `duration` 未设置 **解决方案**: 1. 在管理端编辑视频课件,设置正确的时长(秒) 2. 执行SQL重新计算进度: ```sql UPDATE learning_record lr SET progress = ( SELECT CASE WHEN SUM(cw.duration) IS NULL OR SUM(cw.duration) = 0 THEN 0 ELSE LEAST(100, ROUND(lr.total_duration * 100.0 / SUM(cw.duration), 2)) END FROM courseware cw WHERE cw.course_id = lr.course_id AND cw.type = 'video' ) WHERE lr.total_duration > 0; ``` ### 问题2:图片和PDF进度计算(已解决) **当前实现**: - ✅ 图片和PDF**已参与**进度计算 - ✅ 采用基于完成状态的计算方式 - ✅ 通过查询学习详情记录来判断是否查看过 **计算逻辑**: 1. 统计课程中的图片和PDF数量 2. 查询学生的学习详情记录 3. 根据学习记录数量估算已查看的课件数 4. 计算完成度:已查看数 / 总课件数 × 100% **注意事项**: - 前端需要在学生查看图片/PDF时记录学习详情 - 每个图片/PDF查看一次默认算30秒学习时长 - 如果课程只有图片和PDF,没有视频,进度会基于学习时长计算 ## 📋 数据库字段说明 ### courseware 表(课件表) | 字段 | 类型 | 说明 | 是否必需 | |------|------|------|---------| | `id` | BIGINT | 课件ID | ✅ | | `course_id` | BIGINT | 课程ID | ✅ | | `type` | VARCHAR | 课件类型(video/document/image/text) | ✅ | | `duration` | INT | 视频时长(秒,仅视频类型) | ⚠️ 视频必需 | | `title` | VARCHAR | 课件标题 | ✅ | | `file_path` | VARCHAR | 文件路径 | ✅ | ### learning_record 表(学习记录表) | 字段 | 类型 | 说明 | |------|------|------| | `id` | BIGINT | 记录ID | | `student_id` | BIGINT | 学生ID | | `course_id` | BIGINT | 课程ID | | `total_duration` | INT | 累计学习时长(秒) | | `progress` | DECIMAL | 学习进度(0-100) | | `learn_count` | INT | 学习次数 | | `last_learn_time` | DATETIME | 最后学习时间 | ## 🎯 总结 ### 核心要点 1. **混合计算模式**: - ✅ 视频课件:基于学习时长计算 - ✅ 图片和PDF:基于完成状态计算 - ❌ 文本课件:不参与进度计算 2. **视频时长必须设置**:`duration` 字段必须大于0 3. **计算公式**: ``` 综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100 ``` 4. **进度范围**:限制在 0-100% 之间 5. **图片和PDF完成判断**: - 通过查询学习详情记录来判断 - 每个图片/PDF查看一次默认算30秒学习时长 - 如果学生有学习记录,认为至少查看过部分课件 ### 判断流程图 ``` 开始 ↓ 查询课程的所有课件 ↓ 分类统计:视频、图片、PDF ↓ ┌─────────────────┬─────────────────┐ │ 视频进度计算 │ 非视频进度计算 │ │ (基于时长) │ (基于完成状态) │ ├─────────────────┼─────────────────┤ │ 1. 计算视频总时长│ 1. 统计图片/PDF数│ │ 2. 估算视频学习 │ 2. 查询学习记录 │ │ 时长 │ 3. 估算已查看数 │ │ 3. 计算视频进度 │ 4. 计算完成度 │ └─────────────────┴─────────────────┘ ↓ 计算权重: - 视频权重 = 视频数 / 总课件数 - 非视频权重 = (图片+PDF)数 / 总课件数 ↓ 综合进度 = (视频进度 × 视频权重 + 非视频进度 × 非视频权重) / 100 ↓ 限制在 0-100% 之间 ↓ 返回进度 ``` ## 📚 相关文件 - **后端代码**:`StudyLearningRecordServiceImpl.java` - **诊断SQL**:`database_check_learning_progress.sql` - **修复SQL**:`database_fix_video_duration_and_progress.sql` - **说明文档**:`学习进度问题修复说明.md` ## 🔍 调试方法 ### 查看后端日志 后端已添加详细日志,重启服务后可以看到: ``` INFO - 开始计算学习进度 - 学生ID: 109, 课程ID: 3 INFO - 课程 3 包含 2 个视频课件 WARN - 视频课件 8 (亡羊补牢) 的时长未设置或为0 INFO - 课程 3 总时长: 1440秒 (24分钟), 2个视频有时长, 0个视频无时长 INFO - 学生 109 对课程 3 的累计学习时长: 3秒 (0分钟) INFO - 计算进度: 3 / 1440 * 100 = 0.21% INFO - 最终进度: 0.21% ``` ### 执行诊断SQL ```sql -- 查看学习记录和进度 SELECT lr.id, lr.course_id, c.course_name, lr.total_duration as '累计时长(秒)', lr.progress as '进度(%)', (SELECT SUM(duration) FROM courseware WHERE course_id = lr.course_id AND type = 'video') as '课程总时长(秒)' FROM learning_record lr LEFT JOIN course c ON lr.course_id = c.id; ```