实现课程加入课程表的接口
1. 接口分析:
当用户支付完成或者报名免费课程后,应该立刻将课程加入到课表中。这时就需要通过mq去发送消息通知学习微服务,将课程加入到课表中,同时在学习微服务中,创建监听的类去监听订单微服务发送过来的消息。
2.实现接口
1.找到发送mq消息的微服务的service
通过上述的分析可以知道,消息是从订单微服务中发送,因此,需要去订单微服务中去找到对应的发送消息的方法,如下:

由这段代码可以知道订单微服务发送的消息的交换机,消息队列和路由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.首先通过订单微服务去找到对应退款发消息的方法:

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去补充。
浙公网安备 33010602011771号