视频进度代码,延时队列方案

环境:springboot、mybatis-plus、redisson-starter、hutool

方案说明

提交视频进度,这个接口很高频率。是高频率的写。

这里主要方案如下

1.提交的进度先到redis进行保存,并且同时发送一个15秒的延时任务。

2.到了15s后,取出这个任务数据,和redis中的数据做比对。

2.1.如果进度数据一样的话,说明用户停止播放了视频,可以写库了。

这就是正常的流程了。

效果

前提代码

sql 复制代码
-- 创建视频进度表
DROP TABLE IF EXISTS `video_progress`;
CREATE TABLE IF NOT EXISTS `video_progress`
(
    `id`         BIGINT  PRIMARY KEY,
    `user_id`    BIGINT comment '用户id(关联)',
    `video_id`    BIGINT comment '视频id(关联)',
    `position`      DOUBLE comment '当前进度',
    `duration`      DOUBLE comment '视频总时长',
    `is_finish` tinyint(1) default 0 comment '是否完成',
    `update_time` datetime null on update CURRENT_TIMESTAMP comment '更新时间'
) comment '视频进度表';
java 复制代码
@TableName(value ="video_progress")
@Data
public class VideoProgress implements Serializable {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private Long userId;

    private Long videoId;

    private Double position;

    private Double duration;

    private Integer isFinish;

    private Date updateTime;

    private static final long serialVersionUID = 1L;
}
java 复制代码
public interface VideoProgressMapper extends BaseMapper<VideoProgress> {

}
java 复制代码
@Getter
@ToString
public class DelayedTask<T> implements Delayed, Serializable {

    private static final long serialVersionUID = 3018458065076640934L;
    private final T taskContent;

    private final Long triggerTime;

    /**
     * @param seconds 秒
     */
    public DelayedTask(T taskContent, Long seconds) {
        this.taskContent = taskContent;
        this.triggerTime = System.currentTimeMillis() + seconds * 1000;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return this.triggerTime.compareTo(((DelayedTask) o).triggerTime);
    }

}
java 复制代码
@Data
public class VideoSubmitRequest implements Serializable {

    private static final long serialVersionUID = -102757973917094785L;
    long videoId;
    long userId;
    double duration;
    double position;
}

核心代码

java 复制代码
@Slf4j
@SpringBootApplication
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
@MapperScan
public class VideoProgressApplication {

    private final StringRedisTemplate stringRedisTemplate;
    private final String VIDEO_PROGRESS_KEY = "video:progress";
    private final RedissonClient redissonClient;

    @PostMapping("/submit")
    public void submit(@RequestBody VideoSubmitRequest videoSubmitRequest) {
        // 1.校验参数后,直接写入到redis中
        var key = VIDEO_PROGRESS_KEY + ":" + videoSubmitRequest.userId;
        var value = JSONUtil.toJsonStr(videoSubmitRequest);
        var hashKey = String.valueOf(videoSubmitRequest.videoId);
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
        // 这里要设置过期时间,但是总是报StackOverFlow,为啥啊。
        // stringRedisTemplate.opsForHash().expire(key, Duration.ofSeconds(15), Collections.singletonList(hashKey));


        // 2.发布一个延时任务15s
        RBlockingDeque<DelayedTask<VideoSubmitRequest>> blockingDeque = redissonClient.getBlockingDeque("video-progress-delay-queue");
        RDelayedQueue<DelayedTask<VideoSubmitRequest>> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        delayedQueue.offer(new DelayedTask<>(videoSubmitRequest, 15L),15, TimeUnit.SECONDS);
        log.info("发送延时任务,{}",value);
    }

    public static void main(String[] args) {
        SpringApplication.run(VideoProgressApplication.class, args);

    }

}
java 复制代码
@Component
@RequiredArgsConstructor
@Slf4j
public class DelayQueueHandler implements CommandLineRunner {
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final VideoProgressMapper videoProgressMapper;

    private RBlockingDeque<DelayedTask<VideoSubmitRequest>> blockingDeque;
    private static volatile boolean RUNNING = true;

    @Override
    public void run(String... args) {
        blockingDeque = redissonClient.getBlockingDeque("video-progress-delay-queue");
        new Thread(() -> {
            while (RUNNING) {
                try {
                    // 1.延时任务结束,需要检查提交的数据和redis中的数据是否一致,如果不一致就可以写库了
                    var take = blockingDeque.take();
                    log.info("处理延时任务,{}",take);
                    var oldProgress = take.getTaskContent();
                    // 2.取出redis中的数据,做比对
                    var key = "video:progress:" + oldProgress.getUserId();
                    var hashKey = oldProgress.getVideoId();
                    var value = stringRedisTemplate.opsForHash().get(key, String.valueOf(hashKey));

                    if (value == null) {
                        continue;
                    }
                    // 2.1.从redis中取出最新的数据
                    var newProgress = JSONUtil.toBean((String) value, VideoSubmitRequest.class);
                    // 2.2.比对,这里不一致就直接结束
                    if (!NumberUtil.equals(oldProgress.getPosition(), newProgress.getPosition())) {
                        continue;
                    }

                    // 3.写库
                    var userId = newProgress.getUserId();
                    var videoId = newProgress.getVideoId();
                    var position = newProgress.getPosition();
                    var duration = newProgress.getDuration();

                    var videoProgress = videoProgressMapper.selectOne(Wrappers.lambdaQuery(VideoProgress.class)
                            .eq(VideoProgress::getUserId, userId)
                            .eq(VideoProgress::getVideoId, videoId));

                    if (videoProgress == null) {
                        videoProgress = new VideoProgress();
                        videoProgress.setUserId(userId);
                        videoProgress.setVideoId(videoId);
                        videoProgress.setDuration(duration);
                    }

                    videoProgress.setPosition(position);
                    if (position / duration > 0.8) {
                        videoProgress.setIsFinish(1);
                    }

                    videoProgressMapper.insertOrUpdate(videoProgress);
                    stringRedisTemplate.opsForHash().delete(key, String.valueOf(hashKey));

                } catch (Exception e) {
                    log.error("延时队列出错:{}", e);
                }
            }
        }).start();
    }

    @PreDestroy
    public void destroy() {
        RUNNING = false;
    }
}
相关推荐
wwwzhouhui2 小时前
2025年11月1日-AI 驱动教学革命:3 分钟生成专业级动画课件,还能导出视频 GIF!
人工智能·音视频·ai动画教学
sunsunyu032 小时前
视频转图片工具
python·音视频
王道长服务器 | 亚马逊云4 小时前
AWS + WordPress:中小型外贸独立站的理想组合
服务器·网络·云计算·音视频·aws
ftpeak6 小时前
《Rust MP4视频技术开发》第八章:生成MP4
开发语言·rust·音视频·mp4
wangdao121210 小时前
MP4视频播放问题
音视频
开开心心就好1 天前
电子报纸离线保存:一键下载多报PDF工具
网络·笔记·macos·pdf·word·音视频·phpstorm
无敌最俊朗@1 天前
视频容器(如 MP4)的作用:组织与同步
音视频
Black蜡笔小新1 天前
视频融合平台EasyCVR结合视频智能分析技术构建高空抛物智能监控系统,守护“头顶上的安全”
安全·音视频
EasyCVR1 天前
如何基于视频融合平台EasyCVR实现全域轨迹跟踪,构建主动式安防新体系?
音视频