前言

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



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

场景

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



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



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



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



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

思路

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

总结

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



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


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

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

分享一个网上搜不到的「Redis」实现「聊天回合制」的方案的更多相关文章

  1. 分享一个解决MySQL写入中文乱码的方法

    分享一个解决MySQL写入中文乱码的方法 之前有发帖请教过如何解决MySQL写入中文乱码的问题.但没人会,或者是会的人不想回答.搜索网上的答案并尝试很多次无效,所以当时就因为这个乱码问题搁浅了一个软件 ...

  2. LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下。 ? import java.util.HashMap; impo

    LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下. import java.util.HashMap; import ...

  3. 【开源.NET】 分享一个前后端分离的轻量级内容管理框架

    开发框架要考虑的面太多了:安全.稳定.性能.效率.扩展.整洁,还要经得起实践的考验,从零开发一个可用的框架,是很耗时费神的工作.网上很多开源的框架,为何还要自己开发?我是基于以下两点: 没找到合适的: ...

  4. 分享一个命令行计算器-bc

    分享一个命令行计算器-bc 假如你在一个图形桌面环境中需要一个计算器时,你可能只需要一路进行点击便可以找到一个计算器.例如,Fedora 工作站中就已经包含了一个名为 Calculator 的工具.它 ...

  5. 分享一个漂亮按钮插件FancyButtons

    一转眼,2018年的第10天就这样过去了.回看17年,曾经做了些啥都忘记了,就像每一天写日志时的样子(双手放在键盘上,怒着嘴,抬着头,望着天花板), 然后突然记得好像好久没有写随笔了(@_@).自从配 ...

  6. 【微信支付】分享一个失败的案例 跨域405(Method Not Allowed)问题 关于IM的一些思考与实践 基于WebSocketSharp 的IM 简单实现 【css3】旋转倒计时 【Html5】-- 塔台管制 H5情景意识 --飞机 谈谈转行

    [微信支付]分享一个失败的案例 2018-06-04 08:24 by stoneniqiu, 2744 阅读, 29 评论, 收藏, 编辑 这个项目是去年做的,开始客户还在推广,几个月后发现服务器已 ...

  7. 分享一个简单好用的ipv6正则表达式

    网上找了好几个,都不太好使.比较严谨的又运行缓慢,而且文本中多处含ipv6的时候,又提取不出全部的ipv6. 故分享一个不太严谨效率又高的ipv6正则表达式: ([a-f0-9]{1,4}(:[a-f ...

  8. 分享一个Flink checkpoint失败的问题和解决办法

    本文来自: PerfMa技术社区 PerfMa(笨马网络)官网 接触Flink一段时间了,遇到了一些问题,其中有一个checkpoint失败导致作业重启的问题,遇到了很多次,重启之后一般也能恢复正常, ...

  9. 分享一个关于Cookie做的实验结果

    实验本身是很枯燥的,我尽量把它讲的有趣些. 起因 去网上搜了下关于Cookie的介绍,看了好几篇都长得很一样,阉割一下内容不外乎说是"不同浏览器限制cookie数不同,大致在30-50这个范 ...

随机推荐

  1. 变量 数据类型 条件if语句

    python是解释型  弱类型编程语言;  "优雅", "明确", "简单";  开发效率非常高;  可移植性;  可扩展性; 可嵌入型. ...

  2. numpy---(上)

    Numpy Numpy ndarray N维数组对象ndarray, 是一系列同类型数据的集合, 索引以0下标开始, 创建一个ndarray对象, 需调用array函数: numpy.array(ob ...

  3. SpringMVC-注解@RequestParam

    当请求的参数名称与Controller的业务方法不一致时,就需要通过@RequestParam注解进行显示的绑定 1.value:映射参数 @RequestMapping("/report1 ...

  4. kali下安装docker

    前期准备 物理机:win10 虚拟机:kali 2021 网络连接方式:桥接 一.简介 Vulhub: 是一个面向大众的开源漏洞靶场,无需docker知识,简单执行两条命令即可编译.运行一个完整的漏洞 ...

  5. Java应用工程结构

    分层的本质是关注点分离,隔离对下层的变化,可以简化复杂性,使得层次结构更加清晰. 1. 主流分层结构介绍 目前业界存在两种主流的应用工程结构:一种是阿里推出的<Java开发手册>中推荐的, ...

  6. 2021.11.10 fail树

    2021.11.10 fail树 https://blog.csdn.net/niiick/article/details/87947160 1. AC自动机与fail树的神奇关系 1.1 AC自动机 ...

  7. Java基础语法Day_08(继承、抽象)

    第1节 继承 day09_01_继承的概述 day09_02_继承的格式 day09_03_继承中成员变量的访问特点 day09_04_区分子类方法中重名的三种变量 day09_05_继承中成员方法的 ...

  8. spring中的事件发布与监听

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. spring事件发布与监听的应用场景 当处理完一段代码逻辑,接下来需要同 ...

  9. 各种查找算法的选用分析(顺序查找、二分查找、二叉平衡树、B树、红黑树、B+树)

    目录 顺序查找 二分查找 二叉平衡树 B树 红黑树 B+树 参考文档 顺序查找 给你一组数,最自然的效率最低的查找算法是顺序查找--从头到尾挨个挨个遍历查找,它的时间复杂度为O(n). 二分查找 而另 ...

  10. 2.3 为什么建议使用虚拟机来安装Linux?

    笔者认为,通过虚拟机软件学习是初学者学习 Linux 的最佳方式. 在与部分读者的交流中,笔者发现,很多初学者都认为,学习 Linux 就必须将自己的电脑装成 Linux 系统或者必须要有真正的服务器 ...