给自己复盘用的tjxt笔记day2

开发的流程

那为什么要先设计接口呢?原因有两点:

  • 第一:目前企业开发的模式往往是前后端分离,前后端并行开发。前端开发需要调用后端接口,后端需要开发接口返回数据给前端,要想并行开发,就必须有一套接口标准,前后端基于接口完成开发。

  • 第二:设计接口的过程就是分析业务的过程,弄清楚了业务的细节,更有助于我们设计数据库结构,开发接口功能。

通过产品原型和需求分析,对需要的接口进行了统计。

然后逐个对接口进行了分析,进行接口的设计。

接口设计的核心要素包括:

  • 请求方式

  • 请求路径

  • 请求参数格式

  • 返回值格式

分析完接口,定义相应的请求和返回实体。

设计数据结构

首先通过需求和接口分析明确表格中包含的信息字段

然后寻找数据关系去设计表格

为什么要创建分支

一般开发新功能都需要创建一个feature类型分支,不能在DEV分支直接开发,因此这里我们新建一个功能分支。

代码简化-MP的使用

基于创建的表格使用MP生成代码

代码优化-枚举

前面分析得到的字段里面有状态,如果每次编码都手写很容易写错,因此一般都会定义出枚举

实现接口功能

MQ监听器

在前面的接口分析中我理解MQ信息相当于添加课程的请求方法,调用接口方法实现功能

@Slf4j
@Component
@RequiredArgsConstructor
public class LessonChangeListener {

    private final ILearningLessonService lessonService;

    /**
     * 监听订单支付或课程报名的消息
     * @param order 订单信息
     */
    @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());
    }

调用Feign接口

因为需要调用课程的详细信息,所以需要远程调用

通过token获取登录用户

天机学堂是基于JWT实现登录的,登录信息就保存在请求头的token中。因此要获取当前登录用户,只要获取请求头,解析其中的token即可。

微服务定义拦截器获取用户信息

@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.尝试获取头信息中的用户信息
        String authorization = request.getHeader(JwtConstants.USER_HEADER);
        // 2.判断是否为空
        if (authorization == null) {
            return true;
        }
        // 3.转为用户id并保存到UserContext中
        try {
            Long userId = Long.valueOf(authorization);
            UserContext.setUser(userId);
            return true;
        } catch (NumberFormatException e) {
            log.error("用户身份信息格式不正确,{}, 原因:{}", authorization, e.getMessage());
            return true;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理用户信息
        UserContext.removeUser();
    }
}

ThreadLocal

在这个拦截器中,获取到用户信息后保存到了UserContext中,这是一个基于ThreadLocal的工具,可以确保不同的请求之间互不干扰,避免线程安全问题发生

文件流

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

课后作业

删除课表中课程

   @Override
    @Transactional
    public void deleteCourseFromLesson(Long userId, Long courseIds) {

        if (userId == null) {
            userId = UserContext.getUser();
        }
        // delete * from learning_lesson where user_id = #{userId} and course_id in (#{courseIds})
        remove(buildUserIdAndCourseIdWrapper(userId, courseIds));

    }

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

检查课程是否有效

  @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;
        }
        if(lesson.getStatus()==LessonStatus.EXPIRED){
            return null;
        }
        return lesson.getId();
    }

查询用户课表中指定课程状态

  @Override
    public LearningLessonVO LessonQuey(Long courseId) {
        // 1.获取当前登录的用户
        Long userId = UserContext.getUser();
        // 2.查询指定课程的
        LearningLesson lesson = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId,courseId)
                .one();
        if (lesson == null) {
            return null;
        }
        // 3.拷贝PO基础属性到VO
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        // 4.查询课程信息
        CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
        if (cInfo == null) {
            throw new BadRequestException("课程不存在");
        }
        vo.setCourseName(cInfo.getName());
        vo.setCourseCoverUrl(cInfo.getCoverUrl());
        vo.setSections(cInfo.getSectionNum());

        return vo;
    }

统计课程的学习人数

   @Override
    public Integer countLearningLessonByCourse(Long courseId) {
        Integer courseAmount = lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .count();
        return courseAmount;
    }

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值