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

news2024/11/16 22:22:12

我的课表

需求分析

原型图

管理后台

用户端

流程图

数据设计

接口设计

  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;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ApiHug Official Website

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | MarketplaceApiHug-H…

OpenHarmony 实战开发——分布式硬件管理详解

前言 分布式硬件是 OpenHarmony 提供的一个分布式能力&#xff0c;能将多种设备组成一个“超级终端”&#xff0c;使用户根据现实需要和硬件能力&#xff0c;选择合适的硬件提供服务&#xff0c;灵活运用硬件进行资源分配和按需组合&#xff0c;充分发挥各类硬件设备的能力&am…

Jenkins android 自动打包安卓 centos8.5 运维系列五

1 新建项目android #cat android.sh #!/bin/bash rm -rf /data/.jenkins/workspace/android/app/build/outputs/apk/debug/* rm -rf /data/.jenkins/workspace/android/app/build/outputs/apk/release/* cd /data/.jenkins/workspace/android/app source /etc/profile g…

WEB前端复习——CSS

CSS:层叠样式表 将显示样式与内容分开 基本语法&#xff1a; 选择器{ 规则; } ①标签选择器&#xff1a;以HTML标签名为选择 <style>p{color: red;} </style> <body><p>你好</p> </body> ②id选择器&#xff1a;一次性的 以#号定义 &l…

在做题中学习(57):寻找数组的中心下标

724. 寻找数组的中心下标 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;前缀和后缀和 思路&#xff1a;要看一个数是不是中心下标&#xff0c;就看他前面数的和 与 后面数的和 相不相等。 1.i前面数的和&#xff0c;是[0,i-1] 的前缀和&#xff0c;i后面数的和&am…

音视频入门基础:像素格式专题(2)——不通过第三方库将RGB24格式视频转换为BMP格式图片

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 一、引…

STM32-07-STM32_外部中断

文章目录 STM32 中断系统1. 中断2. NVIC3. EXTI4. AFIO5. 中断配置步骤6. 外部中断代码 STM32 中断系统 1. 中断 目的&#xff1a;中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。中断过程&#xff1a;当CPU正在处理某事件的时候外界发生了紧急事件请求 &#…

ESP-01S刷固件ESP8266_NonOS_AT_Bin_V1.7.5_1 笔记240510

ESP-01S刷固件ESP8266_NonOS_AT_Bin_V1.7.5_1 笔记240510 固件下载地址 ESP-AT固件页面: https://www.espressif.com.cn/zh-hans/products/sdks/esp-at/resource 直接下载ESP8266 NonOS AT Bin V1.7.5.zip: https://www.espressif.com.cn/sites/default/files/ap/ESP8266_No…

32.768kHz晶振的时间精度问题及其解决方法

32.768kHz晶振因其稳定性高、功耗低&#xff0c;被广泛应用于实时时钟(RTC)、计时电路及低功耗电子产品中。然而&#xff0c;在某些情况下&#xff0c;这些晶振可能出现时间偏差&#xff0c;影响设备正常工作。以下是可能导致32.768kHz晶振时间误差的原因及相应的解决策略。 温…

Snort规则编写

1&#xff09;TCP NULL端口扫描的特征&#xff1a; 扫描者发送一个TCP SYN包到目标主机的某个端口&#xff0c;但不设置任何TCP标志位&#xff08;即flag为NULL&#xff09;。 如果端口关闭&#xff0c;目标主机会回应一个RST&#xff08;复位&#xff09;和ACK&#xff08;确认…

数据结构之图——探索图论的奥秘

前言 在这篇文章中&#xff0c;我们一起来看看我们生活中都会用到&#xff0c;但却不那么熟悉的数据结构——图&#xff08;英语&#xff1a;graph&#xff09;。我们看下百科定义&#xff1a; 在计算机科学中&#xff0c;图&#xff08;英语&#xff1a;graph&#xff09;是一…

py黑帽子学习笔记_网络编程工具

tcp客户端 socket.AF_INET表示使用标准IPV4地址和主机名 SOCK_STREAM表示这是一个TCP客户端 udp客户端 udp无需连接&#xff0c;因此不需要client.connect这种代码 socket.SOCK_DGRAM是udp的 tcp服务端 server.listen(5)表示设置最大连接数为5 发现kill server后端口仍占用…

大模型微调之 在亚马逊AWS上实战LlaMA案例(九)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;九&#xff09; 代码阅读 src/llama_recipes/inference/prompt_format_utils.py 这段代码是一个Python模块&#xff0c;它定义了几个类和模板&#xff0c;用于生成安全评估的提示文本。以下是对每一行代码的注释和提示词…

Datax数据采集

一、Datax介绍 官网&#xff1a; DataX/introduction.md at master alibaba/DataX GitHub DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、…

MySQL innodb_buffer_pool_size 相关常用语句

对于MySQL速度慢的问题&#xff0c;除了优化 SQL 以外&#xff0c;应该必须优先想到的即使 MySQL 数据库的 innodb_buffer_pool_size 配置问题。 一般来说&#xff0c;innodb_buffer_pool_size 的默认大小都是很小的&#xff0c;尤其是 win 下其默认大小更是只有离谱的 8M。Li…

2024 年最新本地、云服务器安装部署 miniconda 环境详细教程(更新中)

Anaconda 概述 Anaconda 是专门为了方便使用 Python 进行数据科学研究而建立的一组软件包&#xff0c;涵盖了数据科学领域常见的 Python 库&#xff0c;并且自带了专门用来解决软件环境依赖问题的 conda 包管理系统。主要是提供了包管理与环境管理的功能&#xff0c;可以很方便…

土壤墒情自动监测站—墒情异常数据报警提示

TH-TS600土壤墒情自动监测站通常配备有预警提示功能&#xff0c;用于在墒情出现异常情况时及时向用户发出警告。这一功能对于农业生产至关重要&#xff0c;因为它可以帮助农民或农田管理者及时发现土壤墒情的变化&#xff0c;并采取相应的措施来确保作物健康生长。 土壤墒情自动…

Excel实用技巧持续学习

1、Excel高效设置图标格式&#xff1a; 2、饼图可以统一设置数据标签在图外面&#xff01;&#xff01; 环形图不可以&#xff0c;但是可以中间手动加上白色圆形&#xff0c;将饼图变为圆环。 可以设置标签的文本显示&#xff1a; 3、饼图和环形图最好进行排序&#xff01;显得…

睿尔曼机械臂ROS控制

下载git工程 git clone https://github.com/RealManRobot/rm_robot.git安装配置 catkin build rm_msgs source devel/setup.bash catkin build source setup.bash这里注意&#xff0c;如果采用setup.sh多半不会成功&#xff0c;必须要source setup.bash文件&#xff0c;ros才…

云渲染动画300帧需要多久呢?瑞云渲染为你揭秘

在动画制作过程中&#xff0c;渲染的速度非常关键。对于一个项目需要渲染的300帧来说&#xff0c;由于硬件的限制&#xff0c;许多公司的设备可能无法快速完成这项任务。此时&#xff0c;借助云渲染服务的强大计算能力&#xff0c;可以显著减少完成时间&#xff0c;从而提速整个…