Reids的过期Key监听

2021-11-05  本文已影响0人  lclandld

考试有一个功能:如果用户把浏览器关闭了,或者电脑断点了,这些异常行为就没办法给试卷及时的打分,所有就有了一个功能叫心跳监听,后端给一个接口,第一次打开的时候就创建这个Key并设置key的过期时间,然后前端每隔多少分钟调用一个后端的这个接口,如果key过期了,。后端都还没有调用,就表示前端的浏览器等出问题了(比如断网、浏览器关闭),就把考试的分数和考试的状态等更新进数据库中

这里遇到一个大问题就是我们的系统有租户后台以及管理员后台,将key存到redis中的时候,租户后台还没开始跑,管理员后台就将数据给已经运行一遍了,所以需要加一个锁,只要其中一个运行了,另外一个就不让再跑key过期的监听逻辑处理了,不然数据会不对,叠加

 /**
     * 监听考试页面是否被关掉
     *
     * @return success/false
     */
    @ApiOperation(value = " 监听考试页面是否被关掉。1min前端调用一次" + SPRINT16 + LICHUNLAN)
    @GetMapping("/checkExamination")
    public ResponseModel checkExamination(@RequestParam Long questionPaperInstanceId) {
        //redis检测用户是否正在考试
        try {
            redisKeyExpiredListener.checkExaminationHeartbeat(UserUtil.getCurrentTenantId(),UserUtil.getCurrentUser().getId(), questionPaperInstanceId);
        } catch (Exception ex) {
            log.error("记录用户是否正在考试时,redis出错", ex);
        }
        return ResponseHelper.succeed();
    }

设置一个10分钟到redis key

 /**
     * 前端每隔一定周期向后端发送播放记录。用于记录用户播放时长
     *
     * @param tenantId
     * @param userId
     * @param  questionPaperInstanceId
     */
    public void checkExaminationHeartbeat(Long tenantId, Long userId, Long questionPaperInstanceId) {
        StringBuffer sb = new StringBuffer("Listener")
                .append(Constant.COLON)
                .append(tenantId)
                .append(Constant.COLON)
                .append(userId)
                .append(Constant.COLON)
                .append(questionPaperInstanceId);
        String valForUserExam = sb.toString();
        String keyForUserExam = sb.append(Constant.COLON).append(RedisKeyExpiredListener.keyForExam).toString();
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        String preVal = opsForValue.get(keyForUserExam);
        log.info("检测考试心跳:key是{},value是:{}",keyForUserExam,valForUserExam);
        //将这个值设置到redis缓存中
        if (ComUtil.isEmpty(preVal)) {
            opsForValue.set(keyForUserExam,valForUserExam,Constant.GenericNumber.NUMBER_TEN,TimeUnit.MINUTES);
        }else{
            //设置过期时间为10min
            redisTemplate.expire(keyForUserExam,Constant.GenericNumber.NUMBER_TEN,TimeUnit.MINUTES);
        }
    }

redis监听过期key,好作自己的逻辑操作

/**
 * 试卷考试心跳
 */
@Slf4j
@Service
public class RedisKeyExpiredListener extends KeyExpirationEventMessageListener {
private static final String keyForExam= "keyForExam";

    @Autowired
    public RedisKeyExpiredListener(RedisMessageListenerContainer redisMessageListenerContainer) {
        super(redisMessageListenerContainer);
    }
/**
     * 接收过期消息
     *
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {

        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
        String patternString = new String(pattern, StandardCharsets.UTF_8);
        //学习时长过期的key末尾
        String studyDurationsSuffix = Constant.COLON + keyForDuration;
        String key = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("app:{}:callbackKey:{}",springApplicationName,key);
        log.info("pattern:{}:channel:{}",patternString,channel);
      //处理在线考试过期回调的问题
         if(key.contains(keyForExam)){
            processUserOffLineExam(key);
         }
    }

 /**
     * 处理考试设定时长过期的问题,过期了就需要设置分数为0,状态为4
     */
    @Transactional(rollbackFor = Exception.class)
    void processUserOffLineExam(String key){

        log.info("过期的考试Key是{}",key);
        String[] keyArray = key.split(Constant.COLON);
        String tenantId = keyArray[keyArray.length - 4];
        String userId = keyArray[keyArray.length - 3];
        String  questionPaperInstanceId= keyArray[keyArray.length - 2];
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(Constant.Redis.QUESTION_PAPER_EXAM+questionPaperInstanceId, questionPaperInstanceId, Constant.GenericNumber.NUMBER_ONE, TimeUnit.MINUTES);
        log.info("租户{}下的用户{}的试卷实例ID{}为,枷锁的标志是{}:",tenantId,userId,questionPaperInstanceId,lock);
        if(lock){
            //不存在表示已经过期了,需要将考试的状态设置为意外结束,并设置分数为0
            QuestionPaperInstance questionPaperInstance = questionPaperInstanceService.getById(Long.valueOf(questionPaperInstanceId));
            //第一次考试开始
            boolean isFirstStart = questionPaperInstance.getStatus().equals(Constant.QuestionPaperInstanceStatus.start.getCode());
            //是否设置了补考
            boolean isSettingMakeup = ComUtil.isNotEmpty(questionPaperInstance.getMakeupExamEndTime());
            //补考考试开始
            boolean isMakeupStart = questionPaperInstance.getStatus().equals(Constant.QuestionPaperInstanceStatus.makeUp_start.getCode());
            if(ComUtil.isNotEmpty(questionPaperInstance) && (isFirstStart||isMakeupStart)){
                //考试时长
                LocalDateTime now = LocalDateTime.now();
                Duration durationObj = Duration.between(questionPaperInstance.getStartTime(), now);
                Integer duration = Math.toIntExact(durationObj.toMinutes());
                questionPaperInstance.setDuration(duration);
                //第一次考试异常结束,不需要补考,分数计0,状态异常结束
                if(isFirstStart && !isSettingMakeup){
                    questionPaperInstance.setScore(Constant.GenericNumber.NUMBER_ZERO);
                    questionPaperInstance.setStatus(Constant.QuestionPaperInstanceStatus.exception_finish.getCode());
                }
                //第一次考试异常结束,需要补考就状态设置为补考未开始,分数计0,并发通知
                if(isFirstStart && isSettingMakeup){
                    questionPaperInstance.setScore(Constant.GenericNumber.NUMBER_ZERO);
                    questionPaperInstanceService.sendNotice(true,questionPaperInstance,Constant.GenericNumber.NUMBER_ZERO,Long.valueOf(userId));
                    questionPaperInstance.setStatus(Constant.QuestionPaperInstanceStatus.makeUp_init.getCode());
                }
                //补考开始异常结束
                if(isMakeupStart){
                    questionPaperInstance.setMakeupExamTime(LocalDateTime.now());
                    questionPaperInstance.setMakeupExamScore(Constant.GenericNumber.NUMBER_ZERO);
                    questionPaperInstance.setStatus(Constant.QuestionPaperInstanceStatus.exception_finish.getCode());
                }
                questionPaperInstance.setEndTime(now);
                //更新分数和状态
                questionPaperInstanceService.updateById(questionPaperInstance);
                try {
                    //调用任务逻辑
                    if (questionPaperInstance.getOrigin() == 0) {
                        //更新考试时长和分数
                        questionPaperStatisticsService.updateByPaperInfo(questionPaperInstance);
                        //迭代19发现的问题,只有第一次考试的时候才去更新试卷的进度,第二次补考的时候不更新
                        log.info("试卷心跳的几个状态值是:isFirstStart:{}",isFirstStart);
                        if(isFirstStart){
                            studyTaskService.completePaper(questionPaperInstance.getStudyTaskId(), questionPaperInstance.getQuestionPaperId(), questionPaperInstance.getUserId());
                        }
                    }
                } catch (Exception e) {
                    log.info("试卷心跳过期完成任务异常{}",e.getMessage());
                }
            }
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读