StrivingYu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

实现查询指定课程学习记录接口

        1.接口分析

                由于要实现查询指定课程的学习记录,需要知道当前用户,首先就需要先从线程中取出用户ID,在通过观察到课程学习记录中的需要的字段,依次去数据库中查询。课程id可通过查询学习课程表去找到。而学习记录的列表可以从学习记录表去查询。随后将查询到的数据封装到DTO中返回即可。

        2.接口实现

                学习记录service实现接口代码如下:

/**
代码逻辑:首先获取用户id,根据用户id去查询课程信息,为后续的填充课程id字段,然后根据用户id查询
学习记录表,得到学习记录的信息,然后将其转化为DTO类型,最后天趣缺失的字段即可
**/    

public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //1.获取用户
        Long user = UserContext.getUser();
        //2.查询课表
        LearningLesson lesson = lessonService.queryByUserIdAndCourseId(user,courseId);
        if (lesson == null) {
            return null;
        }
        //3.查询学习记录
        List<LearningRecord> records = lambdaQuery().eq(LearningRecord::getUserId, user)
                .list();
        List<LearningRecordDTO> list = BeanUtils.copyList(records, LearningRecordDTO.class);

        //4.组装数据
        LearningLessonDTO lessonDTO = new LearningLessonDTO();
        lessonDTO.setId(lesson.getId());
        lessonDTO.setLatestSectionId(lesson.getLatestSectionId());
        lessonDTO.setRecords(list);

        return lessonDTO;
    }

实现提交学习记录接口

        1.接口分析

                学习记录就是用户当前学了哪些小节,以及学习到该小节的进度如何。而小节类型分为考试、视频两种。对于考试,其用户完成了考试之后,直接提交就行了,而对于视频,则比较麻烦,需要记录用户当前的播放进度,进度超过50%才算学完。因此视频播放的过程中需要不断提交播放进度到服务端,而服务端则需要保存学习记录到数据库。而对于视频处理来说,还需要判断其是不是第一次提交,若是第一次提交,则需要将数据插入数据库中,若不是第一次提交,需要更新数据库。更新数据库还需要判断是不是第一次完成,若是第一次完成则需要将该小节设为已完成。

        2.接口实现

                service对应的接口实现代码分为四部分,一个是用来判断前端传入的提交类型是视频还是考试。另外两部分是对应的类型处理的方法,最后一部分是提交到数据库的方法,代码如下:

/**、
该部分是判断前端传入的是视频类型还是考试类型
然后选择对应的方法进行处理
**/

@Transactional
    public void addLearningRecord(LearningRecordFormDTO dto) {
        //1.获取用户
        Long userId = UserContext.getUser();
        //2.处理学习记录-一类是考试,一类是视频
        boolean finished = false;
        if (dto.getSectionType() == SectionType.VIDEO.getValue()) {
            //2.1处理视频
            finished = handleVideoRecord(userId,dto);
        }else {
            //2.2处理考试
            finished = handleExamRecord(userId, dto);

        }
        //3.处理课表数据
        handleLearningLessonsChanges(dto,finished);
    }
    /**
 * 处理考试记录的方法
 * @param userId 用户ID
 * @param dto 学习记录表单数据传输对象
 * @return 处理成功返回true
 * @throws DbException 当新增考试记录失败时抛出数据库异常
 */
    private boolean handleExamRecord(Long userId, LearningRecordFormDTO dto) {
        //转换DTO为PO
        LearningRecord record = BeanUtils.copyBean(dto, LearningRecord.class);
        //填充数据
        record.setUserId(userId);
        record.setFinished(true);
        record.setFinishTime(dto.getCommitTime());
        //写入数据库
        boolean success = save(record);
        if(!success){
            throw new DbException("新增考试记录失败");
        }
        return true;
    }
/**
处理视频的方法
代码逻辑:首先,需要从数据库中查询,是否有旧的数据,也就是判断是不是第一次播放该视频,对该记录
做一个插入,若是没有旧的数据,则需要对该次播放进行新增到数据库中。若有旧数据,则需要更新数据库
中的旧数据。而在更新数据库之前,还需要判断该视频是不是已经播放了一半,也就是是否已经完成该小结
的学习,若是完成了,则需要将其的状态更新为已完成,已经将完成时间设置为提交时间。同时,不管完成
与否,都需要提交当前视频的播放进度,最后将其更新到数据库当中
**/    


   private void handleLearningLessonsChanges(LearningRecordFormDTO dto, boolean finished) {
        //1.查询课表
        LearningLesson lesson = lessonService.getById(dto.getLessonId());
        if (lesson == null) {
            throw new BizIllegalException("课表不存在,无法更新数据");
        }
        boolean allLearned = false;
        //2.判断是否有新的完成小节
        if(finished){
            //有新的完成小节,则需要查询课程数据
            CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            if (cInfo == null) {
                throw new BizIllegalException("课程不存在,无法更新数据");
            }
            //比较课程是否全部学完,已学习小节>=课程总小节数
            allLearned = lesson.getLearnedSections() + 1 >= cInfo.getSectionNum();
            //更新课表
            LocalDateTime time = LocalDateTime.now();
        lessonService.lambdaUpdate().set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED)
                .setSql(finished, "learned_sections = learned_sections + 1")
                .set(!finished, LearningLesson::getLatestSectionId, dto.getSectionId())
                .set(!finished, LearningLesson::getLatestLearnTime, dto.getCommitTime())
                .set(lesson.getLearnedSections() == 0 ,LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .eq(LearningLesson::getId, lesson.getId())
                .set(LearningLesson::getLatestLearnTime, time)
                .update();

    }
        }
/**
代码逻辑:该代码是处理课程是否已完成,根据前面的处理视频和考试的方法传回的返回值,可以判断
该小结是否已经完成。然后在再这个代码块中,传入该参数,即可判断,是不是有新的小节已
完成,若是有,则需要将课程的已经学习的小节数量加一,同时还需要判断当前课程是否已经学完,将
该参数作为条件去更新数据库相关数据。
**/   

private void handleLearningLessonsChanges(LearningRecordFormDTO dto, boolean finished) {
        //1.查询课表
        LearningLesson lesson = lessonService.getById(dto.getLessonId());
        if (lesson == null) {
            throw new BizIllegalException("课表不存在,无法更新数据");
        }
        boolean allLearned = false;
        //2.判断是否有新的完成小节
        if(finished){
            //有新的完成小节,则需要查询课程数据
            CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            if (cInfo == null) {
                throw new BizIllegalException("课程不存在,无法更新数据");
            }
            //比较课程是否全部学完,已学习小节>=课程总小节数
            allLearned = lesson.getLearnedSections() + 1 >= cInfo.getSectionNum();
            //更新课表
        lessonService.lambdaUpdate().set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED)
                .setSql(finished, "learned_sections = learned_sections + 1")
                .set(!finished, LearningLesson::getLatestSectionId, dto.getSectionId())
                .set(!finished, LearningLesson::getLatestLearnTime, dto.getCommitTime())
                .set(lesson.getLearnedSections() == 0 ,LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .eq(LearningLesson::getId, lesson.getId())
                .update();

    }
        }

补充意外bug:

       加载 jwt秘钥失败:

当遇到下述错误的时候,看看ip地址是不是发生了改变,要去网关的配置文件中改当前运行环境的ip。

INFO 8248 --- [hFetchJwkThread] c.t.a.gateway.util.JwtSignerHolder       : 尝试加载auth服务地址
20:26:05.733-[LAPTOP-3QDJP4FH][sys] - INFO 8248 --- [hFetchJwkThread] c.t.a.gateway.util.JwtSignerHolder       : 加载auth服务地址成功,http://192.168.33.205:8081/jwks
20:26:05.733-[LAPTOP-3QDJP4FH][sys] - INFO 8248 --- [hFetchJwkThread] c.t.a.gateway.util.JwtSignerHolder       : 尝试加载jwk秘钥
20:26:26.751-[LAPTOP-3QDJP4FH][sys] -ERROR 8248 --- [hFetchJwkThread] c.t.a.gateway.util.JwtSignerHolder       : 加载jwk秘钥失败,原因:ConnectException: Connection timed out: connect这个怎么解决

实现创建学习计划的接口

        1.接口分析

                由于创建学习计划和修改学习计划都可以算是更新学习计划,故可以将这两个接口视为一种接口,只需要对当前课程的学习计划进行更新即可。

        2.接口实现

                service实现的代码如下:

/**
代码逻辑:首先根据用户id查询当前课程是否存在,若是存在,则往当前课程去更新学习计划。注意
在更新学习计划之前还需要判断当前有没有学习计划,若没有学习计划还需修改学习计划的状态。

**/    
    @Transactional
    public void createLearningPlans(Long courseId, Integer freq) {
        //获取当前用户id
        Long userId = UserContext.getUser();
        //查询课表中的指定课程有关的数据
        LearningLesson lesson = queryByUserIdAndCourseId(userId, courseId);
        AssertUtils.isNotNull(lesson, "课程不存在");
        //如果课程存在,则修改学习计划
        LearningLesson l = new LearningLesson();
        l.setId(lesson.getId());
        l.setWeekFreq(freq);
        if (lesson.getPlanStatus() == PlanStatus.NO_PLAN){
            l.setPlanStatus(PlanStatus.PLAN_RUNNING);
        }
        //修改数据
        updateById(l);
    }

实现查询学习计划进度的接口

        1.接口分析

                要查询的数据分为两部分:本周计划学习的每个课程的学习进度;本周计划学习的课程总的学习进度。如下图所示。

        因此查询需要查询本周已经学习的小节数量,计划学习的数量,本周学习积分(暂先不做)

以及需要分页的查询课程信息和每个课程对应的学习记录的信息。通过当前时间减去周几加1即可推断一周的开始时间,同理,通过8-当前是周几,则可判断当前日期还需要加几天才是周日,然后再去当前周日的最大时间即可。

        2.接口实现

        具体的service层代码实现如下:

/**
代码逻辑分析:首先获取当前用户的id和当前一周的起始时间和结束时间,然后通过这三个字段去查询学习记录表中的数据,将查询到的数量返回,则可查询到已经学习完成的小节数量,然后将该字段注入到返回的VO之中。同理去查询本周总的计划学习的数量,通过查询week_freq字段(需要判断有没有学习计划,课程是不是生效中),去求和该字段的数量即为一周总计划需要学习的,然后再查询第二步分页的信息,将分页的信息的记录取出。在根据前端的需要的字段,还需要返回对应的课程信息,以及已经学习的小节数量(此处不用mp去查询的原因是为了防止循环依赖),将上述字段填充到一个新的volist中,返回给前端所需要的分页数据即可

**/

@Override
    @Transactional
    public LearningPlanPageVO queryMyPlans(PageQuery pageQuery) {
        LearningPlanPageVO result = new LearningPlanPageVO();
        //1.获取当前用户id
        Long userId = UserContext.getUser();
        //2.获取本周的起始时间
        LocalDate now = LocalDate.now();
        LocalDateTime beginTime = DateUtils.getWeekBeginTime(now);
        LocalDateTime endTime = DateUtils.getWeekEndTime(now);
        //3.查询总的统计数据
        //3.1本周已经学习的小节数量
        Integer weekFinished = recordMapper.selectCount(new LambdaQueryWrapper<LearningRecord>()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getFinished, true)
                .gt(LearningRecord::getFinishTime, beginTime)
                .lt(LearningRecord::getFinishTime, endTime));

        result.setWeekFinished(weekFinished);
        //3.2本周总的计划学习的小节数量
         Integer weekTotalPlan = getBaseMapper().queryTotalPlan(userId);
        result.setWeekTotalPlan(weekTotalPlan);
        //3.3 TODO 本周学习积分


        //4查询分页数据
        //4.1 分页查询课表信息以及学习计划信息
        Page<LearningLesson> page = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.LEARNING, LessonStatus.NOT_BEGIN)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return result.emptyPage(page);

        }
        //4.2 查询课表对应的课程信息
        Map<Long, CourseSimpleInfoDTO> cMap = queryCourseSimpleInfo(records);
        //4.3 统计每一个课程本周已经学习的小节数量
        List<IdAndNumDTO> list = recordMapper.countLearnedSections(userId, beginTime, endTime);
        Map<Long, Integer> countMap = IdAndNumDTO.toMap(list);
        //4.4 封装数据
        List< LearningPlanVO> voList = new ArrayList<>(records.size());
        for (LearningLesson record : records) {
            //1.直接拷贝属性
            LearningPlanVO vo = BeanUtils.copyBean(record, LearningPlanVO.class);
            //填充课程详细信息
            CourseSimpleInfoDTO cInfo = cMap.get(record.getCourseId());
            if (cInfo != null){
                vo.setCourseName(cInfo.getName());
                vo.setSections(cInfo.getSectionNum());
            }
            //每个课程本周已经学习的小节数量
            vo.setWeekLearnedSections(countMap.getOrDefault(record.getId(),0));
            voList.add(vo);
        }
        return result.pageInfo(page.getTotal(),page.getPages(),voList);
    }

对应的mapper层如下:

/**
统计每一个课程已经学习的小节数量
**/

<select id="countLearnedSections" resultType="com.tianji.api.dto.IdAndNumDTO">
        select lesson_id as id ,count(1) as num
        from learning_record
        where user_id = #{userId}
        and finished =1
        and finish_time &gt; #{begin} and finish_time &lt; #{end}
        group by lesson_id
    </select>

实现定时检查课程是否过期的接口

        1.接口分析

                首先要实现定时任务,需要去启动类上检查是否有定时任务的注解@EnableScheduling。如果通过创建定时任务类。在定时任务类中,首先要得到那些没有过期的课程表列表,只有这些才是有可能过期的。然后循环遍历该列表,如果过期了,重新设置状态,最后统一的交给mp去更新。

        2.接口实现

                以下是定时任务实现类:

/**
注意,需要将该类注入到容器当中,否则会不生效
**/


@Slf4j
@Component
@RequiredArgsConstructor
public class CheckCourseIsExpire {
    private final ILearningLessonService service;

/**
 * 检查课程状态的任务方法
 * 该方法用于执行课程状态的检查操作,检查课程是否过期
 */
    @Scheduled(cron = "0 * * * * ?") //一分钟执行一次,判断课程是否过期
    private void CheckCourseStatusTask() {
        LocalDateTime currentTime = LocalDateTime.now();
        log.info("检查课程是否过期,当前时间为:{}", currentTime);
        //首先要去学生课程表中取出数据,其里面就有过期时间
        //取出当前状态且为未过期的数据
        List<LearningLesson> learningLessonList = service.list(new LambdaQueryWrapper<LearningLesson>()
                .ne(LearningLesson::getStatus, LessonStatus.EXPIRED));

        //然后判断当前时间是否大于过期时间,如果大于则修改课程状态为已过期
        for (LearningLesson list : learningLessonList) {
            if (list.getExpireTime().isBefore(currentTime)) {
                //设置状态为已过期
                list.setStatus(LessonStatus.EXPIRED);
//                service.updateById(list); 这里调用对性能影响大
            }
        }
        service.updateBatchById(learningLessonList);
    }
}

代码推送

        可能会发生的情况:

                将当前分支合并到master分支中会发生冲突,原因就是因为依赖代码生成器生成的Controller,service等等没有推送到本地仓库,使用git status可以发现有些文件没有添加,使用git add 即可添加文件到本地仓库,然后就可以将当前分支合并到master分支,后续使用master分支推送即可。

posted on 2025-11-22 19:30  青云计划  阅读(25)  评论(0)    收藏  举报  来源