StrivingYu

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

实现课程加入课程表的接口

       1. 接口分析:

                当用户支付完成或者报名免费课程后,应该立刻将课程加入到课表中。这时就需要通过mq去发送消息通知学习微服务,将课程加入到课表中,同时在学习微服务中,创建监听的类去监听订单微服务发送过来的消息。

        2.实现接口

                1.找到发送mq消息的微服务的service

                通过上述的分析可以知道,消息是从订单微服务中发送,因此,需要去订单微服务中去找到对应的发送消息的方法,如下:

屏幕截图 2025-11-30 140158

                  由这段代码可以知道订单微服务发送的消息的交换机,消息队列和路由key以及发送的内容。

                2.通过上述找到的内容去创建监听类

        在知道上述信息后,需要在去学习微服务中创建监听方法,,监听mq发送过来的消息。

具体方法如下:

@Component
@Slf4j
@RequiredArgsConstructor
public class LessonChangeListener {

    private final ILearningLessonService learningLessonService;
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value ="learning.lesson.pay.queue" , durable = "true"),
            exchange = @Exchange(name = MqConstants.Exchange.PAY_EXCHANGE,type = ExchangeTypes.TOPIC),
            key = MqConstants.Key.ORDER_PAY_KEY
            ))
    public void listenLessonPay(OrderBasicDTO order){
        //健壮性处理
        if(order == null || order.getUserId() == null || CollUtils.isEmpty(order.getCourseIds())){
            //数据有误
            log.info("接收到的MQ消息有误,订单数据为空:{}",order);
            return;
        }
        log.info("接收到用户{}的订单{},需要添加课程{}到课表中",order.getUserId(),order.getOrderId(),order.getCourseIds());
        //添加课程
        learningLessonService.addUserLesson(order.getUserId(), order.getCourseIds());
    }

    代码解释:该类实现了一个监听的mq消息的方法,其中发送过来的数据类型,可以由上述订单微服务发送的消息可知,代码的逻辑为:先判断发送过来的消息是否为空,不为空则通过学习微服务的service 去实现添加课程的方法其中的RabbitListener的注释中声明了交换机,消息队列和路由key,可以通过上述订单微服务去找到。

                3.实现service接口

     将上述监听类实现后,需要实现learningLessonService中的addUserLesson方法,代码如下:

@Service
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;

/**
 * 添加用户课程到课表
 * @param userId 用户ID,用于标识需要添加课程的用户
 * @param courseIds 课程ID列表,包含需要关联到指定用户的所有课程ID
 */
@Override
@Transactional
    public void addUserLesson(Long userId, List<Long> courseIds) {
        //查询课程的简单信息
    List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
    if (CollUtils.isEmpty(cInfoList)) {
        log.error("课程信息不存在,无法添加到课表");
        return;
    }
    //循环遍历,处理LearningLesson数据
    ArrayList<LearningLesson> list = new ArrayList<>(cInfoList.size());
    for (CourseSimpleInfoDTO cInfo : cInfoList) {
        LearningLesson lesson = new LearningLesson();
        //获取过期时间
        //获取有效时间还有多久
        Integer validDuration = cInfo.getValidDuration();
        //当前时间加上有效时间
        if (validDuration != null && validDuration > 0) {
            LocalDateTime now = LocalDateTime.now();
            lesson.setCreateTime(now);
            lesson.setExpireTime(now.plusDays(validDuration));
        }
        //填充userId,CourseId
        lesson.setUserId(userId);
        lesson.setCourseId(cInfo.getId());
        list.add(lesson);
    }
    //批量新增
    saveBatch(list);
    }
}

      代码解释:在上述代码中,先是做了健壮性处理,先通过传入的课程ids和远程调用CourseClient去查询课程集合,若返回的信息为空,则可以判断课程不存在。返回不为空则去循环遍历这个集合,通过查询的有效时间和当前时间相加,则为过期时间,需要注意的市此处还需要判断是否当前课程是否有有效时间,将过期时间和userId以及couresId注入到LearningLesson对象中,最后通过mp的方法批量将对象LearningLesson的集合list插入到数据库中即可。

实现分页查询接口

        1.接口分析

                实现分页查询前,需要知道当前的用户信息。如何获取当前的用户信息呢?可以让让前端发送请求携带token信息,发送到网关的时候,通过全局过滤器去解析token,发送到下游的微服务,下游的微服务通过拦截器将用户信息取出存入ThreadLocal存放。随后service层便可取到对应的用户id,然后使用mp语句查询。

        2.实现接口

               service层的方法如下:

    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery pageQuery) {
        //获取当前登录用户
        Long userId = UserContext.getUser();
        //分页查询
        Page<LearningLesson> page = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return  PageDTO.empty(page);
        }
        //查询课程信息
        //获取课程ID列表
        List<Long> courseIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toList());
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(cInfoList)) {
            log.error("课程信息不存在,无法添加到课表");
            throw new BadRequestException("课程信息不存在,无法添加到课表");
        }
        //把课程集合处理成Map,key为课程id,value为对应的课程,使用stream流
        Map<Long, CourseSimpleInfoDTO> cMap = cInfoList.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, cInfo -> cInfo));

        //组装返回结果
        List<LearningLessonVO> voList = new ArrayList<>(records.size());
        for (LearningLesson record : records) {
            LearningLessonVO learningLessonVO = BeanUtils.copyBean(record, LearningLessonVO.class);
            //填充课程信息
            CourseSimpleInfoDTO cInfo = cMap.get(record.getCourseId());
            learningLessonVO.setCourseName(cInfo.getName());
            learningLessonVO.setCourseCoverUrl(cInfo.getCoverUrl());
            learningLessonVO.setSections(cInfo.getSectionNum());
            voList.add(learningLessonVO);
        }
        return PageDTO.of(page, voList);
    }
 public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
        if (StringUtils.isBlank(sortBy)){
            sortBy = defaultSortBy;
            this.isAsc = isAsc;
        }
        Page<T> page = new Page<>(pageNo, pageSize);
        OrderItem orderItem = new OrderItem();
        orderItem.setAsc(this.isAsc);
        orderItem.setColumn(sortBy);
        page.addOrder(orderItem);
        return page;
    }

代码解释 :首先确定当前用户是谁,拿出用户信息,拿出用户信息后使用mp查询,其中的查询条件page,里面的参数一般是new一个page对象,但是在这PageQery中准备了个方法,因此使用这个方法。然后将查询的结果返回,即可得到page对象,page对象里面有总页数,分页参数,以及记录内容,但是当前记录内容中只有课程id的信息,没有其他的有关课程的信息,因此需要借用远程调用去查询课程的信息。查询到了课程信息后,进行循环注入,由于注入条件需要匹配课程id较为麻烦,因此将课程信息通过stream的方式转化成map,key为课程id,value为课程信息。创建一个存储课程信息的list,通过循环的方式,将其添加到list,最后返回。

实现查询当前正在学习的课程的接口

        1.接口分析

                实现正在学习的课程的接口,首要要知道当前登录人的id,通过线程去取id,然后通过mp去查询,查询条件为userId,学习状态为正在学习,通过最新的学习时间进行降序排序。查询到的结果返回,观察到其还缺失字段,章节数量,章节名称,当前正在学习的章节序号,通过远程调用client去查找对应的信息即可。

        2.接口实现

                service层代码如下:

  public LearningLessonVO queryNowLearningLesson() { // 定义查询当前学习课程的方法
        //获取当前用户Id
        Long userId = UserContext.getUser();

        //查询当前用户最近学习的课程
        LearningLesson course = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
        if (course == null) return null;
        //将实体对象转换为视图对象
        LearningLessonVO learningLessonVO = BeanUtils.copyBean(course, LearningLessonVO.class);
        //获取课时数量,通过远程调用
        List<CataSimpleInfoDTO> cataInfo = catalogueClient.batchQueryCatalogue(CollUtils.singletonList(course.getCourseId()));
        if(!CollUtils.isEmpty(cataInfo)){
            CataSimpleInfoDTO cataSimpleInfoDTO = cataInfo.get(0);
            //设置小节名称和最后一次学习的章节序号
            learningLessonVO.setLatestSectionName(cataSimpleInfoDTO.getName());
            learningLessonVO.setLatestSectionIndex(cataSimpleInfoDTO.getCIndex());
        }
        //设置课程的总时数
        List<CourseSimpleInfoDTO> courseInfo = courseClient.getSimpleInfoList(CollUtils.singletonList(course.getCourseId()));
        if (!CollUtils.isEmpty(courseInfo)) {
            CourseSimpleInfoDTO courseSimpleInfoDTO = courseInfo.get(0);
            learningLessonVO.setSections(courseSimpleInfoDTO.getSectionNum());
            learningLessonVO.setCourseName(courseSimpleInfoDTO.getName());
            learningLessonVO.setCourseCoverUrl(courseSimpleInfoDTO.getCoverUrl());
        }
        return learningLessonVO;

    }

 实现删除课表中的课程接口

        1.接口分析

                用户删除课表中的课程有两种情况,第一种就是当前课程已经过期了,用户需要进行删除课程。第二种是用户购买了课程,但是后续有退款了,业务需要自动的将该课程删除,首先处理简单的情况(第一种情况),对于课程过期,通过mp去查询数据库,查询条件为当前用户id,和课程id。而对于第而种情况则需要参考实现课程加入课程的操作步骤,当用户退款时,利用mq发送消息到监听器,监听器接收到消息后,从service中调用其方法。

        接口实现
        第一种情况的service层
   @Transactional
    public void removeUserLesson(Long userId, Long courseId) {
        //获取用户id,如果传入的userId为null,则从当前用户上下文中获取
        if(userId == null){
            userId = UserContext.getUser();
        }
    // 使用Lambda方式构建删除条件,删除指定用户和课程的学习记录

        lambdaUpdate().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .remove();
    }
        第二种情况的实现:
                1.首先通过订单微服务去找到对应退款发消息的方法:

image

                2.找到课程表的微服务的监听类,去实现监听方法,调用service:
 @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value ="learning.lesson.refund.queue" , durable = "true"),
                    exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE,type = ExchangeTypes.TOPIC),
                    key = MqConstants.Key.ORDER_REFUND_KEY
            ))
    public void listenLessonRefund(OrderBasicDTO order){
        //健壮性处理:检查订单数据是否完整
        if(order == null || order.getUserId() == null || CollUtils.isEmpty(order.getCourseIds())){
            //数据有误:记录日志并直接返回,不进行后续处理
            log.info("接收到的MQ消息有误,订单数据为空:{}",order);
            return;
        }
        log.info("接收到用户{}的订单{},需要从课表中移除课程{}",order.getUserId(),order.getOrderId(),order.getCourseIds());
        //移除课程
        learningLessonService.removeUserLesson(order.getUserId(), order.getCourseIds().get(0));
    }
        3.去service层实现对应的方法(第一种情况代码)
   @Transactional
    public void removeUserLesson(Long userId, Long courseId) {
        //获取用户id,如果传入的userId为null,则从当前用户上下文中获取
        if(userId == null){
            userId = UserContext.getUser();
        }
    // 使用Lambda方式构建删除条件,删除指定用户和课程的学习记录

        lambdaUpdate().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .remove();
    }

实现统计课程学习人数接口

        1.接口分析

                使用mp去数据库中查询,查询条件为课程id跟传入的课程id一致,以及课程的状态不能为已过期。然后通过通过查询的数量,即可知道有多少人学习该课程

        2.接口实现

                service层接口代码如下:

   @Override
    public Integer getLessonCountByCourseId(Long courseId) {
        //learning_lesson表中有userId,查询匹配条件的课程,即可查询到课程学习人数
        return lambdaQuery().eq(LearningLesson::getCourseId, courseId)
                .in(LearningLesson::getStatus, LessonStatus.LEARNING,
                        LessonStatus.FINISHED,
                        LessonStatus.NOT_BEGIN)
                .count();
    }

剩下的作业接口大体与上述操作类似,若需要返回值,则根据前端需要的vo,后端从数据中查询到lesson对象,缺少前端需要的vo字段可以通过远程调用课程的client以及章节的client去补充。

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