一套簡單的web即時通訊——第二版
前言
接上一版,這一版的頁面與功能都有所優化,具體如下:
1、優化登錄攔截
2、登錄後獲取所有好友並區分顯示在線、離線好友,好友上線、下線都有標記
3、將前後端交互的值改成用戶id、顯示值改成昵稱nickName
4、聊天消息存儲,點擊好友聊天,先追加聊天記錄
5、登錄後獲取所有未讀消息並以小圓點的形式展示
6、搜索好友、添加好友
優化細節
1、登錄攔截由之前的通過路徑中獲取賬號,判斷WebSocketServer.loginList中是否存在key改成登錄的時候設置cookie,登錄攔截從cookie中取值
登錄、登出的時候設置、刪除cookie,
/** * 登錄 */ @PostMapping("login") public Result<ImsUserVo> login(ImsUserVo userVo, HttpServletResponse response) { //加密後再去對比密文 userVo.setPassword(MD5Util.getMD5(userVo.getPassword())); Result<List<ImsUserVo>> result = list(userVo);ImsUserController.javaif (result.isFlag() && result.getData().size() > 0) { ImsUserVo imsUserVo = result.getData().get(0); //置空隱私信息 imsUserVo.setPassword(null); //add WebSocketServer.loginList WebSocketServer.loginList.put(imsUserVo.getUserName(), imsUserVo);//設置cookie Cookie cookie = new Cookie("imsLoginToken", imsUserVo.getUserName()); cookie.setMaxAge(60 * 30); //設置域 // cookie.setDomain("huanzi.cn"); //設置訪問路徑 cookie.setPath("/"); response.addCookie(cookie); return Result.of(imsUserVo); } else { return Result.of(null, false, "賬號或密碼錯誤!"); } } /** * 登出 */ @RequestMapping("logout/{username}") public ModelAndView loginOut(HttpServletResponse response, @PathVariable String username) { new WebSocketServer().deleteUserByUsername(username,response); return new ModelAndView("login.html"); }
改成關閉websocket時不做操作,僅減減socket連接數
/** * 連接關閉調用的方法 */ @OnClose public void onClose(Session session) { //下線用戶名 String logoutUserName = ""; //從webSocketMap刪除下線用戶 for (Entry<String, Session> entry : sessionMap.entrySet()) { if (entry.getValue() == session) { sessionMap.remove(entry.getKey()); logoutUserName = entry.getKey(); break; } } deleteUserByUsername(logoutUserName,null); } /** 用戶下線 */ public void deleteUserByUsername(String username, HttpServletResponse response){ //在線人數減減 WebSocketServer.onlineCount--; if(WebSocketServer.onlineCount <= 0){ WebSocketServer.onlineCount = 0; } if(StringUtils.isEmpty(response)){ return; } //用戶集合delete WebSocketServer.loginList.remove(username); //刪除cookie 思路就是替換原來的cookie,並設置它的生存時間為0 //設置cookie Cookie cookie = new Cookie("imsLoginToken", username); cookie.setMaxAge(0); //設置域 // cookie.setDomain("huanzi.cn"); //設置訪問路徑 cookie.setPath("/"); response.addCookie(cookie); //通知除了自己之外的所有人 sendOnlineCount(username, "{‘type‘:‘onlineCount‘,‘onlineCount‘:" + WebSocketServer.onlineCount + ",username:‘" + username + "‘}"); }WebSocketServer.java
在登錄攔截器中從cookie取用戶賬戶
//其實存的是用戶賬號 String imsLoginToken = ""; Cookie[] cookies = request.getCookies(); if (null != cookies) { for (Cookie cookie : cookies) { if ("imsLoginToken".equals(cookie.getName())) { imsLoginToken = cookie.getValue(); } } } if(WebSocketServer.loginList.containsKey(imsLoginToken)){ //正常處理請求 filterChain.doFilter(servletRequest, servletResponse); }else{ //重定向登錄頁面 response.sendRedirect("/imsUser/loginPage.html"); }LoginFilter.java
2、登錄之後的用戶列表不再是顯示websocket連接的用戶,而是登錄用戶的好友,同時要區分顯示好友的在線與離線,所以新增一個獲取在線好友的接口
/** * 獲取在線好友 */ @PostMapping("getOnlineList") private Result<List<ImsUserVo>> getOnlineList(ImsFriendVo imsFriendVo) { return imsFriendService.getOnlineList(imsFriendVo); } /** * 獲取在線好友 */ @Override public Result<List<ImsUserVo>> getOnlineList(ImsFriendVo imsFriendVo) { //好友列表 List<ImsFriendVo> friendList = list(imsFriendVo).getData(); //在線好友列表 ArrayList<ImsUserVo> onlineFriendList = new ArrayList<>(); //遍歷friendList for(ImsFriendVo imsFriendVo1 : friendList){ ImsUserVo imsUserVo = imsFriendVo1.getUser(); if (!StringUtils.isEmpty(WebSocketServer.getSessionMap().get(imsUserVo.getId().toString()))) { onlineFriendList.add(imsUserVo); } } return Result.of(onlineFriendList); }ImsFriend
//連接成功建立的回調方法 websocket.onopen = function () { //獲取好友列表 // $.post(ctx + "/imsFriend/list",{userId: username},function (data) { // console.log(data) // }); $.ajax({ type: ‘post‘, url: ctx + "/imsFriend/list", contentType: ‘application/x-www-form-urlencoded; charset=UTF-8‘, dataType: ‘json‘, data: {userId: user.id}, success: function (data) { if (data.flag) { //列表 let friends = data.data; for (let i = 0; i < friends.length; i++) { let friend = friends[i].user; let $friendGroupList = $("<div class=\"hz-group-list\">" + "<img class=‘left‘ style=‘width: 23px;‘ src=‘https://avatars3.githubusercontent.com/u/31408183?s=40&v=4‘/>" + "<span class=‘hz-group-list-username‘>" + friend.nickName + "</span><span id=\"" + friend.id + "-status\" style=‘color: #9c0c0c;;‘>[離線]</span>" + "<div id=\"hz-badge-" + friend.id + "\" class=‘hz-badge‘>0</div>" + "</div>"); $friendGroupList.user = friend; $("#hz-group-body").append($friendGroupList); } //好友人數 $("#friendCount").text(friends.length); getOnlineList(user.id); } }, error: function (xhr, status, error) { console.log("ajax錯誤!"); } }); }; /** * 獲取在線好友 */ function getOnlineList(userId){ $.ajax({ type: ‘post‘, url: ctx + "/imsFriend/getOnlineList", contentType: ‘application/x-www-form-urlencoded; charset=UTF-8‘, dataType: ‘json‘, data: {userId: userId}, success: function (data) { if (data.flag) { //列表 let onlineFriends = data.data; for (let i = 0; i < onlineFriends.length; i++) { let friend = onlineFriends[i]; $("#" + friend.id + "-status").text("[在線]"); $("#" + friend.id + "-status").css("color", "#497b0f"); } //好友人數 $("#onlineCount").text(onlineFriends.length); } }, error: function (xhr, status, error) { console.log("ajax錯誤!"); } }); }socketChart.js
3、將之前前後端傳遞用戶賬戶username改成用戶id,同時,顯示的是nickName昵稱,改動的地方比較多,我就不貼代碼了
4、消息存儲
後端存儲關鍵代碼
/** * 服務器接收到客戶端消息時調用的方法 */ @OnMessage public void onMessage(String message, Session session) { try { //JSON字符串轉 HashMap HashMap hashMap = new ObjectMapper().readValue(message, HashMap.class); //消息類型 String type = (String) hashMap.get("type"); //來源用戶 Map srcUser = (Map) hashMap.get("srcUser"); //目標用戶 Map tarUser = (Map) hashMap.get("tarUser"); //如果點擊的是自己,那就是群聊 if (srcUser.get("userId").equals(tarUser.get("userId"))) { //群聊 groupChat(session,hashMap); } else { //私聊 privateChat(session, tarUser, hashMap); } //後期要做消息持久化 ImsFriendMessageVo imsFriendMessageVo = new ImsFriendMessageVo(); imsFriendMessageVo.setToUserId((Integer) tarUser.get("userId")); imsFriendMessageVo.setFromUserId((Integer) srcUser.get("userId")); //聊天內容 imsFriendMessageVo.setContent(hashMap.get("message").toString()); try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); imsFriendMessageVo.setCreatedTime(simpleDateFormat.parse(hashMap.get("date").toString())); imsFriendMessageVo.setUpdataTime(simpleDateFormat.parse(hashMap.get("date").toString())); } catch (ParseException e) { e.printStackTrace(); } imsFriendMessageService.save(imsFriendMessageVo); } catch (IOException e) { e.printStackTrace(); } }WebSocketServer.java
前端點擊好友時,獲取聊天記錄關鍵代碼
//讀取聊天記錄 $.post(ctx + "/imsFriendMessage/getChattingRecords", { fromUserId: userId, toUserId: toUserId }, function (data) { if (data.flag) { for (let i = 0; i < data.data.length; i++) { let msgObj = data.data[i]; //當聊天窗口與msgUserName的人相同,文字在左邊(對方/其他人),否則在右邊(自己) if (msgObj.fromUserId === userId) { //追加聊天數據 setMessageInnerHTML({ id: msgObj.id, isRead: msgObj.isRead, toUserId: msgObj.toUserId, fromUserId: msgObj.fromUserId, message: msgObj.content, date: msgObj.createdTime }); } else { //追加聊天數據 setMessageInnerHTML({ id: msgObj.id, isRead: msgObj.isRead, toUserId: msgObj.fromUserId, message: msgObj.content, date: msgObj.createdTime }); } } } });socketChart.js
/** * 獲取A-B的聊天記錄 */ @RequestMapping("getChattingRecords") public Result<List<ImsFriendMessageVo>> getChattingRecords(ImsFriendMessageVo imsFriendMessageVo){ return imsFriendMessageService.getChattingRecords(imsFriendMessageVo); } @Override public Result<List<ImsFriendMessageVo>> getChattingRecords(ImsFriendMessageVo imsFriendMessageVo) { //A對B的聊天記錄 List<ImsFriendMessageVo> allList = new ArrayList<>(super.list(imsFriendMessageVo).getData()); Integer fromUserId = imsFriendMessageVo.getFromUserId(); imsFriendMessageVo.setFromUserId(imsFriendMessageVo.getToUserId()); imsFriendMessageVo.setToUserId(fromUserId); //B對A的聊天記錄 allList.addAll(super.list(imsFriendMessageVo).getData()); //默認按時間排序 allList.sort(Comparator.comparingLong(vo -> vo.getCreatedTime().getTime())); return Result.of(allList); }ImsFriendMessage
5、登錄後獲取所有未讀消息並以小圓點的形式展示
登錄成功後獲取與好友的未讀消息關鍵代碼,在獲取好友列表之後調用
//獲取未讀消息 $.post(ctx + "/imsFriendMessage/list",{toUserId:userId,isRead:0},function(data){ if(data.flag){ let friends = {}; //將fromUser合並 for (let i = 0; i < data.data.length; i++) { let fromUser = data.data[i]; if(!friends[fromUser.fromUserId]){ friends[fromUser.fromUserId] = {}; friends[fromUser.fromUserId].count = 1; }else{ friends[fromUser.fromUserId].count = friends[fromUser.fromUserId].count + 1; } } for (let key in friends) { let fromUser = friends[key]; //小圓點++ $("#hz-badge-" + key).text(fromUser.count); $("#hz-badge-" + key).css("opacity", "1"); } } });socketChart.js
6、搜索好友、添加好友
可按照賬號、昵稱進行搜索,其中賬號是等值查詢,昵稱是模糊查詢
關鍵代碼
//搜索好友 function findUserByUserNameOrNickName() { let userNameOrNickName = $("#userNameOrNickName").val(); if (!userNameOrNickName) { tip.msg("賬號/昵稱不能為空"); return; } $.post(ctx + "/imsUser/findUserByUserNameOrNickName", { userName: userNameOrNickName, nickName: userNameOrNickName, }, function (data) { if (data.flag) { $("#friendList").empty(); for (let i = 0; i < data.data.length; i++) { let user = data.data[i]; let $userDiv = $("<div>" + "<img style=‘width: 23px;margin: 0 5px 0 0;‘ src=‘" + user.avatar + "‘/>" + "<span>" + user.nickName + "(" + user.userName + ")</span>" + "<button onclick=‘tipUserInfo($(this).parent()[0].user)‘>用戶詳情</button>" + "<button onclick=‘‘>加好友</button>" + "</div>"); $userDiv[0].user = user; $("#friendList").append($userDiv); } } }); }socketChart.js
/** * 根據賬號或昵稱(模糊查詢)查詢 */ @PostMapping("findUserByUserNameOrNickName") public Result<List<ImsUserVo>> findUserByUserNameOrNickName(ImsUserVo userVo) { return imsUserService.findUserByUserNameOrNickName(userVo); } @Override public Result<List<ImsUserVo>> findUserByUserNameOrNickName(ImsUserVo userVo) { return Result.of(CopyUtil.copyList(imsUserRepository.findUserByUserNameOrNickName(userVo.getUserName(), userVo.getNickName()), ImsUserVo.class)); } @Query(value = "select * from ims_user where user_name = :userName or nick_name like %:nickName%",nativeQuery = true) List<ImsUser> findUserByUserNameOrNickName(@Param("userName") String userName,@Param("nickName") String nickName);ImsUser
添加好友
首先要修改ims_friend結構,SQL如下,添加了一個字段is_agree,是否已經同意好友申請 0已申請但未同意 1同意 -1拒絕,之前查詢好友列表的post請求則需要新增參數isAgree=1
/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50528 Source Host : localhost:3306 Source Schema : test Target Server Type : MySQL Target Server Version : 50528 File Encoding : 65001 Date: 14/05/2019 17:25:35 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for ims_friend -- ---------------------------- DROP TABLE IF EXISTS `ims_friend`; CREATE TABLE `ims_friend` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘自增主鍵‘, `user_id` int(11) NULL DEFAULT NULL COMMENT ‘用戶id‘, `friend_id` int(11) NULL DEFAULT NULL COMMENT ‘好友id‘, `friend_type` int(11) NULL DEFAULT NULL COMMENT ‘好友分組id‘, `friend_remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT ‘好友備註‘, `is_agree` int(1) NULL DEFAULT NULL COMMENT ‘是否已經同意好友申請 0已申請但未同意 1同意 -1拒絕‘, `created_time` datetime NULL DEFAULT NULL COMMENT ‘創建時間‘, `updata_time` datetime NULL DEFAULT NULL COMMENT ‘更新時間‘, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = ‘好友表‘ ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1;View Code
在工具欄新加一個系統消息,貼出對應關鍵代碼
//監聽單擊系統消息,彈出窗口 $("body").on("click", "#sysNotification", function () { //此處為單擊事件要執行的代碼 if ($(".sysNotification").length <= 0) { tip.dialog({ title: "系統消息", class: "sysNotification", content: "<div></div>", shade: 0 }); } else { $(".sysNotification").click(); } $("#sysNotification").find(".hz-badge").css("opacity",0); $("#sysNotification").find(".hz-badge").text(0); //已拒絕 //申請好友 $.post(ctx + "/imsFriend/list", { friendId: userId, isAgree: 0, }, function (data) { if (data.flag) { for (let i = 0; i < data.data.length; i++) { let user = data.data[i].user; let $userDiv = $("<div>" + "<img style=‘width: 23px;margin: 0 5px 0 0;‘ src=‘" + user.avatar + "‘/>" + "<span>" + user.nickName + "(" + user.userName + ")</span> 申請添加好友<br/>" + "<button onclick=‘tipUserInfo($(this).parent()[0].user)‘>用戶詳情</button>" + "<button onclick=‘agreeAddFriend(" + data.data[i].id + ")‘>同意</button>" + "</div>"); $userDiv[0].user = user; $(".sysNotification .tip-content").append($userDiv); } } }); }); //申請添加好友 function applyToAddFriend(friendUserId) { let nowTime = commonUtil.getNowTime(); $.post(ctx + "/imsFriend/save", { userId: userId, friendId: friendUserId, friendType: 1, friendRemark: "", isAgree: 0, createdTime: nowTime, updataTime: nowTime, }, function (data) { if (data.flag) { tip.msg({text:"已為你遞交好友申請,對方同意好即可成為好友!",time:3000}); } }); } //同意好友添加 function agreeAddFriend(id){ let nowTime = commonUtil.getNowTime(); $.post(ctx + "/imsFriend/save", { id:id, isAgree: 1, updataTime: nowTime, }, function (data) { if (data.flag) { $.post(ctx + "/imsFriend/save", { userId: data.data.friendId, friendId: data.data.userId, friendType: 1, friendRemark: "", isAgree: 1, createdTime: nowTime, updataTime: nowTime, }, function (data) { if (data.flag) { tip.msg({text:"你們已經是好友了,可以開始聊天!",time:2000}); } }); } }); } //獲取我的申請好友,並做小圓點提示 function getApplyFriend(userId){ $.post(ctx + "/imsFriend/list", { friendId: userId, isAgree: 0, }, function (data) { if (data.flag && data.data.length > 0) { $("#sysNotification").find(".hz-badge").css("opacity",1); $("#sysNotification").find(".hz-badge").text(data.data.length); } }); }socketChart.js
在線、離線提示出來小bug...
後記
第二版暫時記錄到這,第三版持續更新中...
一套簡單的web即時通訊——第二版