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

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

作者: 程序员济癫 | 来源:发表于2022-06-18 14:52 被阅读0次

    前言

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

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

    场景

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

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

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

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

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

    思路

    结合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;
        }
    }
    

    总结

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

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


    本人原创文章纯手打,觉得有一滴滴帮助就请点个赞和收藏吧~

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

    更多最新技术文章可关注GZH:【Java分享客栈】

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/vvgkvrtx.html