当前位置: 首页 > article >正文

天机学堂笔记1-网关拦截器获取用户信息保存到ThreadLocal

@FeignClient(contextId = "course", value = "course-service")
public interface CourseClient {

    /**
     * 根据老师id列表获取老师出题数据和讲课数据
     * @param teacherIds 老师id列表
     * @return 老师id和老师对应的出题数和教课数
     */
    @GetMapping("/course/infoByTeacherIds")
    List<SubNumAndCourseNumDTO> infoByTeacherIds(@RequestParam("teacherIds") Iterable<Long> teacherIds);

    /**
     * 根据小节id获取小节对应的mediaId和课程id
     *
     * @param sectionId 小节id
     * @return 小节对应的mediaId和课程id
     */
    @GetMapping("/course/section/{id}")
    SectionInfoDTO sectionInfo(@PathVariable("id") Long sectionId);

    /**
     * 根据媒资Id列表查询媒资被引用的次数
     *
     * @param mediaIds 媒资id列表
     * @return 媒资id和媒资被引用的次数的列表
     */
    @GetMapping("/course/media/useInfo")
    List<MediaQuoteDTO> mediaUserInfo(@RequestParam("mediaIds") Iterable<Long> mediaIds);

    /**
     * 根据课程id查询索引库需要的数据
     *
     * @param id 课程id
     * @return 索引库需要的数据
     */
    @GetMapping("/course/{id}/searchInfo")
    CourseSearchDTO getSearchInfo(@PathVariable("id") Long id);

    /**
     * 根据课程id集合查询课程简单信息
     * @param ids id集合
     * @return 课程简单信息的列表
     */
    @GetMapping("/courses/simpleInfo/list")
    List<CourseSimpleInfoDTO> getSimpleInfoList(@RequestParam("ids") Iterable<Long> ids);

    /**
     * 根据课程id,获取课程、目录、教师信息
     * @param id 课程id
     * @return 课程信息、目录信息、教师信息
     */
    @GetMapping("/course/{id}")
    CourseFullInfoDTO getCourseInfoById(
            @PathVariable("id") Long id,
            @RequestParam(value = "withCatalogue", required = false) boolean withCatalogue,
            @RequestParam(value = "withTeachers", required = false) boolean withTeachers
    );
}

day02-我的课表

支付或报名课程后,监听到MQ通知,将课程加入课表。
除此以外,如果用户退款,也应该删除课表中的课程,这里同样是通过MQ通知来实现:

CREATE TABLE learning_lesson (
  id bigint NOT NULL COMMENT '主键',
  user_id bigint NOT NULL COMMENT '学员id',
  course_id bigint NOT NULL COMMENT '课程id',
  status tinyint DEFAULT '0' COMMENT '课程状态,0-未学习,1-学习中,2-已学完,3-已失效',
  week_freq tinyint DEFAULT NULL COMMENT '每周学习频率,每周3天,每天2节,则频率为6',
  plan_status tinyint NOT NULL DEFAULT '0' COMMENT '学习计划状态,0-没有计划,1-计划进行中',
  learned_sections int NOT NULL DEFAULT '0' COMMENT '已学习小节数量',
  latest_section_id bigint DEFAULT NULL COMMENT '最近一次学习的小节id',
  latest_learn_time datetime DEFAULT NULL COMMENT '最近一次学习的时间',
  create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  expire_time datetime NOT NULL COMMENT '过期时间',
  update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (id),
  UNIQUE KEY idx_user_id (user_id,course_id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='学生课表';

在这里插入图片描述

package com.tianji.learning.mq;

import cn.hutool.core.collection.CollUtil;
import com.tianji.api.dto.trade.OrderBasicDTO;
import com.tianji.common.constants.MqConstants;
import com.tianji.learning.service.ILearningLessonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
@Slf4j
@RequiredArgsConstructor  
public class LessonChangeListener {

    private final ILearningLessonService lessonService;


    /***
     * MQ消息发送相关代码:
     *         rabbitMqHelper.send(
     *                 MqConstants.Exchange.ORDER_EXCHANGE, // Exchange
     *                 MqConstants.Key.ORDER_PAY_KEY,    // Key
     *                 OrderBasicDTO.builder()
     *                         .orderId(orderId)
     *                         .userId(userId)
     *                         .courseIds(cIds)
     *                         .finishTime(order.getFinishTime())
     *                         .build()
     *         );
     *
     * @param dto 接受的参数类型为OrderBasicDTO
     */
    @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "learning.lesson.pay.queue", durable = "true"),
            exchange = @Exchange(value = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC),
            key = MqConstants.Key.ORDER_PAY_KEY))
    public void onMsg(OrderBasicDTO dto) {
        log.info("LessonChangeListener接收消息,用户{},添加课程{}", dto.getUserId(), dto.getCourseIds());
        // 校验
        if (dto.getUserId() == null
                || dto.getOrderId() == null
                || CollUtil.isEmpty(dto.getCourseIds())) {
            // 这里是接受MQ消息,中断即可,若抛异常,则自动重试
            return;
        }
        // 保存课程到课表
        lessonService.addUserLesson(dto.getUserId(),dto.getCourseIds());
    }


    /**
     * 当用户退款成功时,取消相应课程
     */
    @RabbitListener(bindings = @QueueBinding(value = @Queue(value = "learning.lesson.refund.queue ",durable = "true"),
    exchange = @Exchange(value = MqConstants.Exchange.ORDER_EXCHANGE,type = ExchangeTypes.TOPIC ),
    key = MqConstants.Key.ORDER_REFUND_KEY))
    public void receiveMsg(OrderBasicDTO dto){
        log.info("LessonChangeListener接收消息,用户{},取消课程{}", dto.getUserId(), dto.getCourseIds());
        // 校验
        if (dto.getUserId() == null
                || dto.getOrderId() == null
                || CollUtil.isEmpty(dto.getCourseIds())) {
            // 这里是接受MQ消息,中断即可,若抛异常,则会开启重试
            return;
        }
        // 从课表中删除课程
        lessonService.deleteLessionById(dto.getUserId(),dto.getCourseIds());
    }
}

添加课程

package com.tianji.learning.service.impl;

@SuppressWarnings("ALL")
@Service
@RequiredArgsConstructor
@Slf4j
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;

    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {
        // 1.查询课程有效期 通过Feign远程调用课程服务,得到课程信息
        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);
    }
}

MQ

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在加入课表以后,用户就可以在个人中心查看到这些课程:
因此,这里就需要第二个接口

分页查询个人课程

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
分页:

在这里插入图片描述

用户信息存到ThreadLocal

jwt:头部+载体+签名
在这里插入图片描述

网关判断权限
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // 1.获取请求request信息
    ServerHttpRequest request = exchange.getRequest();
    String method = request.getMethodValue();
    String path = request.getPath().toString();
    String antPath = method + ":" + path;

    // 2.判断请求路径是否在默认不拦截的路径中
    if(isExcludePath(antPath)){
        // 直接放行
        return chain.filter(exchange);
    }

    // 3.尝试获取用户信息
    List<String> authHeaders = exchange.getRequest().getHeaders().get(AUTHORIZATION_HEADER);
    String token = authHeaders == null ? "" : authHeaders.get(0);
    R<LoginUserDTO> r = authUtil.parseToken(token);

    // 4.如果用户是登录状态,尝试更新请求头,传递用户信息
    if(r.success()){
        exchange.mutate()
                .request(builder -> builder.header(USER_HEADER, r.getData().getUserId().toString()))
                //验证通过后将请求头中的"authorization"改成"user_info"
                .build();
    }

    // 5.校验权限
    authUtil.checkAuth(antPath, r);

    // 6.放行
    return chain.filter(exchange);
}

private boolean isExcludePath(String antPath) {
    for (String pathPattern : authProperties.getExcludePath()) {
        if(antPathMatcher.match(pathPattern, antPath)){
            return true;
        }
    }
    return false;
}
拦截器

authsdk.resource.interceptors下:

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.判断是否为空(非法用户 也让访问但是threadlocal中不保存)
        if (authorization == null) {
            return true;
        }
        // 3.转为用户id并保存
        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();
    }
}
package com.tianji.common.utils;

public class UserContext {
    private static final ThreadLocal<Long> TL = new ThreadLocal<>();

    /**
     * 保存用户信息
     */
    public static void setUser(Long userId){
        TL.set(userId);
    }

    /**
     * 获取用户
     */
    public static Long getUser(){
        return TL.get();
    }

    /**
     * 移除用户信息
     */
    public static void removeUser(){
        TL.remove();
    }
}

分页查询我的课程

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

作业

检查课程是否有效

public Long isLessonValid(Long courseId) {
        Long userId = UserContext.getUser();
        // 获取当前登录用户的userId
        // 校验用户课表中是否有该课程
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId).eq(LearningLesson::getCourseId, courseId).one();
        // 用户课表中没有该课程
        if (learningLesson == null) {
            // throw new BizIllegalException("该课程不在用户课表中");
            return null;
        }
        // 校验课程状态是否有效,即是否已过期,根据过期时间字段是否大于当前时间进行判断
        LocalDateTime expireTime = learningLesson.getExpireTime();
        // 当前时间晚于过期时间,已过期
        if (expireTime != null && LocalDateTime.now().isAfter(expireTime)) {
            // throw new BizIllegalException("该课程已过期");
            return null;
        }
        return learningLesson.getId();
    }

根据id查询指定课程的学习状态

  • 对于已经购买的课程:展示为马上学习,并且显示学习的进度、有效期
  • 对于未购买的课程:展示为立刻购买或加入购物车
public LearningLessonVO getLessonInfo(Long courseId) {
        // 获取当前登录用户的userId
        Long userId = UserContext.getUser();
        // 校验用户课表中是否有该课程
        LearningLesson learningLesson = this.lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId).one();
        // 用户课表中没有该课程
        if (learningLesson == null) {
            // throw new BizIllegalException("该课程不在用户课表中");
            return null;
        }
        // 封装数据到vo
        LearningLessonVO learningLessonVO = LearningLessonVO.builder().id(learningLesson.getId())
                .courseId(learningLesson.getCourseId())
                .status(learningLesson.getStatus())
                .learnedSections(learningLesson.getLearnedSections())
                .createTime(learningLesson.getCreateTime())
                .expireTime(learningLesson.getExpireTime())
                .planStatus(learningLesson.getPlanStatus())
                .build();

        return learningLessonVO;
    }

public LearningLessonVO now() {
        // 获取当前登录用户
        Long userId = UserContext.getUser();
        if (userId == null) {
            throw new BizIllegalException("用户未登录");
        }
        // 查询当前用户最近学习课表,降序排序取第一条, status为1表示学习中
        LearningLesson learningLesson = this.lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, 1)
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1 ").one();
        if (learningLesson == null) {
            return null;
        }
        // 查询当前用户报名的课程数
        Integer courseAmount = this.lambdaQuery().eq(LearningLesson::getUserId, userId).count();
        // feign远程调用查询相关课程的课程名、封面url等
        CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(learningLesson.getCourseId(), false, false);
        if (Objects.isNull(courseInfo)) {
            throw new BizIllegalException("课程不存在");
        }
        // feign远程调用查询相关小节的小节名称,小节编号
        List<CataSimpleInfoDTO> catalogueInfoList = catalogueClient.batchQueryCatalogue(List.of(learningLesson.getLatestSectionId()));
        if (CollUtil.isEmpty(catalogueInfoList)) {
            throw new BizIllegalException("最新学习小节不存在");
        }
        // 传参的小节id只有一个,所以可直接使用下标0
        CataSimpleInfoDTO catalogueInfo = catalogueInfoList.get(0);
        // 将po数据封装到vo
        LearningLessonVO learningLessonVO = new LearningLessonVO();
        BeanUtil.copyProperties(learningLesson, learningLessonVO);
        learningLessonVO.setCourseAmount(courseAmount); // 课程数量
        learningLessonVO.setCourseName(courseInfo.getName());   // 最近学习课程名称
        learningLessonVO.setCourseCoverUrl(courseInfo.getCoverUrl());   // 最近学习课程封面
        learningLessonVO.setSections(courseInfo.getSectionNum());   // 最近学习课程的章节数
        // 最近学习的小节id和小节名称
        learningLessonVO.setLatestSectionName(catalogueInfo.getName());
        learningLessonVO.setLatestSectionIndex(catalogueInfo.getCIndex());
        // 返回封装的vo
        return learningLessonVO;

    }

DAY3

学习计划和进度
在这里插入图片描述
保存用户播放到哪里:
learn_lesson有latest_section_id bigint DEFAULT NULL COMMENT ‘最近一次学习的小节id’,

CREATE TABLE IF NOT EXISTS `learning_record` (
  `id` bigint NOT NULL COMMENT '学习记录的id',
  `lesson_id` bigint NOT NULL COMMENT '对应课表的id',
  `section_id` bigint NOT NULL COMMENT '对应小节的id',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `moment` int DEFAULT '0' COMMENT '视频的当前观看时间点,单位秒',
  `finished` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否完成学习,默认false',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '第一次观看时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间(最近一次观看时间)',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_update_time` (`update_time`) USING BTREE,
  KEY `idx_user_id` (`user_id`) USING BTREE,
  KEY `idx_lesson_id` (`lesson_id`,`section_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='学习记录表';

DTO:接收前端参数或者返回时候 用的

public class LearningRecordFormDTO {

    @ApiModelProperty("小节类型:1-视频,2-考试")
    @NotNull(message = "小节类型不能为空")
    @EnumValid(enumeration = {1, 2}, message = "小节类型错误,只能是:1-视频,2-考试")
    private SectionType sectionType;

    @ApiModelProperty("课表id")
    @NotNull(message = "课表id不能为空")
    private Long lessonId;

    @ApiModelProperty("对应节的id")
    @NotNull(message = "节的id不能为空")
    private Long sectionId;

    @ApiModelProperty("视频总时长,单位秒")
    private Integer duration;

    @ApiModelProperty("视频的当前观看时长,单位秒,第一次提交填0")
    private Integer moment;


    @ApiModelProperty("提交时间")
    private LocalDateTime commitTime;
}

想循环引用的话,不用注入service,可以注入它的下一层Mapper

查询学习记录

public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        Long userId = UserContext.getUser();
        // 根据用户userId和课程courseId获取最近学习的小节id和课表id
        LearningLesson learningLesson = learningLessonService.lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .eq(LearningLesson::getUserId, userId).one();

        if (Objects.isNull(learningLesson)) {
            throw new BizIllegalException("该课程未加入课表");
        }
        // 根据课表id获取学习记录
        List<LearningRecord> learningRecordList = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, learningLesson.getId()).list();
        // copyToList有判空校验,不再赘余
        List<LearningRecordDTO> learningRecordDTOList = BeanUtil.copyToList(learningRecordList, LearningRecordDTO.class);

        LearningLessonDTO learningLessonDTO = new LearningLessonDTO();
        learningLessonDTO.setId(learningLesson.getId());
        learningLessonDTO.setLatestSectionId(learningLesson.getLatestSectionId());
        learningLessonDTO.setRecords(learningRecordDTOList);
        return learningLessonDTO;

    }

提交学习记录

保存用户播放到哪里,续播

参数校验:validator

import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;

在这里插入图片描述
续播如何精确到分钟秒的? 有上一次学习时间
.set(!finished, LearningLesson::getLatestLearnTime, recordDTO.getCommitTime())

创建学习计划

在这里插入图片描述

首先,要做到切换设备后还能续播,用户的播放进度必须保存在服务端,而不是客户端。
其次,用户突然断开或者切换设备,续播的时间误差不能超过30秒,那播放进度的记录频率就需要比较高。我们会在前端每隔15秒就发起一次心跳请求,提交最新的播放进度,记录到服务端。这样用户下一次续播时直接读取服务端的播放进度,就可以将时间误差控制在15秒左右。

分页查询我的学习计划

/**
     * 分页查询我的学习计划
     *
     * @param pageQuery 分页参数
     */
    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery pageQuery) {
        Long userId = UserContext.getUser();
        if (Objects.isNull(userId)) {
            throw new BizIllegalException("用户未登录");
        }
        // 查询用户正在进行的课表
        Page<LearningLesson> learningLessonPage = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        // 判断用户是否有正在上的课
        if (CollUtil.isEmpty(learningLessonPage.getRecords())) {
            // 返回空数据
            LearningPlanPageVO emptyVO = new LearningPlanPageVO();
            emptyVO.setTotal(0L);
            emptyVO.setPages(0L);
            emptyVO.setList(Collections.emptyList());
            return emptyVO;
        }
        // TODO 实现本周学习积分,暂未实现,默认0
        // 查询课表相关的课程信息并封装到Map
        Map<Long, CourseSimpleInfoDTO> simpleInfoDTOMap = getLongCourseSimpleInfoDTOMap(learningLessonPage);
        // 封装到VO
        return getPlanPageVO(learningLessonPage, simpleInfoDTOMap);
    }
/**
     * 数据封装
     */
private LearningPlanPageVO getPlanPageVO(Page<LearningLesson> learningLessonPage, Map<Long, CourseSimpleInfoDTO> simpleInfoDTOMap) {
    // 遍历课表
    List<LearningPlanVO> learningPlanVOList = learningLessonPage.getRecords().stream().map(learningLesson -> {
        // 从课程map中取出相应的课程信息
        CourseSimpleInfoDTO courseSimpleInfoDTO = simpleInfoDTOMap.get(learningLesson.getCourseId());

        //LearningPlanVO learningPlanVO = BeanUtils.copyBean(learningLesson, LearningPlanVO.class);
        LearningPlanVO learningPlanVO = LearningPlanVO.builder()
                .courseId(learningLesson.getCourseId()) // 课程id
                .weekFreq(learningLesson.getWeekFreq())     // 本周计划学习数量
                .learnedSections(learningLesson.getLearnedSections())   // 已学习小节数量
                .latestLearnTime(learningLesson.getLatestLearnTime())     // 最近一次学习时间
                .build();
        if (courseSimpleInfoDTO != null) {
            // 赋值课程名和总小节数量属性
            learningPlanVO.setCourseName(courseSimpleInfoDTO.getName());    // 课程名
            learningPlanVO.setSections(courseSimpleInfoDTO.getSectionNum());    // 课程总小节数量
        }
        // 查询该课程本周已学完的小节数
        LocalDate now = LocalDate.now();
        LocalDateTime weekBeginTime = DateUtils.getWeekBeginTime(now);
        LocalDateTime weekEndTime = DateUtils.getWeekEndTime(now);
        // 避免循环依赖,用Mapper不用service
        // 查询该课程本周已学习的小节数量:即查一周内该用户该课程有多少条学习记录
        Integer weekLearnedSections = learningRecordMapper.getWeekLearnedSections(learningLesson.getId(),
                weekBeginTime,weekEndTime);
        // 封装到planVO
        learningPlanVO.setWeekLearnedSections(weekLearnedSections);
        return learningPlanVO;
    }).collect(Collectors.toList());
    // 累加计算本周计划完成的小节数和已学完的小节数量
    Integer weekFinished = 0;   // 本周已学完小节数量
    Integer weekTotalPlan = 0;  // 本周计划学习小节数量
    for (LearningPlanVO learningPlanVO : learningPlanVOList) {
        weekFinished += learningPlanVO.getWeekLearnedSections();
        weekTotalPlan += learningPlanVO.getWeekFreq();
    }
    LearningPlanPageVO planPageVO = LearningPlanPageVO.builder()
            .weekFinished(weekFinished)
            .weekTotalPlan(weekTotalPlan)
            // TODO 学习积分暂为0
            .weekPoints(0)
            .build();
    return planPageVO.pageInfo(learningLessonPage.getTotal(), learningLessonPage.getPages(), learningPlanVOList);
}

/**
 * 查询课表相关的课程信息并封装到Map
 */
private Map<Long, CourseSimpleInfoDTO> getLongCourseSimpleInfoDTOMap(Page<LearningLesson> learningLessonPage) {
    List<Long> courseIds = learningLessonPage.getRecords().stream()
            .map(LearningLesson::getCourseId).collect(Collectors.toList());
    List<CourseSimpleInfoDTO> simpleInfoList = courseClient.getSimpleInfoList(courseIds);
    // 校验课表相关的课程信息
    if (CollUtil.isEmpty(simpleInfoList)) {
        throw new BizIllegalException("未查询到课表中相关课程");
    }
    // 封装为map,方便后面取出,空间换时间
    Map<Long, CourseSimpleInfoDTO> simpleInfoDTOMap = simpleInfoList.stream()
            .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, courseSimpleInfoDTO -> courseSimpleInfoDTO));
    return simpleInfoDTOMap;
}

基础知识

使用 Builder 模式可以让对象的创建更加清晰和灵活,避免了传统构造函数参数过多时的复杂性和可读性问题。在代码中使用 .builder() 方法是一种简洁的方式来创建对象,同时可以提高代码的可维护性和可扩展性。

 LearningPlanPageVO planPageVO = LearningPlanPageVO.builder()
                .weekFinished(weekFinished)
                .weekTotalPlan(weekTotalPlan)
                .weekPoints(0)
                .build();
// LearningPlanVO learningPlanVO = BeanUtils.copyBean(learningLesson, LearningPlanVO.class);
LearningPlanVO learningPlanVO = LearningPlanVO.builder()
         .courseId(learningLesson.getCourseId()) // 课程id
         .weekFreq(learningLesson.getWeekFreq())     // 本周计划学习数量
         .learnedSections(learningLesson.getLearnedSections())   // 已学习小节数量
         .latestLearnTime(learningLesson.getLatestLearnTime())     // 最近一次学习时间
         .build();

3.1.课程过期​
编写一个SpringTask定时任务,定期检查learning_lesson表中的课程是否过期,如果过期则将课程状态修改为已过期。​
启动类上加@EnableScheduling // 开启定时任务​
task上加@Scheduled(cron = “0 0 3 1 * ?”) //秒分时日月周年​

3.2.方案思考​
思考题:思考一下目前提交学习记录功能可能存在哪些问题?有哪些可以改进的方向?​

在这里插入图片描述


http://www.kler.cn/a/507402.html

相关文章:

  • 从AI原理到模型演进及代码实践 的学习二
  • 回归预测 | MATLAB实SVM支持向量机多输入单输出回归预测
  • Unity-Mirror网络框架-从入门到精通之RigidbodyPhysics示例
  • 内存与缓存:保姆级图文详解
  • Go Ebiten小游戏开发:贪吃蛇
  • 单片机存储器和C程序编译过程
  • 从RNN到Transformer:生成式AI技术演变与未来展望
  • 【深度学习】Pytorch:导入导出模型参数
  • python mysql库的三个库mysqlclient mysql-connector-python pymysql如何选择,他们之间的区别
  • 【Linux】打破Linux神秘的面纱
  • 西门子【Library of Basic Controls (LBC)基本控制库”(LBC) 提供基本控制功能】
  • 神经网络基础-正则化方法
  • 机器学习-常用的三种梯度下降法
  • CSS 样式 margin:0 auto; 详细解读
  • Jackson 中的多态类型支持:@JsonTypeInfo 和 @JsonSubTypes 使用技巧
  • 蓝桥杯刷题第四天——字符排序
  • 基于智能物联网的肉鸡舍控制器:设计、实施、性能评估与优化
  • 个人vue3-学习笔记
  • 服务器数据恢复—EMC存储POOL中数据卷被删除的数据恢复案例
  • Qt类的提升(Python)
  • 大模型赋能医疗项目,深兰科技与武汉协和医院达成合作
  • deepin-如何在 ArchLinux 发行版上安装 DDE 桌面环境
  • 老centos7 升级docker.io为docker-ce 脚本
  • 【GIS操作】使用ArcGIS Pro进行海图的地理配准(附:墨卡托投影对比解析)
  • 七大排序算法
  • 网络协议基础--IP协议