当前位置: 首页 > news >正文

做新闻封面的网站网站简约式布局特点

做新闻封面的网站,网站简约式布局特点,新闻发布最新新闻,云南推广经过前面的努力#xff0c;我们已经完成了《我的课程表》相关的功能的基础部分#xff0c;不过还有功能实现的并不完善。还记得昨天给大家的练习题吗#xff1f;《查询我正在学习的课程》#xff0c;在原型图中有这样的一个需求#xff1a; 我们需要在查询结果中返回已学习…经过前面的努力我们已经完成了《我的课程表》相关的功能的基础部分不过还有功能实现的并不完善。还记得昨天给大家的练习题吗《查询我正在学习的课程》在原型图中有这样的一个需求 我们需要在查询结果中返回已学习课时数、正在学习的章节名称。虽然我们在learning_lesson表中设计了两个字段 learned_sections已学习章节数 latest_learn_time最近学习时间 但是这几个字段默认都是空或0我们该如何得知用户到底学习了几节最近一次学习是什么时候最近一次学习的是第几章节呢 以上的问题归纳下来就是一个学习进度统计问题这在在线教育、视频播放领域是一个非常常见的问题。因此学会了解决这套解决方案你就能游刃有余的应对相关行业的类似问题了。 大家在学习这套解决方案的同时也可以增强下面的能力 需求分析和表设计能力 复杂SQL的编写能力 处理高并发写数据库的能力 1.分析产品原型 大部分人的学习自律性是比较差的属于“买了就算会了”的状态。如果学员学习积极性下降学习结果也会不尽人意从而产生挫败感。导致购买课程的欲望也会随之下降形成恶性循环不利于我们卖课。 所以我们推出学习计划的功能让学员制定一套学习计划每周要学几节课。系统会做数据统计每一周计划是否达标达标后给予奖励未达标则提醒用户达到督促用户持续学习的目的。 用户学习效果好了产生了好的结果就会有继续学习、购买课程的欲望形成良性循环。 因此学习计划、学习进度统计其实是学习辅助中必不可少的环节。 1.1.分析业务流程 我们从两个业务点来分析 学习计划 学习进度统计 1.1.1.学习计划 在我的课程页面可以对有效的课程添加学习计划 学习计划就是简单设置一下用户每周计划学习几节课 这个在昨天的数据库设计中已经有对应的字段了只不过功能尚未完成。 有了计划以后我们就可以在我的课程页面展示用户计划的完成情况提醒用户尽快学习 可以看到在学习计划中是需要统计用户“已经学习的课时数量”的。那么我们该如何统计用户学了多少课时呢 1.1.2.学习进度统计 要统计学习进度需要先弄清楚用户学习的方式学习的内容。在原型图《课程学习页-录播课-课程学习页-目录》中可以看到学习课程的原型图 一个课程往往包含很多个章chapter每一章下又包含了很多小节section。章本身没有课程内容只是划分课程的一个概念因此统计学习进度就是看用户学了多少个小节。 小节也分两种一种是视频一种是每章最后的阶段考试。用户学完一个视频或者参加了最终的考试都算学完了一个小节。 考试只要提交了就算学完了比较容易判断是否学完。但是视频该如何统计呢达到什么样的标准才算这一小节的视频学完了呢 这里我们不能要求用户一定要播放进度到100%太苛刻了。所以天机学堂的产品是这样设计的 因此只要视频播放进度达到50%就算是完成本节学习了。所以用户在播放视频的过程中需要不断提交视频的播放进度当我们发现视频进度超过50%时就可以标记这一小节为已学完。 当然我们不能仅仅记录视频是否学完还应该记录用户具体播放的进度到了第几秒。只有这样在用户关闭视频再次播放时我们才能实现视频自动续播功能用户体验会比较好。 也就是说要记录用户学习进度需要记录下列核心信息 小节的基础信息id、关联的课程id等 当前的播放进度第几秒 当前小节是否已学完播放进度是否超50% 用户每学习一个小节就会新增一条学习记录当该课程的全部小节学习完毕则该课程就从学习中进入已学完状态了。整体流程如图 1.2.业务接口统计 接下来我们分析一下这部分功能相关的接口有哪些按照用户的学习顺序依次有下面几个接口 创建学习计划 查询学习记录 提交学习记录 查询我的计划 1.2.1.创建学习计划 在个人中心的我的课表列表中没有学习计划的课程都会有一个创建学习计划的按钮在原型图就能看到 创建学习计划本质就是让用户设定自己每周的学习频率 而学习频率我们在设计learning_lesson表的时候已经有两个字段来表示了 CREATE TABLE learning_lesson (id bigint NOT NULL COMMENT 主键,user_id bigint NOT NULL COMMENT 学员id,course_id bigint NOT NULL COMMENT 课程id,status tinyint NULL DEFAULT 0 COMMENT 课程状态0-未学习1-学习中2-已学完3-已失效,week_freq tinyint NULL DEFAULT NULL COMMENT 每周学习频率每周3天每天2节则频率为6,plan_status tinyint NOT NULL DEFAULT 0 COMMENT 学习计划状态0-没有计划1-计划进行中,learned_sections int NOT NULL DEFAULT 0 COMMENT 已学习小节数量,latest_section_id bigint NULL DEFAULT NULL COMMENT 最近一次学习的小节id,latest_learn_time datetime NULL DEFAULT NULL COMMENT 最近一次学习的时间,create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,expire_time datetime NOT NULL COMMENT 过期时间,update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,PRIMARY KEY (id) USING BTREE,UNIQUE INDEX idx_user_id(user_id, course_id) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 学生课程表 ROW_FORMAT Dynamic;当我们创建学习计划时就是更新learning_lesson表写入week_freq并更新plan_status为计划进行中即可。因此请求参数就是课程的id、每周学习频率。 再按照Restful风格最终接口如下 参数 说明 请求方式 POST 请求路径 /lessons/plans 请求参数 参数名 类型 说明 courseId Long 课程id weekFreq Integer 计划每周学习频率 返回值 无 1.2.2.查询学习记录 用户创建完计划自然要开始学习课程在用户学习视频的页面首先要展示课程的一些基础信息。例如课程信息、章节目录以及每个小节的学习进度 其中课程、章节、目录信息等数据都在课程微服务而学习进度肯定是在学习微服务。课程信息是必备的而学习进度却不一定存在。 因此查询这个接口的请求肯定是请求到课程微服务查询课程、章节信息再由课程微服务向学习微服务查询学习进度合并后一起返回给前端即可。 所以学习中心要提供一个查询章节学习进度的Feign接口事实上这个接口已经在tj-api模块的LearningClient中定义好了 /*** 查询当前用户指定课程的学习进度* param courseId 课程id* return 课表信息、学习记录及进度信息*/ GetMapping(/learning-records/course/{courseId}) LearningLessonDTO queryLearningRecordByCourse(PathVariable(courseId) Long courseId); 对应的DTO也都在tj-api模块定义好了因此整个接口规范如下 参数 说明 请求方式 GET 请求路径 /learning-records/course/{courseId} 请求参数 路径占位符参数courseId课表关联的课程id 返回值 参数名 类型 说明 id Long 课表id latestSectionid Long 最近学习的小节id records array 参数名 类型 说明 sectionId Long 小节id moment int 视频播放进度第几秒 finished boolean 是否学完 1.2.3.提交学习记录 之前分析业务流程的时候已经聊过学习记录就是用户当前学了哪些小节以及学习到该小节的进度如何。而小节类型分为考试、视频两种。 考试比较简单只要提交了就说明这一节学完了。 视频比较麻烦需要记录用户的播放进度进度超过50%才算学完。因此视频播放的过程中需要不断提交播放进度到服务端而服务端则需要保存学习记录到数据库。 只要记录了用户学过的每一个小节以及小节对应的学习进度、是否学完。无论是视频续播、还是统计学习计划进度都可以轻松实现了。 因此提交学习记录就是提交小节的信息和小节的学习进度信息。考试提交一次即可视频则是播放中频繁提交。提交的信息包括两大部分 小节的基本信息 小节id lessonId 小节类型可能是视频也可能是考试。考试无需提供播放进度信息 提交时间 播放进度信息 视频时长时长结合播放进度可以判断有没有超过50% 视频播放进度也就是第几秒 综上提交学习记录的接口信息如下 参数 说明 请求方式 POST 请求路径 /learning-records 请求参数 参数名 类型 说明 lessonId long 课表id sectionId long 小节id sectionType int 小节类型1-视频2-考试 commitTime LocalDateTime 提交时间 duration int 视频总时长单位秒 moment int 视频播放进度单位秒 返回值 无 接口描述 视频播放当播放进度超过50%则判定为本节学完 考试考试结束时提交记录直接判定为本节学完 1.2.4.查询我的学习计划 在个人中心的我的课程页面会展示用户的学习计划及本周的学习进度原型如图 需要注意的是这个查询其实是一个分页查询因为页面最多展示10行而学员同时在学的课程可能会超过10个这个时候就会分页展示当然这个分页可能是滚动分页所以没有进度条。另外查询的是我的学习计划隐含的查询条件就是当前登录用户这个无需传递通过请求头即可获得。 因此查询参数只需要分页参数即可。 查询结果中有很多对于已经学习的小节数量的统计因此将来我们一定要保存用户对于每一个课程的学习记录哪些小节已经学习了哪些已经学完了。只有这样才能统计出学习进度。 查询的结果如页面所示分上下两部分。 总的统计信息 本周已完成总章节数需要对学习记录做统计 课程总计划学习数量累加课程的总计划学习频率即可 本周学习积分积分暂不实现 正在学习的N个课程信息的集合其中每个课程包含下列字段 该课程本周学了几节统计学习记录 计划学习频率在learning_lesson表中有对应字段 该课程总共学了几节在learning_lesson表中有对应字段 课程总章节数查询课程微服务 该课程最近一次学习时间在learning_lesson表中有对应字段 综上查询学习计划进度的接口信息如下 参数 说明 请求方式 GET 请求路径 /lessons/plans 请求参数 分页参数PageQuery 返回值 参数名 类型 说明 weekPoints int 本周学习积分 weekFinished int 本周已学完小节数量 weekTotalPlan int 本周计划学习小节数量 list Array 参数 类型 说明 courseId Long 课程id courseName String 课程名称 weekLearnedSections int 本周学习的小节数量 weekFreq int 本周计划学习数量 learnedSections int 总已学习小节数量 sections int 总小节数量 latestLearnTime LocalDateTime 最近一次学习时间 1.3.设计数据库 数据表的设计要满足学习计划、学习进度的功能需求。学习计划信息在learning_lesson表中已经设计因此我们关键是设计学习进度记录表即可。 按照之前的分析用户学习的课程包含多个小节小节的类型包含两种 视频视频播放进度超过50%就算当节学完 考试考完就算一节学完 学习进度除了要记录哪些小节学完还要记录学过的小节、每小节的播放的进度方便续播。因此需要记录的数据就包含以下部分 学过的小节的基础信息 小节id 小节对应的lessonId 用户id学习课程的人 小节的播放进度信息 视频播放进度也就是播放到了第几秒 是否已经学完播放进度有没有超过50% 第一次学完的时间用户可能重复学习第一次从未学完到学完的时间要记录下来 再加上一些表基础字段整张表结构就出来了 CREATE TABLE IF NOT EXISTS learning_record (id bigint NOT NULL COMMENT 学习记录的id,lesson_id bigint NOT NULL COMMENT 对应课表的id,section_id bigint NOT NULL COMMENT 对应小节的id,user_id bigint NOT NULL COMMENT 用户id,moment int DEFAULT 0 COMMENT 视频的当前观看时间点单位秒,finished bit(1) NOT NULL DEFAULT b0 COMMENT 是否完成学习默认false,create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 第一次观看时间,update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间最近一次观看时间,PRIMARY KEY (id) USING BTREE,KEY idx_update_time (update_time) USING BTREE,KEY idx_user_id (user_id) USING BTREE,KEY idx_lesson_id (lesson_id,section_id) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci ROW_FORMATDYNAMIC COMMENT学习记录表;课前资料也提供了对应的SQL语句 1.4.生成基础代码 接下来我们就可以生成数据库实体对应的基础代码了。 1.4.1.创建新分支 动手之前不要忘了开发新功能需要创建新的分支。这里我们依然在DEV分支基础上创建一个新的feature类型分支feature-learning-records 我们可以选择用命令: git checkout -b feature-learning-records 也可以选择图形界面方式 1.4.2.代码生成 同样是使用MybatisPlus插件这里不再赘述。效果如下 需要注意的是我们同样需要把生成的实体类的ID策略改成雪花算法 另外按照Restful风格 把controller的路径做修改 1.4.3.类型枚举 在昨天学习的课表中有一种状态枚举就是把课程的状态通过枚举定义出来避免出现错误。而在学习记录中有一个section_type字段代表记录的小节有两种类型 1视频类型 2考试类型 为了方便我们也定义为枚举称为类型枚举 具体代码 package com.tianji.learning.enums;import com.baomidou.mybatisplus.annotation.EnumValue; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.tianji.common.enums.BaseEnum; import lombok.Getter;Getter public enum SectionType implements BaseEnum {VIDEO(1, 视频),EXAM(2, 考试),;JsonValueEnumValueint value;String desc;SectionType(int value, String desc) {this.value value;this.desc desc;}JsonCreator(mode JsonCreator.Mode.DELEGATING)public static SectionType of(Integer value){if (value null) {return null;}for (SectionType status : values()) {if (status.equalsValue(value)) {return status;}}return null;} } 2.实现接口 2.1.查询学习记录 首先回顾一下接口基本信息 参数 说明 请求方式 GET 请求路径 /learning-records/course/{courseId} 请求参数 路径占位符参数courseId课表关联的课程id 返回值 参数名 类型 说明 id Long 课表id latestSectionid Long 最近学习的小节id records array 参数名 类型 说明 sectionId Long 小节id moment int 视频播放进度第几秒 finished boolean 是否学完 2.1.1.思路分析 做个接口是给课程微服务调用的因此在tj-api模块的LearningClient中定义好了 /*** 查询当前用户指定课程的学习进度* param courseId 课程id* return 课表信息、学习记录及进度信息*/ GetMapping(/learning-records/course/{courseId}) LearningLessonDTO queryLearningRecordByCourse(PathVariable(courseId) Long courseId); 对应的DTO也都在tj-api模块定义好了。我们直接实现接口即可。 由于请求参数是courseId而返回值中包含lessonId和latestSectionid都在learning_lesson表中因此我们需要根据courseId和userId查询出lesson信息。然后再根据lessonId查询学习记录。整体流程如下 获取当前登录用户id 根据courseId和userId查询LearningLesson 判断是否存在或者是否过期 如果不存在或过期直接返回空 如果存在并且未过期则继续 查询lesson对应的所有学习记录 2.1.2.代码实现 首先在tj-learning模块下的com.tianji.learning.controller.LearningRecordController下定义接口 package com.tianji.learning.controller;import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.learning.service.ILearningRecordService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;/*** p* 学习记录表 前端控制器* /p*/ RestController RequestMapping(/learning-records) Api(tags 学习记录的相关接口) RequiredArgsConstructor public class LearningRecordController {private final ILearningRecordService recordService;ApiOperation(查询指定课程的学习记录)GetMapping(/course/{courseId})public LearningLessonDTO queryLearningRecordByCourse(ApiParam(value 课程id, example 2) PathVariable(courseId) Long courseId){return recordService.queryLearningRecordByCourse(courseId);} } 然后在com.tianji.learning.service.ILearningRecordService中定义方法 package com.tianji.learning.service;import com.baomidou.mybatisplus.extension.service.IService; import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.learning.domain.po.LearningRecord;/*** p* 学习记录表 服务类* /p*/ public interface ILearningRecordService extends IServiceLearningRecord {LearningLessonDTO queryLearningRecordByCourse(Long courseId); } 最后在com.tianji.learning.service.impl.LearningRecordServiceImpl中定义实现类 package com.tianji.learning.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.api.dto.leanring.LearningRecordDTO; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.UserContext; import com.tianji.learning.domain.po.LearningLesson; import com.tianji.learning.domain.po.LearningRecord; import com.tianji.learning.mapper.LearningRecordMapper; import com.tianji.learning.service.ILearningLessonService; import com.tianji.learning.service.ILearningRecordService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service;import java.util.List;/*** p* 学习记录表 服务实现类* /p** author 虎哥* since 2022-12-10*/ Service RequiredArgsConstructor public class LearningRecordServiceImpl extends ServiceImplLearningRecordMapper, LearningRecord implements ILearningRecordService {private final ILearningLessonService lessonService;Overridepublic LearningLessonDTO queryLearningRecordByCourse(Long courseId) {// 1.获取登录用户Long userId UserContext.getUser();// 2.查询课表LearningLesson lesson lessonService.queryByUserAndCourseId(userId, courseId);// 3.查询学习记录// select * from xx where lesson_id #{lessonId}ListLearningRecord records lambdaQuery().eq(LearningRecord::getLessonId, lesson.getId()).list();// 4.封装结果LearningLessonDTO dto new LearningLessonDTO();dto.setId(lesson.getId());dto.setLatestSectionId(lesson.getLatestSectionId());dto.setRecords(BeanUtils.copyList(records, LearningRecordDTO.class));return dto;} } 其中查询课表的时候需要调用ILessonService中的queryByUserAndCourseId()方法该方法代码如下 Override public LearningLesson queryByUserAndCourseId(Long userId, Long courseId) {return getOne(buildUserIdAndCourseIdWrapper(userId, courseId)); }private LambdaQueryWrapperLearningLesson buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {LambdaQueryWrapperLearningLesson queryWrapper new QueryWrapperLearningLesson().lambda().eq(LearningLesson::getUserId, userId).eq(LearningLesson::getCourseId, courseId);return queryWrapper; } 2.2.提交学习记录 回顾一下接口信息 参数 说明 请求方式 POST 请求路径 /learning-records 请求参数 参数名 类型 说明 lessonId long 课表id sectionId long 小节id sectionType int 小节类型1-视频2-考试 commitTime LocalDateTime 提交时间 duration int 视频总时长单位秒 moment int 视频播放进度单位秒 返回值 无 接口描述 视频播放当播放进度超过50%则判定为本节学完 考试考试结束时提交记录直接判定为本节学完 2.2.1.思路分析 学习记录就是用户当前学了哪些小节以及学习到该小节的进度如何。而小节类型分为考试、视频两种。 考试比较简单只要提交了就说明这一节学完了。 视频比较麻烦需要记录用户的播放进度进度超过50%才算学完。因此视频播放的过程中需要不断提交播放进度到服务端而服务端则需要保存学习记录到数据库。 以上信息都需要保存到learning_record表中。 特别需要注意的是学习记录learning_record表记录的是每一个小节的学习进度。而在learning_lesson表也需要记录一些学习进度相关字段 这些字段是整个课程的进度统计 learned_sections已学习小节数量 latest_section_id最近一次学习的小节id latest_learn_time最近一次学习时间 每当有一个小节被学习都应该更新latest_section_id和latest_learn_time每当有一个小节学习完后learned_sections都应该累加1。不过这里有一点容易出错的地方 考试只会被参加一次考试提交则小节学完learned_sections累加1 视频可以被重复播放只有在第一次学完一个视频时learned_sections才需要累加1 那么问题来了如何判断视频是否是第一次学完我认为应该同时满足两个条件 视频播放进度超过50% 之前学习记录的状态为未学完 另外随着learned_sections字段不断累加最终会到达课程的最大小节数这就意味着当前课程被全部学完了。那么课程状态需要从“学习中”变更为“已学完”。 综上最终的提交学习记录处理流程如图 2.2.2.表单实体 请求参数比较多所以需要定义一个表单DTO实体这个在课前资料已经提供好了 具体代码如下 package com.tianji.learning.domain.dto;import com.tianji.common.validate.annotations.EnumValid; import com.tianji.learning.enums.SectionType; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;import javax.validation.constraints.NotNull; import java.time.LocalDateTime;Data ApiModel(description 学习记录) public class LearningRecordFormDTO {ApiModelProperty(小节类型1-视频2-考试)NotNull(message 小节类型不能为空)EnumValid(enumeration {1, 2}, message 小节类型错误只能是1-视频2-考试)private SectionType sectionType;ApiModelProperty(课表id)NotNull(message 课表id不能为空)private Long lessonId;ApiModelProperty(对应节的id)NotNull(message 节的id不能为空)private Long sectionId;ApiModelProperty(视频总时长单位秒)private Integer duration;ApiModelProperty(视频的当前观看时长单位秒第一次提交填0)private Integer moment;ApiModelProperty(提交时间)private LocalDateTime commitTime; } 2.2.3.代码实现 首先在tj-learning模块下的com.tianji.learning.controller.LearningRecordController下定义接口 package com.tianji.learning.controller;import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.learning.domain.dto.LearningRecordFormDTO; import com.tianji.learning.service.ILearningRecordService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;/*** p* 学习记录表 前端控制器* /p** author 虎哥* since 2022-12-10*/ RestController RequestMapping(/learning-records) Api(tags 学习记录的相关接口) RequiredArgsConstructor public class LearningRecordController {private final ILearningRecordService recordService;ApiOperation(查询指定课程的学习记录)GetMapping(/course/{courseId})public LearningLessonDTO queryLearningRecordByCourse(ApiParam(value 课程id, example 2) PathVariable(courseId) Long courseId){return recordService.queryLearningRecordByCourse(courseId);}ApiOperation(提交学习记录)PostMappingpublic void addLearningRecord(RequestBody LearningRecordFormDTO formDTO){recordService.addLearningRecord(formDTO);} } 然后在com.tianji.learning.service.ILearningRecordService中定义方法 package com.tianji.learning.service;import com.baomidou.mybatisplus.extension.service.IService; import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.learning.domain.dto.LearningRecordFormDTO; import com.tianji.learning.domain.po.LearningRecord;/*** p* 学习记录表 服务类* /p** author 虎哥* since 2022-12-10*/ public interface ILearningRecordService extends IServiceLearningRecord {LearningLessonDTO queryLearningRecordByCourse(Long courseId);void addLearningRecord(LearningRecordFormDTO formDTO); } 最后在com.tianji.learning.service.impl.LearningRecordServiceImpl中定义实现类 package com.tianji.learning.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.tianji.api.client.course.CourseClient; import com.tianji.api.dto.course.CourseFullInfoDTO; import com.tianji.api.dto.leanring.LearningLessonDTO; import com.tianji.api.dto.leanring.LearningRecordDTO; import com.tianji.common.exceptions.BizIllegalException; import com.tianji.common.exceptions.DbException; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.UserContext; import com.tianji.learning.domain.dto.LearningRecordFormDTO; import com.tianji.learning.domain.po.LearningLesson; import com.tianji.learning.domain.po.LearningRecord; import com.tianji.learning.enums.LessonStatus; import com.tianji.learning.enums.SectionType; import com.tianji.learning.mapper.LearningRecordMapper; import com.tianji.learning.service.ILearningLessonService; import com.tianji.learning.service.ILearningRecordService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;import java.util.List;/*** p* 学习记录表 服务实现类* /p*/ Service RequiredArgsConstructor public class LearningRecordServiceImpl extends ServiceImplLearningRecordMapper, LearningRecord implements ILearningRecordService {private final ILearningLessonService lessonService;private final CourseClient courseClient;// 。。。略OverrideTransactionalpublic void addLearningRecord(LearningRecordFormDTO recordDTO) {// 1.获取登录用户Long userId UserContext.getUser();// 2.处理学习记录boolean finished false;if (recordDTO.getSectionType() SectionType.VIDEO) {// 2.1.处理视频finished handleVideoRecord(userId, recordDTO);}else{// 2.2.处理考试finished handleExamRecord(userId, recordDTO);}// 3.处理课表数据handleLearningLessonsChanges(recordDTO, finished);}private void handleLearningLessonsChanges(LearningRecordFormDTO recordDTO, boolean finished) {// 1.查询课表LearningLesson lesson lessonService.getById(recordDTO.getLessonId());if (lesson null) {throw new BizIllegalException(课程不存在无法更新数据);}// 2.判断是否有新的完成小节boolean allLearned false;if(finished){// 3.如果有新完成的小节则需要查询课程数据CourseFullInfoDTO cInfo courseClient.getCourseInfoById(lesson.getCourseId(), false, false);if (cInfo null) {throw new BizIllegalException(课程不存在无法更新数据);}// 4.比较课程是否全部学完已学习小节 课程总小节allLearned lesson.getLearnedSections() 1 cInfo.getSectionNum(); }// 5.更新课表lessonService.lambdaUpdate().set(lesson.getLearnedSections() 0, LearningLesson::getStatus, LessonStatus.LEARNING.getValue()).set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED.getValue()).set(!finished, LearningLesson::getLatestSectionId, recordDTO.getSectionId()).set(!finished, LearningLesson::getLatestLearnTime, recordDTO.getCommitTime()).setSql(finished, learned_sections learned_sections 1).eq(LearningLesson::getId, lesson.getId()).update();}private boolean handleVideoRecord(Long userId, LearningRecordFormDTO recordDTO) {// 1.查询旧的学习记录LearningRecord old queryOldRecord(recordDTO.getLessonId(), recordDTO.getSectionId());// 2.判断是否存在if (old null) {// 3.不存在则新增// 3.1.转换POLearningRecord record BeanUtils.copyBean(recordDTO, LearningRecord.class);// 3.2.填充数据record.setUserId(userId);// 3.3.写入数据库boolean success save(record);if (!success) {throw new DbException(新增学习记录失败);}return false;}// 4.存在则更新// 4.1.判断是否是第一次完成boolean finished !old.getFinished() recordDTO.getMoment() * 2 recordDTO.getDuration();// 4.2.更新数据boolean success lambdaUpdate().set(LearningRecord::getMoment, recordDTO.getMoment()).set(finished, LearningRecord::getFinished, true).set(finished, LearningRecord::getFinishTime, recordDTO.getCommitTime()).eq(LearningRecord::getId, old.getId()).update();if(!success){throw new DbException(更新学习记录失败);}return finished ;}private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {return lambdaQuery().eq(LearningRecord::getLessonId, lessonId).eq(LearningRecord::getSectionId, sectionId).one();}private boolean handleExamRecord(Long userId, LearningRecordFormDTO recordDTO) {// 1.转换DTO为POLearningRecord record BeanUtils.copyBean(recordDTO, LearningRecord.class);// 2.填充数据record.setUserId(userId);record.setFinished(true);record.setFinishTime(recordDTO.getCommitTime());// 3.写入数据库boolean success save(record);if (!success) {throw new DbException(新增考试记录失败);}return true;} } 2.3.创建学习计划 回顾下接口信息 参数 说明 请求方式 POST 请求路径 /lessons/plans 请求参数 参数名 类型 说明 courseId Long 课程id weekFreq Integer 计划每周学习频率 返回值 无 2.3.1.思路分析 创建学习计划本质就是让用户设定自己每周的学习频率 虽说接口是创建学习计划但本质这是一个更新的接口。因为学习计划字段都保存在learning_lesson表中。 CREATE TABLE learning_lesson (id bigint NOT NULL COMMENT 主键,user_id bigint NOT NULL COMMENT 学员id,course_id bigint NOT NULL COMMENT 课程id,status tinyint NULL DEFAULT 0 COMMENT 课程状态0-未学习1-学习中2-已学完3-已失效,week_freq tinyint NULL DEFAULT NULL COMMENT 每周学习频率每周3天每天2节则频率为6,plan_status tinyint NOT NULL DEFAULT 0 COMMENT 学习计划状态0-没有计划1-计划进行中,learned_sections int NOT NULL DEFAULT 0 COMMENT 已学习小节数量,latest_section_id bigint NULL DEFAULT NULL COMMENT 最近一次学习的小节id,latest_learn_time datetime NULL DEFAULT NULL COMMENT 最近一次学习的时间,create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,expire_time datetime NOT NULL COMMENT 过期时间,update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,PRIMARY KEY (id) USING BTREE,UNIQUE INDEX idx_user_id(user_id, course_id) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 学生课程表 ROW_FORMAT Dynamic;当我们创建学习计划时就是更新learning_lesson表写入week_freq并更新plan_status为计划进行中即可。 2.3.2.表单实体 表单包含两个字段 courseId weekFreq 前端是以JSON方式提交我们需要定义一个表单DTO实体。在课前资料中已经提供给大家了 具体代码 package com.tianji.learning.domain.dto;import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.hibernate.validator.constraints.Range;import javax.validation.constraints.Min; import javax.validation.constraints.NotNull;Data ApiModel(description 学习计划表单实体) public class LearningPlanDTO {NotNullApiModelProperty(课程表id)Min(1)private Long courseId;NotNullRange(min 1, max 50)ApiModelProperty(每周学习频率)private Integer freq; } 2.3.3.代码实现 首先在com.tianji.learning.controller.LearningLessonController中添加一个接口 package com.tianji.learning.controller;import com.tianji.learning.domain.dto.LearningPlanDTO; import com.tianji.learning.service.ILearningLessonService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;import javax.validation.Valid;/*** p* 学生课程表 前端控制器* /p** author 虎哥* since 2022-12-02*/ RestController RequestMapping(/lessons) Api(tags 我的课表相关接口) RequiredArgsConstructor public class LearningLessonController {private final ILearningLessonService lessonService;// 略。。。ApiOperation(创建学习计划)PostMapping(/plans)public void createLearningPlans(Valid RequestBody LearningPlanDTO planDTO){lessonService.createLearningPlan(planDTO.getCourseId(), planDTO.getFreq());} } 然后在com.tianji.learning.service.ILearningLessonService中定义service方法 package com.tianji.learning.service;import com.baomidou.mybatisplus.extension.service.IService; import com.tianji.learning.domain.po.LearningLesson;import java.util.List;/*** p* 学生课程表 服务类* /p*/ public interface ILearningLessonService extends IServiceLearningLesson {// ... 略void createLearningPlan(Long courseId, Integer freq); } 最后在com.tianji.learning.service.impl.LearningLessonServiceImpl中实现方法 // ... 略Override public void createLearningPlan(Long courseId, Integer freq) {// 1.获取当前登录的用户Long userId UserContext.getUser();// 2.查询课表中的指定课程有关的数据LearningLesson lesson queryByUserAndCourseId(userId, courseId);AssertUtils.isNotNull(lesson, 课程信息不存在);// 3.修改数据LearningLesson l new LearningLesson();l.setId(lesson.getId());l.setWeekFreq(freq);if(lesson.getPlanStatus() PlanStatus.NO_PLAN) {l.setPlanStatus(PlanStatus.PLAN_RUNNING);}updateById(l); }// ... 略 2.4.查询学习计划进度 页面原型如图 接口回顾 参数 说明 请求方式 GET 请求路径 /lessons/plans 请求参数 分页参数PageQuery 返回值 参数名 类型 说明 weekPoints int 本周学习积分 weekFinished int 本周已学完小节数量 weekTotalPlan int 本周计划学习小节数量 list Array 参数 类型 说明 courseId Long 课程id courseName String 课程名称 weekLearnedSections int 本周学习的小节数量 weekFreq int 本周计划学习数量 learnedSections int 总已学习小节数量 sections int 总小节数量 latestLearnTime LocalDateTime 最近一次学习时间 2.4.1.思路分析 要查询的数据分为两部分 本周计划学习的每个课程的学习进度 本周计划学习的课程总的学习进度 对于本周计划学习的每个课程的学习进度首先需要查询出学习中的LearningLesson的信息查询条件包括 属于当前登录用户 学习计划进行中 查询到的LearningLesson可能有多个而且查询到的PO数据跟最终的VO相比还有差距: PO VO 具体来说PO中缺少了courseName和weekSections两个字段。其中courseName可以通过courseId去课程微服务查询。weekSections只能对学习记录做统计得到。 因此我们需要搜集查询到的课表中的courseId查询出对应的课程信息还需要搜集查询到的课表的id去learning_record中统计每个课表本周已学习的小节数量。 最终遍历处理每个PO转换为VO格式。 除了本周每个课程的学习进度以外我们还要统计本周计划学习的课程总的学习进度。其中的积分数据暂时不管剩下的两个需要分别对两张表统计 weekTotalPlan对learning_lesson表统计查询计划学习的课程的weekFreq字段做累加即可 weekFinished对learning_record表对已学完的小节记录做count即可 注意 虽然这里是分页查询但是每个用户购买的课程其实是有限的为了便于数据统计建议采用查询全部数据然后手动逻辑分页的方式。这样在统计全部课程学习进度的时候会方便很多。 2.4.2.实体 VO实体已经在课前资料中给出 2.4.3.代码实现 首先在tj-learning模块的com.tianji.learning.controller.LearningLessonController中定义controller接口 ApiOperation(查询我的学习计划) GetMapping(/plans) public LearningPlanPageVO queryMyPlans(PageQuery query){return lessonService.queryMyPlans(query); } 然后在com.tianji.learning.service.ILearningLessonService中定义service方法 LearningPlanPageVO queryMyPlans(PageQuery query); 最后在com.tianji.learning.service.impl.LearningLessonServiceImpl中实现该方法 版本1物理分页分别统计 Override public LearningPlanPageVO queryMyPlans(PageQuery query) {LearningPlanPageVO result new LearningPlanPageVO();// 1.获取当前登录用户Long userId UserContext.getUser();// 2.获取本周起始时间LocalDate now LocalDate.now();LocalDateTime begin DateUtils.getWeekBeginTime(now);LocalDateTime end DateUtils.getWeekEndTime(now);// 3.查询总的统计数据// 3.1.本周总的已学习小节数量Integer weekFinished recordMapper.selectCount(new LambdaQueryWrapperLearningRecord().eq(LearningRecord::getUserId, userId).eq(LearningRecord::getFinished, true).gt(LearningRecord::getFinishTime, begin).lt(LearningRecord::getFinishTime, end));result.setWeekFinished(weekFinished);// 3.2.本周总的计划学习小节数量Integer weekTotalPlan getBaseMapper().queryTotalPlan(userId);result.setWeekTotalPlan(weekTotalPlan);// TODO 3.3.本周学习积分// 4.查询分页数据// 4.1.分页查询课表信息以及学习计划信息PageLearningLesson p lambdaQuery().eq(LearningLesson::getUserId, userId).eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING).in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING).page(query.toMpPage(latest_learn_time, false));ListLearningLesson records p.getRecords();if (CollUtils.isEmpty(records)) {return result.emptyPage(p);}// 4.2.查询课表对应的课程信息MapLong, CourseSimpleInfoDTO cMap queryCourseSimpleInfoList(records);// 4.3.统计每一个课程本周已学习小节数量ListIdAndNumDTO list recordMapper.countLearnedSections(userId, begin, end);MapLong, Integer countMap IdAndNumDTO.toMap(list);// 4.4.组装数据VOListLearningPlanVO voList new ArrayList(records.size());for (LearningLesson r : records) {// 4.4.1.拷贝基础属性到voLearningPlanVO vo BeanUtils.copyBean(r, LearningPlanVO.class);// 4.4.2.填充课程详细信息CourseSimpleInfoDTO cInfo cMap.get(r.getCourseId());if (cInfo ! null) {vo.setCourseName(cInfo.getName());vo.setSections(cInfo.getSectionNum());}// 4.4.3.每个课程的本周已学习小节数量vo.setWeekLearnedSections(countMap.getOrDefault(r.getId(), 0));voList.add(vo);}return result.pageInfo(p.getTotal(), p.getPages(), voList); }private MapLong, CourseSimpleInfoDTO queryCourseSimpleInfoList(ListLearningLesson records) {// 3.1.获取课程idSetLong cIds records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());// 3.2.查询课程信息ListCourseSimpleInfoDTO cInfoList courseClient.getSimpleInfoList(cIds);if (CollUtils.isEmpty(cInfoList)) {// 课程不存在无法添加throw new BadRequestException(课程信息不存在);}// 3.3.把课程集合处理成Mapkey是courseId值是course本身MapLong, CourseSimpleInfoDTO cMap cInfoList.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c - c));return cMap; } 其中需要调用LearningRecordMapper实现对本周每个课程的已学习小节的统计对应实现如下 public interface LearningRecordMapper extends BaseMapperLearningRecord {ListIdAndNumDTO countLearnedSections(Param(userId) Long userId,Param(begin) LocalDateTime begin,Param(end) LocalDateTime end); } 对应的SQL如下 select idcountLearnedSections resultTypecom.tianji.api.dto.IdAndNumDTOSELECT lesson_id AS id, COUNT(1) AS numFROM learning_recordWHERE user_id #{userId}AND finished 1AND finish_time gt; #{begin} AND finish_time lt; #{end}GROUP BY lesson_id; /select 版本2不分页stream流统计 Override public LearningPlanPageVO queryMyPlans(PageQuery query) {LearningPlanPageVO result new LearningPlanPageVO();// 1.获取当前登录用户Long userId UserContext.getUser();// 2.获取本周起始时间LocalDate now LocalDate.now();LocalDateTime begin DateUtils.getWeekBeginTime(now);LocalDateTime end DateUtils.getWeekEndTime(now);// 3.查询本周计划学习的所有课程满足三个条件属于当前用户、有学习计划、学习中ListLearningLesson lessons lambdaQuery().eq(LearningLesson::getUserId, userId).eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING).in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING).list();if (CollUtils.isEmpty(lessons)) {return null;}// 4.统计当前用户每个课程的已学习小节数量ListLearningRecord learnedRecords recordMapper.selectList(new QueryWrapperLearningRecord().lambda().eq(LearningRecord::getUserId, userId).eq(LearningRecord::getFinished, true).gt(LearningRecord::getFinishTime, begin).lt(LearningRecord::getFinishTime, end));MapLong, Long countMap learnedRecords.stream().collect(Collectors.groupingBy(LearningRecord::getLessonId, Collectors.counting()));// 5.查询总的统计数据// 5.1.本周总的已学习小节数量int weekFinished learnedRecords.size();result.setWeekFinished(weekFinished);// 5.2.本周总的计划学习小节数量int weekTotalPlan lessons.stream().mapToInt(LearningLesson::getWeekFreq).sum();result.setWeekTotalPlan(weekTotalPlan);// TODO 5.3.本周学习积分// 6.处理分页数据// 6.1.分页查询课表信息以及学习计划信息PageLearningLesson p new Page(query.getPageNo(), query.getPageSize(), lessons.size());ListLearningLesson records CollUtils.sub(lessons, query.from(), query.from() query.getPageSize());if (CollUtils.isEmpty(records)) {return result;}// 6.2.查询课表对应的课程信息MapLong, CourseSimpleInfoDTO cMap queryCourseInfo(records);// 6.3.组装数据VOListLearningPlanVO voList new ArrayList(records.size());for (LearningLesson r : records) {// 6.4.1.拷贝基础属性到voLearningPlanVO vo BeanUtils.copyBean(r, LearningPlanVO.class);// 6.4.2.填充课程详细信息CourseSimpleInfoDTO cInfo cMap.get(r.getCourseId());if (cInfo ! null) {vo.setCourseName(cInfo.getName());vo.setSections(cInfo.getSectionNum());}// 6.4.3.每个课程的本周已学习小节数量vo.setWeekLearnedSections(countMap.getOrDefault(r.getId(), 0L).intValue());voList.add(vo);}return result.pageInfo(p.getTotal(), p.getPages(), voList); } 3.练习 3.1.课程过期 编写一个SpringTask定时任务定期检查learning_lesson表中的课程是否过期如果过期则将课程状态修改为已过期。 3.2.方案思考 思考题思考一下目前提交学习记录功能可能存在哪些问题有哪些可以改进的方向
http://www.pierceye.com/news/486701/

相关文章:

  • 如何做天猫网站医学ppt模板免费下载网站
  • 网站上的通话功能怎么做网站用不用备案
  • 信誉好的模板网站建设wordpress 伪静态设置
  • wordpress主题外贸网站wordpress检查php版本号
  • 便宜电商网站建设找平面图的网站
  • 大型网站建设制作平台东莞南城房价
  • 360免费视频网站建设mvc网站开发之美
  • 武宁县建设工程招标公告门户网站设计一个网站先做哪些构造
  • 公司网站免费建设2023设计院裁员惨烈程度
  • 别人做的网站不能用设计网站教程
  • 设计师发布作品的网站wordpress仿
  • 品牌微信网站建设柳州做网站制作的公司有哪些
  • 买域名做网站推广都是些什么网站点击后的loading是怎么做的
  • 北京网站优化技术泰州自助建站软件
  • 公司企业网站建设目的站长统计官方网站
  • 集团公司网站模板wordpress更换主题方法
  • 临沂网站建设电话建设网站审批手续
  • 国外做健康的网站专门做鞋子的网站吗
  • 手机网站支持微信支付吗北京短视频拍摄
  • 做静态网站工资多少网站前期推广
  • 做预算查价格的网站是哪个好网站开发维护多少钱
  • 个人互动网站365建筑人才网
  • 天津外贸公司网站制作淘客cms网站建设
  • 怎样做微网站网站建设pc指什么软件
  • 四川 网站建设wordpress腾讯地图插件下载
  • 宁波网站公司哪家好百度关键词搜索量排名
  • 北京国税局网站做票种核定时眉山网站优化
  • 网站备案授权书成都网站建设十强企业
  • 网站流量图怎么做的wordpress单号管理系统
  • 生活服务网站建设方案天猫店铺装修做特效的网站