乐趣区

关于java:分享一个网上搜不到的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;
    }
}

总结

其实写起来很简略,思路也不难,但突然间让你来实现这个小性能的话还是挺吃力的,理不分明就会始终卡在外面,理分明了霎时就念头通达。

这个性能目前曾经上线,并且运行稳固没有任何问题,感兴趣的能够珍藏起来,如果有一天做聊天相干业务的话,说不定就会遇到相似的需要。


自己原创文章纯手打,感觉有一滴滴帮忙就请点个赞和珍藏吧~

自己继续分享理论工作教训和支流技术,喜爱的话能够关注下哦~

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

退出移动版