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

我的课表

需求分析

原型图

管理后台

用户端

流程图

数据设计

接口设计

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

下面目录只是部分 ├─天机学堂day05 │ 01-回顾及作业.mp4 │ 02-问题及回答相关接口表结构.mp4 │ 03-新增修改互动问题接口实现.mp4 │ 04-用户端分页查询问题列表-1.mp4 │ 05-用户端分页查询问题列表-2及测试.mp4 │ 06-用户端查询问题详情.mp4 │ 07-管理端分页查询问题列表-1.mp4 │ 08-管理端分页查询问题列表-2.mp4 │ ├─天机学堂day06 │ 01-回顾及新增回答或评论步骤.mp4 │ 02-新增回答或评论实现及分页查询回答评论以及测试.mp4 │ 03-点赞业务介绍及库表服务搭建.mp4 │ 04-点赞及取消赞步骤.mp4 │ 04-点赞相关接口实现.mp4 │ 05-点赞相关接口测试.mp4 │ 06-feign降级.mp4 │ 07-点赞接口改进思路.mp4 │ 08-接口改造-点赞业务改造.mp4 │ 09-接口改造-定时任务及批量查询点赞状态.mp4 │ ├─天机学堂day07 │ 01-回顾.mp4 │ 02-需求分析及表结构.mp4 │ 03-签到思路分析及bitmap-setbit命令.mp4 │ 04-bitmap-bitfield命令及对应的java代码练习.mp4 │ 05-bitmap补充.mp4 │ 06-签到-1.mp4 │ 07-签到-2.mp4 │ 08-新增积分.mp4 │ 09-查询我的今日积分情况.mp4 │ ├─天机学堂day08 │ 01-回顾.mp4 │ 02-作业-查询我的签到记录查询赛季列表.mp4 │ 03-实时排行榜-利用redis的zset结构记录实时排行榜.mp4 │ 04-查询积分榜-1.mp4 │ 05-查询积分榜-2及测试.mp4 │ 06-历史排行榜思路.mp4 │ 07-历史排行榜-定时任务建表.mp4 │ 08-分区补充.mp4 │ 09-分布式任务调度介绍.mp4 │ 10-xxl-job入门demo及改造历史排行榜定时建表任务.mp4 │ ├─天机学堂day09 │ 01-回顾.mp4 │ 02-MP动态表名插件及持久化上赛季排行榜数据到db步骤.mp4 │ 03-持久化上赛季数据到db实现.mp4 │ 04-xxl-job分片广播及删除erdis中上赛季榜单数据.mp4 │ 05-xxl-job任务链说明.mp4 │ 06-优惠劵需求及环境搭建.mp4 │ 07-新增优惠劵-管理端.mp4 │ 08-分页条件查询优惠劵-管理端.mp4 │ 09-发放优惠劵.mp4 │ ├─天机学堂day10 │ 01-回顾.mp4 │ 02-兑换码分析.mp4 │ 03-兑换码分析2.mp4 │ 04-发行优惠劵异步生成兑换码步骤.mp4 │ 05-生成兑换码实现及测试.mp4 │ 06-async注解异步生成兑换码及测试.mp4 │ 07-查询发放中的优惠劵-1.mp4 │ 08-查询发放中的优惠劵-2及测试.mp4 │ 09-用户领取优惠劵及测试.mp4 │ 10-兑换码兑换优惠劵及测试.mp4 │ ├─天机学堂day11 │ 01-回顾.mp4 │ 02-回顾2.mp4 │ 03-超卖及锁失效问题.mp4 │ 04-事务边界事务失效问题.mp4 │ 05-简单分布式改造及问题.mp4 │ 06-Redisson使用.mp4 │ 07-aop例子.mp4 │ 08-通用的分布式锁组件.mp4 │ ├─天机学堂day12 │ 01-回顾-1.mp4 │ 02-回顾-2.mp4 │ 03-异步领卷的思路.mp4 │ 04-异步领卷-1.mp4 │ 05-异步领卷-
### 关于天机学堂 IT 培训课程的评价 天机学堂作为一个基于微服务架构的生产级在线教育项目,主要定位于成人非学历职业技能培训平台[^1]。该项目不仅提供了完整的业务逻辑复杂的系统设计,还涵盖了多种实际应用场景下的解决方案,例如学习辅助系统、考试系统以及电商类项目的促销优惠系统等。 #### 微服务开发中的实践价值 在天机学堂学习过程中,学员能够深入接触并掌握微服务开发的核心技能及其应用方案。这包括但不限于分布式事务处理、服务治理、负载均衡等方面的知识点。通过参与此类高仿真度的企业级项目实战训练,学员可以显著提高自身的工程能力解决复杂问题的能力[^3]。 #### 行业背景与发展前景 随着国家“双减”政策出台后传统K12领域受到重创的同时,职业教育市场却迎来了新的发展机遇期。据统计数据显示,在线职业培训需求量逐年攀升,并成为推动整个教育培训行业发展的重要力量之一。因此选择加入像天机学堂这样专注于成人技能培训方向上的机构进行深造无疑具有良好的就业潜力发展空间。 #### 学员反馈与自我提升建议 有学员表示,在完成类似天机学堂这样的综合性大型项目之后,他们对于软件开发的整体设计理念有了更深刻的理解;即使日常工作多集中于简单的增删改查(CRUD)操作上,但仍可以从中学到许多有助于个人成长的技术细节[^4]。 综上所述,参加天机学堂所提供的IT培训课程不仅可以帮助求学者获得扎实的专业基础理论知识外加丰富的实践经验积累,同时也为其未来职业生涯规划奠定了坚实的基础。 ```python # 示例代码展示如何模拟一个简单的服务注册发现机制 import random class ServiceRegistry: def __init__(self): self.services = {} def register(self, service_name, address): if service_name not in self.services: self.services[service_name] = [] self.services[service_name].append(address) def get_service(self, service_name): available_services = self.services.get(service_name, []) return random.choice(available_services) if available_services else None registry = ServiceRegistry() registry.register('payment-service', 'http://localhost:8080') print(registry.get_service('payment-service')) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值