StreamUtils

package com.redis.utils;

import com.SpringUtils;
import com.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.stream.Record;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StreamOperations;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * redis Stream类型数据操作Dao接口
 * @author muzhi
 * @author <a href="mailto:maming.zhong2526@nvxclouds.com">Muzhi Zhang</a>
 * @version 1.0.0
 * @date 2023-12-14 20:45
 */
@Slf4j
public class StreamUtils<T> {
    private final StringRedisTemplate stringRedisTemplate;
    private final StreamOperations<String,String, T> streamOperations;

    public StreamUtils() {
        this.stringRedisTemplate = SpringUtils.getBean(StringRedisTemplate.class);
        this.streamOperations = stringRedisTemplate.opsForStream();
        stringRedisTemplate.execute((RedisCallback) RedisConnection::scriptingCommands);
    }

    /**
     * 获取当前操作 StreamOperations
     * @return
     */
    public StreamOperations getStreamOperations() {
        return streamOperations;
    }

    /**
     *
     * @return
     */
    public StringRedisTemplate getStringRedisTemplate() {
        return stringRedisTemplate;
    }

    /**
     * 初始化stream和消费者组
     * @param key
     * @param group
     */
    public void initStream(String key, String group) {
        //判断key是否存在,如果不存在则创建
        boolean hasKey = true;
        if (StringUtils.isNotBlank(key)){
            hasKey = stringRedisTemplate.hasKey(key);
        }
        if(!hasKey){
            Map<String,T> map = new HashMap<>();
            map.put("field", (T) "value");
            String recordId = insertStreamAll(key, map);
            createGroup(key,group);
            //将初始化的值删除掉
            remove(key,recordId);

            log.debug("stream:{}-group:{} initialize success",key,group);
        }
    }

    /**
     * 添加消息到末尾
     * @param key
     * @param field
     * @param value
     * @return
     */
    public String insertStream(String key, String field, T value) {
        Map<String, T> content = new HashMap<>(1);
        content.put(field, value);
        return insertStreamAll(key, content);
    }

    /**
     * 批量添加消息到末尾
     * @param key
     * @param content
     * @return
     */
    public String insertStreamAll(String key, Map<String, T> content) {
        return streamOperations.add(key,content).getValue();
    }

    /**
     *  删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度
     *  消息存储在stream的节点下,删除时仅对消息做删除标记,当一个节点下的所有条目都被标记为删除时,销毁节点
     * @param key
     * @param recordIds
     * @return
     */
    public Long remove(String key, String... recordIds) {
        return streamOperations.delete(key,recordIds);
    }


    /**
     * 创建消费组
     * @param key
     * @param group
     * @return
     */
    public String createGroup(String key, String group) {
        return streamOperations.createGroup(key,group);
    }

    /**
     * 确认已消费
     * @param key
     * @param group
     * @param recordIds
     * @return
     */
    public Long ack(String key, String group, String... recordIds) {
        return streamOperations.acknowledge(key, group, recordIds);
    }

    /**
     * 确认已消费
     * @param group
     * @param record
     * @return
     */
    public Long ack(String group, Record<String, ?> record) {
        return streamOperations.acknowledge(group, record);
    }

    /**
     * 消息长度
     * @param key
     * @return
     */
    public Long len(String key) {
        return streamOperations.size(key);
    }

    /**
     * 从头开始读
     * @param key
     * @return
     */
    public List<MapRecord<String, String, T>> readByZero(String key) {
        return streamOperations.read(StreamOffset.fromStart(key));
    }

    /**
     * 从指定的ID开始读
     * @param key
     * @param recordId
     * @return
     */
    public List<MapRecord<String, String, T>> readById(String key, String recordId) {
        return streamOperations.read(StreamOffset.from(MapRecord.create(key, new HashMap<>(1)).withId(RecordId.of(recordId))));
    }

    /**
     * 读取pending 中未处理的数据
     * @param key
     * @param consumer
     * @return
     */
    public PendingMessages readWithPending(String key, Consumer consumer) {
        //从零到最大,10条数据
//        return streamOperations.pending(key, consumer, Range.closed("0", "+"), 10L);
        return streamOperations.pending(key, consumer);
    }

    /**
     * 读取pending 中未处理的数据
     * @param key
     * @param group
     * @return
     */
    public PendingMessagesSummary readWithPending(String key, String group) {
        return streamOperations.pending(key, group);
    }

    /**
     * 获取消费组信息
     * @param key
     * @param group
     * @return
     */
    public StreamInfo.XInfoConsumers getConsumers(String key, String group) {

        return streamOperations.consumers(key, group);
    }

    /**
     * 获取消息列表,会自动过滤已经删除的消息
     * @param key
     * @param rightOpen
     * @return
     */
    public List<MapRecord<String, String, T>> getMsgList(String key, Range<String> rightOpen) {
        return streamOperations.range(key, rightOpen);
    }

    /**
     * 从特定范围内的流中读取记录
     * @param key
     * @param startId
     * @param endId
     * @param count
     * @return
     */
    public List<MapRecord<String, String, T>> range(String key, RecordId startId, RecordId endId, Integer count) {

        return streamOperations .range(key,
            Range.from(Range.Bound.exclusive(startId.getValue())).to(Range.Bound.exclusive(startId.getValue())),
            RedisZSetCommands.Limit.limit().count(count));
    }
}

 

posted @ 2023-12-18 19:51  牧之丨  阅读(28)  评论(0编辑  收藏  举报