2

分享一个网上搜不到的「Redis」实现「聊天回合制」的方案

 1 year ago
source link: https://www.cnblogs.com/fulongyuanjushi/p/16388264.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

分享一个网上搜不到的「Redis」实现「聊天回合制」的方案 - 福隆苑居士 - 博客园

为什么说网上搜不到,因为关于聊天回合制的方案作者本人快把百度搜秃噜了也没找到,好在最终是公司一个关系不错的大佬帮提供了点思路,最终作者将其完整实现了出来。


分享出来大家可以收藏,万一你哪天也碰到这样的需求,可不就节省大把时间了吗。

先说下我这边的场景,读过我文章的同好都知道,我是做互联网医疗行业的,我们的项目中是包含聊天功能的,我们服务的对象主要是医院的医生,患者在网上找医生问诊时,往往会出现不停问的情况。


医生目前唯一的做法是自己结束这个咨询,或等待系统自动结束,这就带来了一个问题,不管是系统结束还是医生手动结束,患者都喜欢投诉和打差评,导致医生不敢擅自结束,问烦了又不好不回复,不回复也要被投诉。


最终聊天回合制这个需求就摆出来了,主动告诉患者我们的聊天是有回合的,所以你要一次问清楚,回合数满了我们不会再回复,如果患者硬要投诉,医生也可以说,这是做这个产品的公司自己设定的。


结下来就是,我们要把锅端好。


实际上,聊天回合制的诞生,基本上都和这个场景的诉求类似,为了减少用户频繁且无休止的咨询。

结合redis能够很好的实现聊天回合制,当然也可以直接通过数据库来实现,但显然redis操作更简单性能更优越。

总体思路如下:

1)、redis中存储两个key,一个是表示对象,声明为chat-who:consultId,value为对象标识,比如这里就是医生和患者,医生用D标识,患者用P标识;另一个key是表示回合数,声明为chat-num:consultId,value就是当前回合数。这里的consultId是动态的,表示这个咨询的id,可以根据自己的业务来定;


2)、这两个key的过期时间我们都定为2天,具体过期时间要根据自己业务规则来适配;


3)、我们在特定的位置进行初始化,只要是进入聊天之前都可以,比如这里的场景,就是患者发起咨询成功后才开始聊天,我们就在发起成功后的方法中初始化聊天回合数为默认值6个回合,这个默认值还可以做成配置的形式进行动态读取的;


4)、我们在发消息的方法中做一个判断,获取redis中的chat-who:consultId,看是否存在,存在就往下执行,不存在就说明发的是第一条消息,那就创建chat-who:consultId这个key到redis中,value为当前发消息人D或者P;


5)、承接4,如果chat-who存在,我们继续将当前发消息的对象和redis的chat-who存储的对象值进行比较,如果一样,则跳过不管,如果不一样,更新chat-who的值为当前发消息的人。同时,我们判断当前发消息的人是不是医生也就是D,是D的话才更新回合数,执行-1操作,这样做的目的是把医生作为回合数更新的维度,维度只能有一个,这样才能保证回合数更新最准确。

接下来,我使用伪代码把整个思路写出来。

1、定义redis-key

/**
 * 聊天回合制常量
 */
public final class ChatRoundConstants {
    /**
     * 聊天回合数key前缀
     */
    public static final String CHAT_NUM = "chat-num:";
    /**
     * 聊天对象key前缀
     */
    public static final String CHAT_WHO = "chat-who:";
    /**
     * redis-key过期时间
     */
    public static final Long EXPIRE_TIME = 48 * 3600L;
    /**
     * 聊天对象value值,医生-D,患者-P。
     */
    public static final String DOCTOR = "D";
    public static final String PATIENT = "P";
}

2、初始化聊天回合数

在聊天之前初始化,这里我们项目的场景是患者发起咨询成功后,就在这个成功后的方法中初始化。

/**
 * 发起咨询成功
 */
public void consultSuccess() {
    // ....其他业务逻辑处理
    
    // 初始化聊天回合数
    initChatRoundNum(ConsultDTO consultDTO);
}

/**
 * 初始化聊天回合数
 * -- 过期时间48小时
 * @param consultDTO 咨询信息
 */
private void initChatRoundNum(ConsultDTO consultDTO) {
    // 初始6回合
    int chatNum = 6;
    
    // 获取系统配置的默认回合数,这里是伪代码根据自己需要编写。
    ParameterDTO parameterDTO = getConfigValue();
    if(!ObjectUtils.isEmpty(parameterDTO)) {
        chatNum = parameterDTO.getPvalue();
    }
    
    // 初始化到redis,key是chat-num:consultId
    redisService.set(ChatRoundConstants.CHAT_NUM + consultDTO.getId(), 
        chatNum, ChatRoundConstants.EXPIRE_TIME);
}

3、更新回合数

这里是核心逻辑,主要分为两步:初始化chat-who:consultId,更新chat-num:consultId。

/**
 * 发消息
 */
public void sendMsg() {
    // ....其他业务逻辑
    
    // 更新聊天回合数
    handleChatRoundNum(consultDTO, consultDetailInfoDTO);
}


/**
 * 处理聊天回合数
 * @param consultDTO 咨询信息
 * @param consultDetailInfoDTO 聊天信息
 */
private void handleChatRoundNum(ConsultDTO consultDTO, 
                                ConsultDetailInfoDTO consultDetailInfoDTO) {
    
    // 获取redis保存的医生患者标识key
    String chatWhoKey = ChatRoundConstants.CHAT_WHO + consultDTO.getId();
    
    // 获取当前发消息的人对应的标识
    String current = ChatWhoEnum.getCodeById(consultDetailInfoDTO.getSource());
    
    // chat-who:consultId是否存在
    if(redisService.exists(chatWhoKey)) {
    
        String chatWhoValue = (String) redisService.get(chatWhoKey);
        
        // 判断当前发消息的人和chatWho的值是否相同,如果不同,更新chatWho为当前发消息的人。
        if(!Objects.equals(ChatWhoEnum.getIdByCode(chatWhoValue), 
        consultDetailInfoDTO.getSource())) {
        
            // 更新chatWho为当前发消息的人
            redisService.setRange(chatWhoKey, current, 0);
            
            // 判断当前发消息的人是否为D,是D的话才更新回合数。
            if(Objects.equals(ChatWhoEnum.DOCTOR.getId(), 
                                consultDetailInfoDTO.getSource())) {
            
                // 更新chatNum-1
                String chatNumKey = ChatRoundConstants.CHAT_NUM + consultDTO.getId();
                int chatNumValue = Integer.parseInt(
                                        (String) redisService.get(chatNumKey)
                                    );
                if(redisService.exists(chatNumKey) && chatNumValue > 0) {
                    redisService.decr(chatNumKey);
                }
                
            }
            
        }
    } else {
        // 不存在说明是第一条消息,创建这个key。
        redisService.set(chatWhoKey, current, ChatRoundConstants.EXPIRE_TIME);
    }
}

定义的发消息对象枚举

/**
 * 聊天对象来源的枚举类
 */
public enum ChatWhoEnum {
    // 来源 :
    // 0 医生
    // 1 患者
    DOCTOR(0, "D", "医生"),
    PATIENT(1, "P", "患者");
    
    private final int id;
    private final String code;
    private final String label;
    
    ChatWhoEnum(final int id, final String code, final String label) {
        this.id = id;
        this.code = code;
        this.label = label;
    }
    
    public int getId() {
        return id;
    }
    public String getCode() {
        return code;
    }
    public String getLabel() {
        return label;
    }
    
    public static String getCodeById(int id) {
        for(ChatWhoEnum type: ChatWhoEnum.values()) {
            if(type.getId() == id) {
                return type.getCode();
            }
        }
        return null;
    }
    
    public static Integer getIdByCode(String code) {
        for(ChatWhoEnum type: ChatWhoEnum.values()) {
            if(code.equalsIgnoreCase(type.getCode())) {
                return type.getId();
            }
        }
        return null;
    }
}

其实写起来很简单,思路也不难,但忽然间让你来实现这个小功能的话还是挺费劲的,理不清楚就会一直卡在里面,理清楚了瞬间就念头通达。


这个功能目前已经上线,并且运行稳定没有任何问题,感兴趣的可以收藏起来,如果有一天做聊天相关业务的话,说不定就会遇到类似的需求。


本人原创文章纯手打,觉得有一滴滴帮助就请点个推荐吧~

本人持续分享实际工作经验和主流技术,喜欢的话可以关注下哦~

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK