一、分页查询我的课表
按照Restful风格,查询请求应该使用GET
方式。
请求路径一般是资源名称,比如这里资源是课表,所以资源名可以使用lessons
,同时这里是分页查询,可以在路径后跟一个/page
,代表分页查询
请求参数,因为是分页查询,首先肯定要包含分页参数,一般有两个:
-
pageNo:页码
-
pageSize:每页大小
这里只有两个排序条件,没有过滤条件,因此加上两个排序字段即可:
-
sortBy:排序方式
-
isAsc:是否升序
定义好
-
统一的分页请求Query实体
-
统一的分页结果DTO实体
-
课表分页VO实体
@Override
public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
// 1.获取当前登录用户
Long userId = UserContext.getUser();
// 2.分页查询
// select * from learning_lesson where user_id = #{userId} order by latest_learn_time limit 0, 5
Page<LearningLesson> page = lambdaQuery()
.eq(LearningLesson::getUserId, userId) // where user_id = #{userId}
.page(query.toMpPage("latest_learn_time", false));
List<LearningLesson> records = page.getRecords();
if (CollUtils.isEmpty(records)) {
return PageDTO.empty(page);
}
// 3.查询课程信息
Map<Long, CourseSimpleInfoDTO> cMap = queryCourseSimpleInfoList(records);
// 4.封装VO返回
List<LearningLessonVO> list = new ArrayList<>(records.size());
// 4.1.循环遍历,把LearningLesson转为VO
for (LearningLesson r : records) {
// 4.2.拷贝基础属性到vo
LearningLessonVO vo = BeanUtils.copyBean(r, LearningLessonVO.class);
// 4.3.获取课程信息,填充到vo
CourseSimpleInfoDTO cInfo = cMap.get(r.getCourseId());
vo.setCourseName(cInfo.getName());
vo.setCourseCoverUrl(cInfo.getCoverUrl());
vo.setSections(cInfo.getSectionNum());
list.add(vo);
}
return PageDTO.of(page, list);
}
private Map<Long, CourseSimpleInfoDTO> queryCourseSimpleInfoList(List<LearningLesson> records) {
// 3.1.获取课程id
Set<Long> cIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
// 3.2.查询课程信息
List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(cIds);
if (CollUtils.isEmpty(cInfoList)) {
// 课程不存在,无法添加
throw new BadRequestException("课程信息不存在!");
}
// 3.3.把课程集合处理成Map,key是courseId,值是course本身
Map<Long, CourseSimpleInfoDTO> cMap = cInfoList.stream()
.collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
return cMap;
}
第一段代码是一个方法queryMyLessons,用于查询当前登录用户的学习课程信息,并返回封装好的LearningLessonVO对象列表。方法逻辑包括: 1. 获取当前登录用户的ID。 2. 进行分页查询学习课程记录。 3. 调用queryCourseSimpleInfoList方法查询学习课程对应的课程简要信息。 4. 封装学习课程记录和课程简要信息到LearningLessonVO对象列表中。
第二段代码实现了将学习课程记录和对应课程简要信息关联起来,并返回一个包含完整信息的LearningLessonVO对象列表的功能。
二、添加课程到课表
当用户支付完成或者报名免费课程后,应该立刻将课程加入到课表中。交易服务会通过MQ通知学习服务,我们需要查看交易服务的源码,查看MQ通知的消息格式,来确定监听消息的格式。
private List<CourseSimpleInfoDTO> getOnShelfCourse(List<Long> courseIds) {
// 1.查询课程
List<CourseSimpleInfoDTO> courseInfos = courseClient.getSimpleInfoList(courseIds);
LocalDateTime now = LocalDateTime.now();
// 2.判断状态
for (CourseSimpleInfoDTO courseInfo : courseInfos) {
// 2.1.检查课程是否上架
if(!CourseStatus.SHELF.equalsValue(courseInfo.getStatus())){
throw new BizIllegalException(TradeErrorInfo.COURSE_NOT_FOR_SALE);
}
// 2.2.检查课程是否过期
if(courseInfo.getPurchaseEndTime().isBefore(now)){
throw new BizIllegalException(TradeErrorInfo.COURSE_EXPIRED);
}
}
return courseInfos;
}
@Override
@Transactional
public PlaceOrderResultVO enrolledFreeCourse(Long courseId) {
Long userId = UserContext.getUser();
// 1.查询课程信息
List<Long> cIds = CollUtils.singletonList(courseId);
List<CourseSimpleInfoDTO> courseInfos = getOnShelfCourse(cIds);
if (CollUtils.isEmpty(courseInfos)) {
// 课程不存在
throw new BizIllegalException(TradeErrorInfo.COURSE_NOT_EXISTS);
}
CourseSimpleInfoDTO courseInfo = courseInfos.get(0);
if(!courseInfo.getFree()){
// 非免费课程,直接报错
throw new BizIllegalException(TradeErrorInfo.COURSE_NOT_FREE);
}
// 2.创建订单
Order order = new Order();
// 2.1.基本信息
order.setUserId(userId);
order.setTotalAmount(0);
order.setDiscountAmount(0);
order.setRealAmount(0);
order.setStatus(OrderStatus.ENROLLED.getValue());
order.setFinishTime(LocalDateTime.now());
order.setMessage(OrderStatus.ENROLLED.getProgressName());
// 2.2.订单id
Long orderId = IdWorker.getId(order);
order.setId(orderId);
// 3.订单详情
OrderDetail detail = packageOrderDetail(courseInfo, order, 0);
// 4.写入数据库
saveOrderAndDetails(order, CollUtils.singletonList(detail));
// 5.发送MQ消息,通知报名成功
rabbitMqHelper.send(
MqConstants.Exchange.ORDER_EXCHANGE,
MqConstants.Key.ORDER_PAY_KEY,
OrderBasicDTO.builder()
.orderId(orderId)
.userId(userId)
.courseIds(cIds)
.finishTime(order.getFinishTime())
.build()
);
// 6.返回vo
return PlaceOrderResultVO.builder()
.orderId(orderId)
.payAmount(0)
.status(order.getStatus())
.build();
}
由此,我们可以得知发送消息的Exchange、RoutingKey,以及消息体。消息体的格式是OrderBasicDTO,包含四个字段:
-
orderId:订单id
-
userId:下单的用户id
-
courseIds:购买的课程id集合
-
finishTime:支付完成时间
实体类
@Data
@Builder
public class OrderBasicDTO {
/**
* 订单id
*/
private Long orderId;
/**
* 下单用户id
*/
private Long userId;
/**
* 下单的课程id集合
*/
private List<Long> courseIds;
/**
* 订单完成时间
*/
private LocalDateTime finishTime;
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "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 order){
// 1.健壮性处理
if(order == null || order.getUserId() == null || CollUtils.isEmpty(order.getCourseIds())){
// 数据有误,无需处理
log.error("接收到MQ消息有误,订单数据为空");
return;
}
// 2.添加课程
log.debug("监听到用户{}的订单{},需要添加课程{}到课表中", order.getUserId(), order.getOrderId(), order.getCourseIds());
lessonService.addUserLessons(order.getUserId(), order.getCourseIds());
}
@Override
@Transactional
public void addUserLessons(Long userId, List<Long> courseIds) {
// 1.查询课程有效期
List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
if (CollUtils.isEmpty(cInfoList)) {
// 课程不存在,无法添加
log.error("课程信息不存在,无法添加到课表");
return;
}
// 2.循环遍历,处理LearningLesson数据
List<LearningLesson> list = new ArrayList<>(cInfoList.size());
for (CourseSimpleInfoDTO cInfo : cInfoList) {
LearningLesson lesson = new LearningLesson();
// 2.1.获取过期时间
Integer validDuration = cInfo.getValidDuration();
if (validDuration != null && validDuration > 0) {
LocalDateTime now = LocalDateTime.now();
lesson.setCreateTime(now);
lesson.setExpireTime(now.plusMonths(validDuration));
}
// 2.2.填充userId和courseId
lesson.setUserId(userId);
lesson.setCourseId(cInfo.getId());
list.add(lesson);
}
// 3.批量新增
saveBatch(list);
}