為什麼說網上搜不到,因為關於聊天回合制的方案作者本人快把百度搜禿嚕了也沒找到,好在最終是公司一個關係不錯的大佬幫提供了點思路,最終作者將其完整實現了出來。
分享出來大家可以收藏,萬一你哪天也碰到這樣的需求,可不就節省大把時間了嗎。
先說下我這邊的場景,讀過我文章的同好都知道,我是做網際網路醫療行業的,我們的專案中是包含聊天功能的,我們服務的物件主要是醫院的醫生,患者在網上找醫生問診時,往往會出現不停問的情況。
醫生目前唯一的做法是自己結束這個諮詢,或等待系統自動結束,這就帶來了一個問題,不管是系統結束還是醫生手動結束,患者都喜歡投訴和打差評,導致醫生不敢擅自結束,問煩了又不好不回覆,不回覆也要被投訴。
最終聊天回合制這個需求就擺出來了,主動告訴患者我們的聊天是有回合的,所以你要一次問清楚,回合數滿了我們不會再回復,如果患者硬要投訴,醫生也可以說,這是做這個產品的公司自己設定的。
結下來就是,我們要把鍋端好。
實際上,聊天回合制的誕生,基本上都和這個場景的訴求類似,為了減少使用者頻繁且無休止的諮詢。
結合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操作,這樣做的目的是把醫生作為回合數更新的維度,維度只能有一個,這樣才能保證回合數更新最準確。
接下來,我使用虛擬碼把整個思路寫出來。
/**
* 聊天回合制常數
*/
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";
}
在聊天之前初始化,這裡我們專案的場景是患者發起諮詢成功後,就在這個成功後的方法中初始化。
/**
* 發起諮詢成功
*/
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);
}
這裡是核心邏輯,主要分為兩步:初始化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;
}
}
其實寫起來很簡單,思路也不難,但忽然間讓你來實現這個小功能的話還是挺費勁的,理不清楚就會一直卡在裡面,理清楚了瞬間就念頭通達。
這個功能目前已經上線,並且執行穩定沒有任何問題,感興趣的可以收藏起來,如果有一天做聊天相關業務的話,說不定就會遇到類似的需求。
本人原創文章純手打,覺得有一滴滴幫助就請點個推薦吧~
本人持續分享實際工作經驗和主流技術,喜歡的話可以關注下哦~