天机学堂—学习辅助功能(含场景问答和作业)

我的课表

需求分析

原型图

管理后台

用户端

流程图

数据设计

接口设计

  1. 支付成功报名课程后, 加入到我的课表(MQ)
  2. 分页查询我的课表
  3. 查询我正在学习的课程
  4. 根据id查询指定课程的学习状态
  5. 删除课表中的某课程

代码实现

数据表设计

添加课程到课表(非标准接口)

需求:用户购买/报名课程后,交易服务会通过MQ通知学习服务,学习服务将课程加入用户课表中

接口设计

首先写Listener,定义好RabbitMQ,对DTO数据校验后调用lessonService

@Component
@RequiredArgsConstructor
@Slf4j
public class LessonChangeListener {

    private final ILearningLessonService lessonService;

    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(name = "learning.lesson.pay.queue", durable = "true"),
                    exchange = @Exchange(name = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC),
                    key = MqConstants.Key.ORDER_PAY_KEY
            )
    )
    public void listenLessonPay(OrderBasicDTO dto) {
        //1.非空判断
        if(dto==null || dto.getOrderId()==null || CollUtils.isEmpty(dto.getCourseIds())){
            log.error("接收到MQ的消息有误,订单数据为空");
            return;
        }
        //2.调用业务
        lessonService.addLesson(dto);
    }

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

    private final CourseClient courseClient;


    @Override
    public void addLesson(OrderBasicDTO dto) {

        //1.远程调用tj-course微服务,根据课程id查询出课程信息(课程有效期)
        List<Long> courseIds = dto.getCourseIds();
        List<CourseSimpleInfoDTO> courseList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(courseList)) {
            throw new BadRequestException("课程不存在");
        }
        //2.遍历课程信息,封装LearningLesson(用户id,课程id,过期时间)
        List<LearningLesson> lessonList = new ArrayList<>();
        for (CourseSimpleInfoDTO course : courseList) {
            LearningLesson lesson = new LearningLesson();
            //2.1 填充userId和courseId
            lesson.setUserId(dto.getUserId());
            lesson.setCourseId(course.getId());
            //2.2 算出过期时间
            lesson.setExpireTime(LocalDateTime.now().plusMonths(course.getValidDuration())); 
            lessonList.add(lesson);
        }
        //3.批量保存
        saveBatch(lessonList);

    }
}

Q1: 过期时间怎么计算?

加入课表的时间(LocalDateTime.now())+课程有效期

Q2: 课程信息是怎么获得的?

远程调用课程微服务tj-course,根据课程id查询课程信息

Q3: 当前添加课表的业务是怎么保证幂等性?

当前业务靠MySQL,在DDL中把(user id ,course id)设置唯一约束

通用做法:在DTO中找个唯一标识,判断Redis里面是否有

分页查询我的课表

需求: 在个人中心-我的课程页面,可以分页查询当前用户的课表及学习状态信息。

接口设计

分页用到的类如下

PageQuery

public class PageQuery {
    public static final Integer DEFAULT_PAGE_SIZE = 20;
    public static final Integer DEFAULT_PAGE_NUM = 1;

    @ApiModelProperty(value = "页码", example = "1")
    @Min(value = 1, message = "页码不能小于1")
    private Integer pageNo = DEFAULT_PAGE_NUM;

    @ApiModelProperty(value = "每页大小", example = "5")
    @Min(value = 1, message = "每页查询数量不能小于1")
    private Integer pageSize = DEFAULT_PAGE_SIZE;

    @ApiModelProperty(value = "是否升序", example = "true")
    private Boolean isAsc = true;

    @ApiModelProperty(value = "排序字段", example = "id")
    private String sortBy;
}

返回的分页结果PageDTO

public class PageDTO<T> {
    @ApiModelProperty("总条数")
    protected Long total;
    @ApiModelProperty("总页码数")
    protected Long pages;
    @ApiModelProperty("当前页数据")
    protected List<T> list;
}

接口

@RestController
@RequestMapping("/lessons")
@Api(tags = "我的课表相关的接口")
@RequiredArgsConstructor
public class LearningLessonController {

    private final ILearningLessonService lessonService;

    @GetMapping("/page")
    @ApiOperation("分页查询我的课表")
    public PageDTO<LearningLessonVO> queryMyLesson(@Validated PageQuery query){
        return lessonService.queryMyLesson(query);
    }

}
    /**
     * 分页查询我的课表
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> queryMyLesson(PageQuery query) {
        // 1. 分页查询出当前用户的课表信息
        Long userId = UserContext.getUser();
        Page<LearningLesson> pageResult = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<LearningLesson> lessonList = pageResult.getRecords();
        if (CollUtils.isEmpty(lessonList)) {
            return PageDTO.empty(pageResult);
        }

        // 2. 根据courseId查询出课程信息
        List<Long> cIds = lessonList.stream().map(LearningLesson::getCourseId).collect(Collectors.toList());
        List<CourseSimpleInfoDTO> courseList = courseClient.getSimpleInfoList(cIds);
        if (CollUtils.isEmpty(courseList)) {
            throw new BadRequestException("课程信息不存在");
        }
        Map<Long, CourseSimpleInfoDTO> courseMap = courseList.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));


        // 3. 遍历课表List,封装LearningLessonVO
        List<LearningLessonVO> voList = new ArrayList<>();
        for (LearningLesson lesson : lessonList) {
            LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
            //封装课程信息
            CourseSimpleInfoDTO course = courseMap.get(lesson.getCourseId());
            if (course!=null){
                vo.setCourseName(course.getName());
                vo.setCourseCoverUrl(course.getCoverUrl());
                vo.setSections(course.getSectionNum());
            }
            voList.add(vo);
        }

        // 4. 封装PageDTO
        return PageDTO.of(pageResult,voList);
    }

Q1: 为什么把courseList转成了courseMap

Map中Key是课程id,Value是课程对象,从而可以通过课程id拿到课程对象                                                            

查询最近正在学习的课程

需求: 在首页、个人中心-课程表页,需要查询并展示当前用户最近一次学习的课程

接口设计

这次代码是个标准的三层都写案例了

@GetMapping("/now")
@ApiOperation("查询当前用户正在学习的课程")
public LearningLessonVO queryCurrent() {
    return lessonService.queryCurrent();
}
    /**
     * 查询当前用户正在学习的课程
     *
     * @return
     */
    @Override
    public LearningLessonVO queryCurrent() {
        //1.获得当前用户Id
        Long userId = UserContext.getUser();
        //2.查询课表,当前用户正在学习的课程 SELECT * FROM   learning_lesson WHERE user_id =2 AND status  = 1 ORDER BY latest_learn_time  DESC  limit 0,1;
        LearningLesson lesson = getBaseMapper().queryCurrent(userId);
        if(lesson == null){
            return null;
        }

        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        //3.根据课程id查询出课程信息
        CourseFullInfoDTO course = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if(course == null){
            throw new BadRequestException("课程不存在");
        }
        vo.setCourseName(course.getName());
        vo.setCourseCoverUrl(course.getCoverUrl());
        vo.setSections(course.getSectionNum());

        //4.统计课程中的课程
        Integer courseAmount = lambdaQuery().eq(LearningLesson::getUserId, userId).count();
        vo.setCourseAmount(courseAmount);

        //5.根据最近学习的章节id查询章节信息
        List<CataSimpleInfoDTO> catalogueList = catalogueClient.batchQueryCatalogue(List.of(lesson.getLatestSectionId()));
        if(!CollUtils.isEmpty(catalogueList)){
            CataSimpleInfoDTO cata = catalogueList.get(0);
            vo.setLatestSectionIndex(cata.getCIndex());
            vo.setLatestSectionName(cata.getName());
        }
        return vo;
    }
public interface LearningLessonMapper extends BaseMapper<LearningLesson> {

    @Select("SELECT * FROM learning_lesson WHERE user_id = #{userId} AND status=1 ORDER BY latest_learn_time DESC limit 0,1")
    LearningLesson queryCurrent(@Param("userId") Long userId);
}

根据id查询指定课程的学习状态

需求: 在课程详情页需要查询用户是否购买了指定课程,如果购买了则要返回学习状态信息

接口设计

代码最简单的一集

@GetMapping("/{courseId}")
@ApiOperation("根据课程id查询课程状态")
public LearningLessonVO queryByCourseId(@PathVariable("courseId") Long courseId) {
    return lessonService.queryByCourseId(courseId);
}
    /**
     * 根据课程id查询出课程状态
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonVO queryByCourseId(Long courseId) {
        // 1. 根据用户id和课程id查询出课表LearningLesson
        LearningLesson lesson = lambdaQuery()
                .eq(LearningLesson::getUserId, UserContext.getUser())
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if (lesson == null) {
            return null;
        }
        // 2. 根据课程id查询出课程信息
        CourseFullInfoDTO course = courseClient.getCourseInfoById(courseId, false, false);
        if(course==null){
            throw new BadRequestException("课程不存在");
        }

        // 3. 封装vo
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        vo.setCourseName(course.getName());
        vo.setCourseCoverUrl(course.getCoverUrl());
        vo.setSections(course.getSectionNum());
        return vo;
    }

Q1: 为什么不能直接使用courseId查询课表?

还需要userid的约束

检查课程是否有效(作业)

这是一个微服务内部接口,当用户学习课程时,可能需要播放课程视频。此时提供视频播放功能的媒资系统就需要校验用户是否有播放视频的资格。所以,开发媒资服务(tj-media)的同事就请你提供这样一个接口。

    @ApiOperation("校验当前课程是否已经报名")
    @GetMapping("/{courseId}/valid")
    public Long isLessonValid(
            @ApiParam(value = "课程id" ,example = "1") @PathVariable("courseId") Long courseId){
        return lessonService.isLessonValid(courseId);
    }
    @Override
    public Long isLessonValid(Long courseId) {
        // 1.获取登录用户
        Long userId = UserContext.getUser();
        if (userId == null) {
            return null;
        }
        // 2.查询课程
        LearningLesson lesson = lambdaQuery()
                .eq(LearningLesson::getUserId, UserContext.getUser())
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if (lesson == null) {
            return null;
        }
        return lesson.getId();
    }

删除课表中课程(作业)

删除课表中的课程有两种场景:

  • 用户直接删除已失效的课程
  • 用户退款后触发课表自动删除
    @DeleteMapping("/{courseId}")
    @ApiOperation("删除指定课程信息")
    public void deleteCourseFromLesson(
            @ApiParam(value = "课程id" ,example = "1") @PathVariable("courseId") Long courseId) {
        lessonService.deleteCourseFromLesson(null, courseId);
    }
@Override
    public void deleteCourseFromLesson(Long userId, Long courseId) {
        // 1.获取当前登录用户
        if (userId == null) {
            userId = UserContext.getUser();
        }
        // 2.删除课程
        remove(buildUserIdAndCourseIdWrapper(userId, courseId));
    }

private LambdaQueryWrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {
        LambdaQueryWrapper<LearningLesson> queryWrapper = new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
        return queryWrapper;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/618676.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

猫头虎分享已解决Bug || **Babel转换器下载问题** Failed to resolve babel-loader dependency`

猫头虎分享已解决Bug &#x1f42f; || Babel转换器下载问题 &#x1f6ab;Failed to resolve babel-loader dependency 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a…

网络 | 应用层-websocket协议报文格式解析

websocket的官方文档为rfc(request for comments)&#xff0c;是网络协议的规范文档。它包含了许多计算机世界的核心知识 除了这里的websocket&#xff0c;它里边还包含我们熟知的http,tcp等协议的解析。 websocket协议对应的编号是rfc 6455 websocket协议格式解析 由图可知&a…

【RAG论文】RAG中半结构化数据的解析和向量化方法

论文简介 论文题目&#xff1a; 《A Method for Parsing and Vectorization of Semi-structured Data used in Retrieval Augmented Generation》 论文链接&#xff1a; https://arxiv.org/abs/2405.03989 代码: https://github.com/linancn/TianGong-AI-Unstructure/tree/m…

数据结构学习/复习13

一、选择排序 1.直接选择排序 2.堆排序 3.性能测试 二、交换排序 1.冒泡排序 2.快速排序 1.Hore版(递归) 2.随机选关键字Hore版(递归) 3.三数取中Hore版(递归) 3.挖坑法改进Hore版(递归) 4.前后指针法 5.小区间优化 5.性能测试 注意事项1&#xff1a;关键字选取时&#xff0c…

微服务架构与单体架构

微服务架构与与单体架构比较 微服务架构是一种将应用程序作为一组小的、独立服务的系统架构风格&#xff0c;每个服务运行在其自己的进程中&#xff0c;并通常围绕业务能力组织。这些服务通过定义良好且轻量级的机制&#xff08;通常是HTTP REST API&#xff09;进行通信。微服…

常用的简单友好的工单系统(免费)- WGCAT

最近在项目中&#xff0c;有工单系统的需求场景&#xff0c;所以想寻找一款轻量简单的运维工单软件&#xff0c;主要用来记录和处理工作中的一些故障、维护&#xff0c;主要用来记录设备的维护状态&#xff0c;包括服务器、主机、交换机那些 WGCAT&#xff0c;是一款简单轻量的…

上位机图像处理和嵌入式模块部署(树莓派4b的软件源)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多文章都建议替换一下树莓派4b的软件源&#xff0c;不过我自己实际使用下来&#xff0c;官方的软件下载速度其实还可以。这里下载的时候&#xf…

前端Vue架构

1 理解&#xff1a; 创建视图的函数&#xff08;render&#xff09;和数据之间的关联&#xff1b; 当数据发生变化的时候&#xff0c;希望render重新执行&#xff1b; 监听数据的读取和修改&#xff1b; defineProperty&#xff1a;监听范围比较窄&#xff0c;只能通过属性描…

QX---mini51单片机学习---(8)8*8点阵屏

目录 1LED点阵屏简绍 2 8*8点阵屏电路图74 3 74HC595芯片 4实践编程 1LED点阵屏简绍 2 8*8点阵屏电路图74 怎么点亮&#xff0c;正极给高负极给低 不能同时静态显示&#xff0c;跟数码管动态显示一样&#xff0c;反复横跳&#xff0c;利用视觉效果 3 74HC595芯片 …

kilimall非洲电商培训,基础版+进阶版+高阶版 从0-1个人可入驻的平台(12节)

亲爱的朋友们&#xff0c;你们知道吗&#xff1f;有一个神奇的电商平台——kilimall&#xff0c;它可以帮助你实现创业梦想&#xff0c;让你走上财富之路&#xff01; 首先&#xff0c;让我给大家介绍kilimall的基础版。基础版针对的是0经验的小白&#xff0c;提供了详细的教程…

有没有适合女生或者宝妈下班后可以做的副业?

宝妈与上班族女生的新篇章&#xff1a;水牛社副业兼职之旅 在繁忙的职场和温馨的家庭之间&#xff0c;不少女性渴望找到一种既能兼顾家庭又能实现自我价值的兼职方式。对于上班族女生和宝妈们来说&#xff0c;水牛社这样的线上任务平台为她们提供了一个全新的选择。 上班族女…

MFC的CPen与CBush画图对象使用步骤

在MFC中&#xff0c;CPen和CBrush是两个常用的绘图对象&#xff0c;分别用于定义画笔和画刷&#xff0c;可以用于绘制图形、填充区域等。下面我会详细介绍如何在MFC中使用CPen和CBrush来绘制和填充图形。 使用 CPen 绘制图形&#xff1a; 创建 CPen 对象&#xff1a; 首先&am…

2000-2022年各地级市知识产权审判结案数数据

2000-2022年各地级市知识产权审判结案数数据 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;城市知识产权审判结案数 3、来源&#xff1a;整理自北大法宝 4、范围&#xff1a;287个地级市 5、用途&#xff1a;可用于衡量地级市知识产权保护水平 6、指标解释&#x…

第五步->手撕spring源码之资源加载器解析到注册

本步骤目标 在完成 Spring 的框架雏形后&#xff0c;现在我们可以通过单元测试进行手动操作 Bean 对象的定义、注册和属性填充&#xff0c;以及最终获取对象调用方法。但这里会有一个问题&#xff0c;就是如果实际使用这个 Spring 框架&#xff0c;是不太可能让用户通过手动方式…

数据结构·一篇搞定栈!

好久不见&#xff0c;超级想念 废话不多说&#xff0c;直接看 引言 在数据结构的大家族中&#xff0c;栈&#xff08;Stack&#xff09;是一种非常重要的线性数据结构&#xff0c;它的特点是后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09;。栈在程序设…

Star15.3k,开源数据可视化分析工具项目

好东西来了&#xff0c;这是一个人人可用的开源数据可视化分析工具项目&#xff0c;V 哥迫不及待的要给大家推荐这个项目&#xff0c;帆软、Tableau 等商业 BI 工具的开源替代&#xff0c;已在 Github 上被 Star了15.3k了&#xff0c;大家一起来了解一下。自己搭建起来可用&…

consul启动Error_server_rejoin_age_max (168h0m0s) - consider wiping your data dir

consul 启动报错&#xff1a; consul[11880]: 2024-05-12T08:37:51.095-0400 [ERROR] agent: startup error: error"refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s) - consider wiping you…

MFC桌面应用中窗口的客户区与非客户区的

在MFC&#xff08;Microsoft Foundation Class&#xff09;中&#xff0c;窗口被分为客户区和非客户区。理解这两个概念对于设计和开发Windows应用程序至关重要。 客户区&#xff08;Client Area&#xff09;&#xff1a; 客户区是窗口中用于显示应用程序内容的区域。它是窗口…

机器学习周报第三十八周 iTransformer

文章目录 week38 iTransformer摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构**转置Embedding&#xff1a;****LayerNorm&#xff08;层归一化&#xff09;****Feed-forward network&#xff08;前馈网络&#xff09;****Multivariate-Attention&#xff08;多变量注意…

深度学习中的一些概念

训练术语 欠拟合 欠拟合是指模型没有很好地捕获到数据特性&#xff0c;不能完整地表示数据的全部信息&#xff0c;也就是模型的复杂度低于应有的水平。例如&#xff0c;假设一个数据集实际上服从二阶多项式分布&#xff0c;但我们使用一阶线性模型去拟合它&#xff0c;这样的…