日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

刚刚更新:在线聊天系统设计(原理+思路+源码+效果图)

發布時間:2025/7/14 windows 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 刚刚更新:在线聊天系统设计(原理+思路+源码+效果图) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

這周項目要做一個在線聊天系統,感覺不是特別困難,原理也很簡單,分享給大家。

?

技術

Java(Spring)+Mysql+MemCache

Spring做的是事件驅動模型,所有DB,更新緩存操作改成異步的。

MemCache存放緩存,每個用戶的聊天記錄緩存,好友關系維護。

?

需求

用戶分為虛擬用戶,普通用戶,高級用戶(在線經理人),管理員用戶(客服)。

虛擬,普通用戶有一個好友列表,好友列表保存著用戶的好友,對于虛擬,普通用戶來說,他們的好友列表只有高級用戶+管理員用戶。

高級用戶,管理員用戶來說只要是用戶給我發過消息,我都能看到,并且回復。

?

效果圖

?

?

?

后臺提供的接口列表

|--聊天列表
?? |--普通用戶獲取動態聊天列表,目前固定是三位,客服+經理2
?? |--特殊用戶獲取用戶對自己提問的列表
|--聊天回復
?? |--直接發送消息到后臺
|--獲取聊天數據
?? |--獲取該用戶跟某用戶的聊天記錄,帶分頁
|--定時檢查接口
?? |--檢測此用戶是否有新消息提示

?

提供接口控制器的源碼:

@Controller public class CommunicateCtrl extends BaseController {@RequestMapping("/communicate/ask")@ResponseBodypublic void doAsk(@RequestAttr ResultData resultData, Communicate model, HttpServletRequest request) throws Exception {model.checkChatIdEmpty("聊天對象Id不能為空");model.checkContentEmpty("聊天內容不能為空");model.checkContentIllegal("您的聊天內容帶有敏感詞");UserInfo userInfo = getUserInfo();if (null != userInfo) { // 如果聊天者已經登錄model.setUserId(String.valueOf(userInfo.getUserId()));model.setMobile(userInfo.getMobile());model.setName(StringUtil.isNullOrEmpty(userInfo.getUserName()) ? "" : userInfo.getUserName());} else {model.setUserId(getUserId());if (Str.isEmpty(model.getUserId())) // 當傳過來的cookie為空,則生成一個cookie,并使用虛擬userIdgenerateVirUserInfoWhenUserIdEmpty(model);}model.setStatus(1); // 未回復model.setUserType(1); // 普通用戶model.setBuildTime(new Date());communicateService.save(model); // 保存DB對象resultData.setData(model);putEvent(model);}@RequestMapping("/communicate/friends")@ResponseBodypublic void doFriendList(@RequestAttr ResultData resultData, HttpServletRequest request) throws Exception {// 如果為普通用戶if (null == getUserInfo() || getUserInfo().getType() != 3) {List<UserInfo> userInfos = userInfoService.getList(" and type = 3 "); // 加載特殊角色,提供在線聊天功能for (UserInfo userInfo : userInfos)userInfo.getDicMap().put("userType", 2); // userType 0 虛擬用戶 1普通用戶 2經紀人resultData.setData(userInfos);return;}// 特殊用戶獲取好友列表List<String> friendList = communicateHandle.getFriendListCache(getUserId()); // 獲取好友列表List<Object> list = new ArrayList<Object>(friendList.size());for (String userId : friendList) {if (Str.isEmpty(userId))continue;Object o = null;if (ZhengzeValidate.isInteger(userId)) { // 普通用戶IdUserInfo userInfo = userInfoService.getById(Integer.parseInt(userId));if (null != (o = userInfo)) {userInfo.setHeadImg(Str.isEmpty(userInfo.getHeadImg()) ? defaultImg : userInfo.getHeadImg());userInfo.getDicMap().put("userType", 1); // userType 0 虛擬用戶 1普通用戶 2經紀人}} else {// user.dicMap.userType// userType 0 虛擬用戶 1普通用戶 2經紀人o = MapBean.getNew().set("userId", userId).set("headImg", defaultImg).set("dicMap", MapBean.getNew("userType", 0));}list.add(o);}resultData.setData(list);}@RequestMapping("/communicate/check")@ResponseBodypublic void doCheck(@RequestAttr ResultData resultData, String updateStatusUserId) throws Exception {String userId = getUserId();List<MapBean> dataMapList = new ArrayList<MapBean>(); // 用戶是否有新消息列表List<String> friendList = communicateHandle.getFriendListCache(userId); // 獲取好友列表List<Communicate> chatsList = null;List<Communicate> unReaderList = null; // 未讀消息列表,提供給前端// 循環所有好友的聊天數據,檢測是否有新數據for (String friendUserId : friendList) {// 110&8_chart_list// 8&110_chart_listchatsList = communicateHandle.getChatsCache(friendUserId, userId); // 取得與每個好友的聊天記錄,注意與生成key的順序區別if (!chatsList.isEmpty()) {// 如果存在聊天數據int size = 0;String lastMsg = null;if (Str.isNotEmpty(updateStatusUserId))unReaderList = new ArrayList<Communicate>();for (Communicate communicate : chatsList) {lastMsg = communicate.getContent();if (!communicate.getUserId().equals(userId) && communicate.getStatus() == 1) { // 只查詢我未讀的消息,過濾我的消息size += 1;if (communicate.getUserId().equals(updateStatusUserId)) { // 如果聊天對象一致,則更新狀態,并返回未讀消息列表communicate.setStatus(2);// 內存與db一致Communicate communicateDB = communicateService.getById(communicate.getId());if (communicateDB.getStatus() == 2) // 如果其他線程已更新狀態,這里則不返回continue;communicateDB.setStatus(communicate.getStatus());communicateService.updateById(communicateDB);unReaderList.add(communicate);}// // 如果需要更新狀態 --- 性能更好的一種批量更新方式// if (Str.isNotEmpty(isUpdateStatus)) {// communicateService.updateStatus(list.get(0), " and user_id = '" + userId + "' and status = " + Communicate.STATUS_MGR_REPLY);// }}}MapBean dataMap = MapBean.getNew("userId", friendUserId, "oper", "normal"); // 返回最后的頁數,操作為正常(沒有新消息)// 返回新消息if (size > 0) {if (null != unReaderList && !unReaderList.isEmpty())communicateHandle.updateChatsCache(updateStatusUserId, userId, chatsList); // 更新緩存dataMap.set("oper", "new").set("msg", tl("你有:0條未讀消息", size));dataMap.set("lastMsg", lastMsg).set("unReadMsgCount", size);dataMap.set("unReaderList", unReaderList);}dataMapList.add(dataMap);}}resultData.setData(dataMapList); // 設置與所有用戶聊天數據// 如果出現某一個用戶的聊天數據,則返回該用戶的聊天數據if (Str.isNotEmpty(updateStatusUserId)) {for (MapBean dataMap : dataMapList) {if (dataMap.getString("userId").equals(updateStatusUserId)) {resultData.setData(dataMap);break;}}}}@RequestMapping("/communicate/chats")@ResponseBodypublic void doChats(@RequestAttr ResultData resultData, String chatId) throws Exception {if (Str.isEmpty(chatId))throw new RuntimeException("聊天對象Id不能為空");int pageSize = Tool.convertInt(RequestTool.getParameter("pageSize"), 10);int msgId = Tool.convertInt(RequestTool.getParameter("msgId"), 0);List<Communicate> chatsList = communicateHandle.getChatsCache(chatId, getUserId()); // 取得與每個好友的聊天記錄int size = chatsList.size();int lastIndex = size - 1; // List索引可能出現 1-1=0的情況,if中做兼容if (lastIndex >= 0) { // 如果存在聊天數據if (msgId <= 0) { // 如果消息Id為空,則取最后數據N條if (size <= pageSize)resultData.setData(chatsList);elseresultData.setData(chatsList.subList(size - pageSize, size)); // 倒序,取最后一節數據return;}// 根據msgId來取數據int msgIdIndex = binarySearch(chatsList, msgId);if (msgIdIndex == -1 || msgIdIndex == 0) // -1則表示此msgId不存在,0則表示在它之前已經沒有了任何數據return;int subIndex = msgIdIndex - pageSize;resultData.setData(chatsList.subList(subIndex < 0 ? 0 : subIndex, msgIdIndex)); // 取出比msgId小的Id// msgIdIndex += 1;// +1 過濾掉自己// int subSize = msgIdIndex + pageSize;// resultData.setData(chatsList.subList(msgIdIndex, subSize > size ? size : subSize)); //取出比msgId大的Id}}// 二分法查找,查找線性表必須是有序列表int binarySearch(List<Communicate> chatsList, int key) {int low = 0, high = chatsList.size() - 1, mid;while (low <= high) {mid = (low + high) >>> 1;if (key == chatsList.get(mid).getId()) {return mid;} else if (key < chatsList.get(mid).getId()) {high = mid - 1;} else {low = mid + 1;}}return -1;}void generateVirUserInfoWhenUserIdEmpty(Communicate communicate) {communicate.setUserId(UUID.randomUUID().toString().replace("-", "")); // 生成虛擬UUIDHttpUtils.addCookie(RequestTool.getResponse(), Constants.VIR_USER_ID, communicate.getUserId(), 24 * 60 * 60 * 1000 * 7); // 保存cookie一周}String getUserId() {String userId = null;if (null != getUserInfo()) {userId = getUserInfo().getUserId() + "";} else {Cookie cookie = getCookieByName(Constants.VIR_USER_ID);if (null != cookie)userId = cookie.getValue();}// debug模式可以傳入用戶idString id = RequestTool.getParameter("id");return isDebug() && Str.isNotEmpty(id) ? id : userId;}@SuppressWarnings("unchecked")Map<Class<?>, Set<String>> setResultJsonFilter(Class<?> clazz, Set<String> set) {Map<Class<?>, Set<String>> includeMap = (Map<Class<?>, Set<String>>) RequestTool.getRequest().getAttribute("includeMap");if (null == includeMap)RequestTool.getRequest().setAttribute("includeMap", includeMap = new HashMap<Class<?>, Set<String>>());includeMap.put(clazz, set);RequestTool.getRequest().setAttribute("jsonFilter", new ComplexPropertyPreFilter(includeMap));return includeMap;}void putEvent(Communicate model) {SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(model));}@AutowiredCommunicateService communicateService;@AutowiredCommunicateHandle communicateHandle;@AutowiredUserInfoService userInfoService;String defaultImg = ConfigLoader.loader.getString("user_default_img"); }



?




Spring異步觀察者事件處理:

@Component @SuppressWarnings("unchecked") public class CommunicateHandle implements ApplicationListener<CommunicateEvent> {static final String chartsKey = "_chart_list";static final String friendsKey = "_friend_list";@Overridepublic void onApplicationEvent(CommunicateEvent event) {Communicate model = (Communicate) event.getSource();if (null == model.getId())communicateService.save(model); // 保存DB對象// 查詢并更新自己的好友列表getAndAddFriendList(model);// 查詢并更新聊天對象的好友列表getAndAddFriendList(model, "friend");// 查詢并添加自己與聊天對象的記錄列表getAndAddChats(model);}List<Communicate> getAndAddChats(Communicate model) {List<Communicate> list = null; // 用戶所有的聊天記錄try {list = getChatsCache(model.getUserId(), model.getChatId());list.add(model);Collections.sort(list); // 排序此用戶的消息隊列updateChatsCache(model.getUserId(), model.getChatId(), list);// 保存至緩存} catch (Exception e) {e.printStackTrace();}return list;}List<String> getAndAddFriendList(Communicate model, String... friends) {List<String> list = null; // 所有用戶的好友try {list = friends.length == 0 ? getFriendListCache(model.getUserId()) : getFriendListCache(model.getChatId());if (friends.length == 0 ? !list.contains(model.getChatId()) && list.add(model.getChatId()) : !list.contains(model.getUserId()) && list.add(model.getUserId()))setCache(getKey(model, friends) + friendsKey, list); // 自動追加為好友} catch (Exception e) {e.printStackTrace();}return list;}String getKey(Communicate model, String... friends) {String key = model.getUserId();if (friends.length > 0)key = model.getChatId();return key;}public List<String> getFriendListCache(Object userId) throws Exception {List<String> list = (List<String>) MemCacheClient.get(userId + friendsKey);if (Str.isNull(list))list = new ArrayList<String>();return list;}public List<Communicate> getChatsCache(Object userId, Object chatsUserId) throws Exception {List<Communicate> list = (List<Communicate>) MemCacheClient.get(userId + "&" + chatsUserId + chartsKey);if (Str.isNull(list))list = new ArrayList<Communicate>();return list;}public boolean updateChatsCache(Object userId, Object chatsUserId, Object o) throws Exception {setCache(chatsUserId + "&" + userId + chartsKey, o); // 1296000秒 = 15天setCache(userId + "&" + chatsUserId + chartsKey, o); // 1296000秒 = 15天return true;}boolean setCache(String key, Object o) throws Exception {return MemCacheClient.set(key, 1296000, o); // 1296000秒 = 15天}@AutowiredCommunicateService communicateService; }


項目啟動時,根據聊天記錄,初始化用戶好友列表&用戶與用戶之間的聊天記錄:


@Service public class CommunicateServiceImpl extends BaseServiceImpl<Communicate, Integer> implements CommunicateService {@Overridepublic void initAllChats() {List<Communicate> allList = getList("");try {// 清除原有緩存for (Communicate communicate : allList) {communicateListener.updateChatsCache(communicate.getUserId(), communicate.getChatId(), new ArrayList<Communicate>());}// 增加聊天記錄緩存for (Communicate communicate : allList) {SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(communicate));}} catch (Exception e) {throw new RuntimeException("在線聊天緩存初始化出現了異常:" + e.getMessage());}}@AutowiredCommunicateHandle communicateListener;}



直接將數據push到緩存中,在Spring事件監聽里已經做了處理:


if (null == model.getId())communicateService.save(model); // 保存DB對象

有些代碼個人感覺寫的還是很精妙的,希望你們能找出來,哈哈~

?

?

?優化版源碼

提供接口控制器的源碼:

新接口整合了friend與check接口,增加了虛擬用戶轉成真實用戶后,經理人反查此真實用戶以前的虛擬用戶的聊天信息~ 增加了查詢好友列表緩存功能,Boss再也不用擔心程序性能~? 一切依賴于緩存~~


@Controller public class CommunicateCtrl extends BaseController {@RequestMapping("/communicate/ask")@ResponseBodypublic void doAsk(@RequestAttr ResultData resultData, Communicate model, HttpServletRequest request) throws Exception {model.checkChatIdEmpty("聊天對象Id不能為空");model.checkContentEmpty("聊天內容不能為空");model.checkContentIllegal("您的聊天內容帶有敏感詞");UserInfo userInfo = getUserInfo();model.setUserId(getUserId());if (null != userInfo) { // 如果聊天者已經登錄model.setMobile(userInfo.getMobile());model.setName(userInfo.getUserName());} else {if (Str.isEmpty(model.getUserId())) // 當傳過來的cookie為空,則生成一個cookie,并使用虛擬userIdgenerateVirUserInfoWhenUserIdEmpty(model);}model.setStatus(1); // 未回復model.setUserType(1); // 普通用戶model.setBuildTime(new Date());communicateService.save(model); // 保存DB對象SpringContextUtil.getApplicationContext().publishEvent(new CommunicateEvent(model));resultData.setData(model);}@RequestMapping("/communicate/check")@ResponseBodypublic void doCheck(@RequestAttr ResultData resultData, String updateStatusUserId) throws Exception {String userId = getUserId();List<MapBean> dataMapList = new ArrayList<MapBean>(); // 用戶是否有新消息列表&用戶列表if (null == getUserInfo() || getUserInfo().getType() != 3) { // 如果為普通用戶List<UserInfo> userInfos = communicateHandle.getCache("db_friend_list");if (null == userInfos)communicateHandle.setCache("db_friend_list", 3600, userInfos = userInfoService.getList(" and type = 3 ")); // 從DB中加載特殊角色,提供在線聊天功能,3600秒=1小時List<String> friendList = new ArrayList<String>(userInfos.size());for (UserInfo userInfo : userInfos)friendList.add(userInfo.getUserId() + "");dataMapList = getAndUpdateNewMsgList(userId, updateStatusUserId, friendList); // 用戶獲取特殊好友列表} elsedataMapList = getAndUpdateNewMsgList(userId, updateStatusUserId, communicateHandle.getFriendListCache(userId)); // 特殊用戶獲取好友列表resultData.setData(dataMapList); // 設置與所有用戶聊天數據// 如果出現某一個用戶的聊天數據,則返回該用戶的聊天數據if (Str.isNotEmpty(updateStatusUserId)) {for (MapBean dataMap : dataMapList) {Object oUser = dataMap.get("user");if (oUser instanceof UserInfo) {if (((UserInfo) oUser).getUserId().toString().equals(updateStatusUserId)) { // 真實用戶id是純數字resultData.setData(dataMap);break;}} else {if (((MapBean) oUser).getString("userId").equals(updateStatusUserId)) { // 虛擬用戶id對比resultData.setData(dataMap);break;}}// end else}// end for}// end if}@RequestMapping("/communicate/chats")@ResponseBodypublic void doChats(@RequestAttr ResultData resultData, String chatId) throws Exception {if (Str.isEmpty(chatId))throw new RuntimeException("聊天對象Id不能為空");doCheck(resultData, chatId); // 更新聊天信息int pageSize = Tool.convertInt(RequestTool.getParameter("pageSize"), 10);int msgId = Tool.convertInt(RequestTool.getParameter("msgId"), 0);List<Communicate> chatsList = communicateHandle.getChatsCache(chatId, getUserId(), getVirUserId()); // 取得與每個好友的聊天記錄int size = chatsList.size();if (size > 0) { // 如果存在聊天數據if (msgId <= 0) { // 如果消息Id為空,則取最后數據N條if (size <= pageSize)resultData.setData(chatsList);elseresultData.setData(chatsList.subList(size - pageSize, size)); // 倒序,取最后一節數據return;}// 根據msgId來取數據int msgIdIndex = binarySearch(chatsList, msgId);if (msgIdIndex == -1 || msgIdIndex == 0) // -1則表示此msgId不存在,0則表示在它之前已經沒有了任何數據return;int subIndex = msgIdIndex - pageSize;resultData.setData(chatsList.subList(subIndex < 0 ? 0 : subIndex, msgIdIndex)); // 取出比msgId小的Id}}// 二分法查找,查找線性表必須是有序列表int binarySearch(List<Communicate> chatsList, int key) {int low = 0, high = chatsList.size() - 1, mid;while (low <= high) {mid = (low + high) >>> 1;if (key == chatsList.get(mid).getId()) {return mid;} else if (key < chatsList.get(mid).getId()) {high = mid - 1;} else {low = mid + 1;}}return -1;}void generateVirUserInfoWhenUserIdEmpty(Communicate communicate) {communicate.setUserId(UUID.randomUUID().toString().replace("-", "")); // 生成虛擬UUIDHttpUtils.addCookie(RequestTool.getResponse(), Constants.VIR_USER_ID, communicate.getUserId(), 0); // 保存cookie永久有效}String getUserId() {String userId = null != getUserInfo() ? userId = getUserInfo().getUserId() + "" : getVirUserId();// debug模式可以傳入用戶idString id = RequestTool.getParameter("id");return isDebug() && Str.isNotEmpty(id) ? id : userId;}String getVirUserId() {Cookie cookie = getCookieByName(Constants.VIR_USER_ID);if (null != cookie)return cookie.getValue();return null;}List<MapBean> getAndUpdateNewMsgList(String userId, String updateStatusUserId, List<String> friendList) throws Exception {List<MapBean> dataMapList = new ArrayList<MapBean>();List<Communicate> chatsList = null;List<Communicate> unReaderList = null; // 未讀消息列表,提供給前端for (String friendUserId : friendList) {if (Str.isEmpty(friendUserId))continue;String userBindVirUser = null; // 真實用戶綁定的虛擬用戶Object o = null;if (ZhengzeValidate.isInteger(friendUserId)) { // 普通用戶IdUserInfo userInfo = communicateHandle.getCache(tl("db:0_friend_list", friendUserId));if (null == userInfo)communicateHandle.setCache(tl("db:0_friend_list", friendUserId), 3600, userInfo = userInfoService.getById(Integer.parseInt(friendUserId))); // 從DB中加載特殊角色,提供在線聊天功能,3600秒=1小時if (null != (o = userInfo)) {userInfo.setHeadImg(Str.isEmpty(userInfo.getHeadImg()) ? defaultImg : userInfo.getHeadImg());userInfo.getDicMap().put("userType", userInfo.getType()); // userType 0 虛擬用戶 1普通用戶 2經紀人,如果是普通用戶獲取的特殊用戶的好友列表,則用戶類型為經紀人,如果是特殊用戶獲取的用戶列表,則獲取的用戶類型為普通用戶userBindVirUser = Str.isNotEmpty(userInfo.getVirUserId()) ? userInfo.getVirUserId() : null; // 取出用戶綁定虛擬用戶}} elseo = MapBean.getNew().set("userId", friendUserId).set("headImg", defaultImg).set("dicMap", MapBean.getNew("userType", 0)); // userType 0 虛擬用戶 1普通用戶 2經紀人chatsList = communicateHandle.getChatsCache(friendUserId, userId, userBindVirUser); // 取得與每個好友的聊天記錄int size = 0;String lastMsg = null;if (Str.isNotEmpty(updateStatusUserId))unReaderList = new ArrayList<Communicate>();for (Communicate communicate : chatsList) {lastMsg = communicate.getContent();if (!communicate.getUserId().equals(userId) && communicate.getStatus() == 1) { // 只查詢我未讀的消息,過濾我的消息size += 1;if (communicate.getUserId().equals(updateStatusUserId)) { // 如果聊天對象一致,則更新狀態,并返回未讀消息列表communicate.setStatus(2);// 內存與db一致Communicate communicateDB = communicateService.getById(communicate.getId());if (communicateDB.getStatus() == 2) // 如果其他線程已更新狀態,這里則不返回continue;communicateDB.setStatus(communicate.getStatus());communicateService.updateById(communicateDB);unReaderList.add(communicate);}// // 如果需要更新狀態 --- 性能更好的一種批量更新方式// if (Str.isNotEmpty(isUpdateStatus)) {// communicateService.updateStatus(list.get(0), " and user_id = '" + userId + "' and status = " + Communicate.STATUS_MGR_REPLY);// }}}MapBean dataMap = MapBean.getNew("user", o, "oper", "normal"); // 操作為正常(沒有新消息)// 返回新消息if (size > 0) {if (null != unReaderList && !unReaderList.isEmpty())communicateHandle.updateChatsCache(updateStatusUserId, userId, chatsList); // 更新緩存dataMap.set("oper", "new").set("msg", tl("你有:0條未讀消息", size));dataMap.set("lastMsg", lastMsg).set("unReadMsgCount", size);dataMap.set("unReaderList", unReaderList);}dataMapList.add(dataMap);}return dataMapList;}@AutowiredCommunicateService communicateService;@AutowiredCommunicateHandle communicateHandle;@AutowiredUserInfoService userInfoService;String defaultImg = ConfigLoader.loader.getString("user_default_img");}



?

?

?

?

?

?

?

?

?

?

?

?

?

?

轉載于:https://my.oschina.net/linapex/blog/518651

總結

以上是生活随笔為你收集整理的刚刚更新:在线聊天系统设计(原理+思路+源码+效果图)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。