公司有需求做一个聊天功能。 APP端,跟网页端互相聊天
android端直接嵌入了环信提供的DEMO。聊天记录。都是存储在本地自己进行维护。
所以本次只需要维护网页端的聊天记录~还有接收发送的消息就好啦。
好啦~人狠话不多。看效果吧!
总结一下要实现的功能点
1、发送与接收文字、表情、图片、地址消息、自定义消息 --》拉取聊天记录 (三天内的)
2、消息来了。外层菜单的红点提示,未读消息
3、redis中的聊天记录存储3天。 3天以后数据将插入到DB中
(下图为效果)
1、发送与接收文字、表情、图片、地址消息、自定义消息 (拉取聊天记录->三天内的)
本次无论是回调,还是发送,都将聊天的JSON字符串放入redis中。进行保存。
结构设计:
先看一个 环信webim接收到的回调数据结构 (用文本的举例)
{ "id": "449906829204391972", "type": "chat", "from": "iambuyer_30", "to": "jjcceshi", "data": "文本数据", "ext": {}, "sourceMsg": "文本数据", "error": false, "errorText": "", "errorCode": "" }
设计思路:
其实将2个人的聊天记录(上述的这个结构)存在redis中。
采用List结构即可。
KEY的话。可以使用2个人的ID作为KEY,比如
user_20
user_21
这2个用户。 key可以使用 chat||user_20&&user_21
意思就是无论回调,还是发送消息
使用ajax的方法,调用接口,将该JSON体扔进当前聊天双方所属的聊天内容中即可。
废话不多说。继续上代码。
先嵌入环信的WEB_IM 引入官方提示的资源文件
<script src="${rc.contextPath}/resources/webIm/webim.config.js"></script> <script src="${rc.contextPath}/resources/webIm/strophe-1.2.8.min.js"></script> <script src="${rc.contextPath}/resources/webIm/websdk-1.4.13.js"></script> <script src="${rc.contextPath}/resources/webIm/adapter.js"></script> <script src="${rc.contextPath}/resources/webIm/webrtc-1.4.12.js"></script> <script> var jsRootPath = '${rc.contextPath}'; //定义一个自己的ID。通过session取 var thisId = '$!session.getAttribute("LOGIN_USER").ssoUserId'; var thisHeadImg = '$!session.getAttribute("LOGIN_USER").headImg'; </script> <script type='text/javascript' src='${rc.contextPath}/resources/webIm/iambuyerKorEasemob.js'></script>
自定义一些逻辑控制变量
var EASEMOBTYPE = new Object; EASEMOBTYPE.txt='txt'; EASEMOBTYPE.img='img'; EASEMOBTYPE.audio='audio'; EASEMOBTYPE.loc='loc'; EASEMOBTYPE.prodLink='prodLink'; EASEMOBTYPE.prod='prod'; var EASEMOBWEBSHOWTYPE = new Object; EASEMOBWEBSHOWTYPE.me='mine'; EASEMOBWEBSHOWTYPE.you='user'; var IAMBUYER_PREFIX = "iambuyer_"; //定义一个自己的ID。通过session取 //ID 以及 头像拿到了菜单html中 /*var thisId = '$!session.getAttribute("LOGIN_USER").ssoUserId'; var thisHeadImg = '$!session.getAttribute("LOGIN_USER").headImg';*/ thisId = 202; //定义对方的ID 表示当前聊天的人是谁 var toUserId = ""; var toUserHeadImg = ""; //聊天窗口的ID var chatMsgContentDiv = "chatMsgContentDiv"; //记录游标 var index = 0; //记录自己 var me = "mine"; var you= "user";
写一下环信的登录、各种回调。
var conn = {}; conn = new WebIM.connection({ isMultiLoginSessions: WebIM.config.isMultiLoginSessions, https: typeof WebIM.config.https === 'boolean' ? WebIM.config.https : location.protocol === 'https:', url: WebIM.config.xmppURL, isAutoLogin: true, heartBeatWait: WebIM.config.heartBeatWait, autoReconnectNumMax: WebIM.config.autoReconnectNumMax, autoReconnectInterval: WebIM.config.autoReconnectInterval, apiUrl: WebIM.config.apiURL }); // tips: ie8 support fileInputId should match with the file in the document WebIM.flashUpload = UploadShim({fileInputId: 'image'}, conn).flashUpload; // listern,添加回调函数 conn.listen({ onOpened: function (message) { //连接成功回调,连接成功后才可以发送消息 //如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息 // 手动上线指的是调用conn.setPresence(); 在本例中,conn初始化时已将isAutoLogin设置为true // 所以无需调用conn.setPresence(); console.log("%c [opened] 连接已成功建立", "color: green") }, onTextMessage: function (message) { message['msgType'] = EASEMOBTYPE.txt; // 在此接收和处理消息,根据message.type区分消息来源,私聊或群组或聊天室 if(message.ext.isProId != undefined || message.ext.isProId == true ){ message['msgType'] = EASEMOBTYPE.prod; } if(message.ext.isPro != undefined || message.ext.isPro == true ){ message['msgType'] = EASEMOBTYPE.prodLink; } resolveEasemobJson(message,thisId,true); insertRedis(message,message['msgType']); //扔未读消息 insertRedisCount(message.from); }, //收到文本消息 onEmojiMessage: function (message) { // 当为WebIM添加了Emoji属性后,若发送的消息含WebIM.Emoji里特定的字符串,connection就会自动将 // 这些字符串和其它文字按顺序组合成一个数组,每一个数组元素的结构为{type: 'emoji(或者txt)', data:''} // 当type='emoji'时,data表示表情图像的路径,当type='txt'时,data表示文本消息 console.log('Emoji'); console.log(JSON.stringify(message)); var data = message.data; for (var i = 0, l = data.length; i < l; i++) { console.log(data[i]); } message['msgType'] = EASEMOBTYPE.txt; resolveEasemobJson(message,thisId,true); insertRedis(message,EASEMOBTYPE.txt); //扔未读消息 insertRedisCount(message.from); }, //收到表情消息 onPictureMessage: function (message) { console.log(message); console.log(JSON.stringify(message)); console.log('Picture'); var options = {url: message.url}; options.onFileDownloadComplete = function () { // 图片下载成功 console.log('Image download complete!'); console.log(message.url); message['msgType'] = EASEMOBTYPE.img; resolveEasemobJson(message,thisId,true); insertRedis(message,EASEMOBTYPE.img); //扔未读消息 insertRedisCount(message.from); }; options.onFileDownloadError = function () { // 图片下载失败 console.log('Image download failed!'); }; WebIM.utils.download.call(conn, options); // 意义待查 }, //收到图片消息 onCmdMessage: function (message) { console.log('CMD'); }, //收到命令消息 onAudioMessage: function (message) { console.log("Audio"); console.log(JSON.stringify(message)); }, //收到音频消息 onLocationMessage: function (message) { console.log(JSON.stringify(message)); console.log("Location"); message['msgType'] = EASEMOBTYPE.loc; resolveEasemobJson(message,thisId,true); insertRedis(message,EASEMOBTYPE.loc); //扔未读消息 insertRedisCount(message.from); },//收到位置消息 onOnline: function () { console.log('onLine'); }, //本机网络连接成功 onOffline: function () { console.log('offline'); }, //本机网络掉线 onError: function (message) { console.log('Error'); console.log(message); console.log(JSON.stringify(message)); if (message && message.type == 1) { console.warn('连接建立失败!请确认您的登录账号是否和appKey匹配。') } } //失败回调 });
现在开始贴一下。 回调中的resolveEasemobJson方法
主要作用就是。无论发送,还是收到消息。将该消息解析到当前聊天的窗口中去
//解析环信字符串append到html中 //初始化、回调通用 //tempfalg 追加到哪里 function resolveEasemobJson(easemobJson,thisUserId,tempfalg){ console.log(JSON.stringify(easemobJson)); var msgType = easemobJson.msgType; var from = easemobJson.from; var to = easemobJson.to; var falg =false; //判断是不是当前聊天窗口的人。如果不是 不显示。 if(IAMBUYER_PREFIX+toUserId == to || IAMBUYER_PREFIX+toUserId == from){ falg =true; } if(falg){ var isWho; if(from == IAMBUYER_PREFIX+thisUserId){ isWho = EASEMOBWEBSHOWTYPE.me; }else{ isWho = EASEMOBWEBSHOWTYPE.you; } //文本消息 if(EASEMOBTYPE.txt == msgType){ var sourceMsg = easemobJson.sourceMsg; sourceMsg = samilToImg(sourceMsg); sendText(isWho,sourceMsg,undefined,tempfalg); }else if(EASEMOBTYPE.img == msgType){//图片消息 var url = easemobJson.url; sendImg(isWho,url,undefined,tempfalg) }else if(EASEMOBTYPE.auto == msgType){//语音消息 }else if(EASEMOBTYPE.loc == msgType){//地址消息 var addr = easemobJson.addr; var lat = easemobJson.lat; var lng = easemobJson.lng; sendLoc(isWho,undefined,addr,lat,lng,tempfalg); }else if(EASEMOBTYPE.prodLink == msgType){自定义的消息 var proId = easemobJson.ext.proId; var proPice = easemobJson.ext.price; var proName = easemobJson.ext.productName; var proImg = easemobJson.ext.proImg; sendPro(isWho,proName,proPice,proId,proImg,undefined,tempfalg); } } //延时一秒。展示未读 setTimeout("getAllUnMsgCount()",1000); //延时一秒。展示未读 setTimeout("initUserList(false)",1000); }
根据上述代码。可以看见其实就是根据json聊天数据的类型。区分是什么样的数据。
调用不同的方法。 动态的拼接JS 生成在html中展示给用户
下面在贴一个sendText发送文本的方法
//文本消息封装 //true 追加到后面 false 追加到前面 function sendText(IsWho,txt,headImg,falg){ var html =""; headImg = setHeadImg(IsWho,headImg); var className = ""; if(IsWho == EASEMOBWEBSHOWTYPE.me){ className = "layim-chat-"+EASEMOBWEBSHOWTYPE.me; } html +='<li class="'+className+'">'; html +=' <div class="layim-chat-user">'; html +=' <img src="'+headImg+'">'; html +=' </div>'; html +=' <div class="layim-chat-text">'+txt+'</div>'; html +='</li>'; console.log("----追加文本"); console.log(html); appendOrPrepend(falg,html); }
从上面可以看到还有一个 appendOrPrepend方法。 这个方法其实就是获取更多聊天记录。 每次从redis中会取10条聊天记录出来。 从最上面的变量定义有一个index 这个index其实就是定义游标。 记录一下 聊天记录取到哪了。
其实每次点击一下聊天列表中的用户头像。就是把 toUserId进行一次替换。 游标也会进行清空。 现在帖一下初始化用户列表
//用户沟通列表初始化 //falg 需要不需要初始化聊天窗口 function initUserList(falg){ jQuery.ajax({ url : "selectChatByUserId/"+thisId, type : 'get', contentType : 'application/json;charset=UTF-8', success : function(data) { $("#chatListDiv").empty(); var html =""; console.log("-------初始化用户列表----") console.log(JSON.stringify(data)); for(var i = 0 ; i < data.content.length ; i ++){ var userHeadImg = data.content[i].userHeadImg; var userId = data.content[i].userId; var endTime = data.content[i].endTime; var userName = data.content[i].userName; var chatContent = data.content[i].chatContent; var unMsgCount = data.content[i].unMsgCount; //初始化聊天窗口以及沟通记录 if(falg){ if(i==0){ initChatWin(userId,userHeadImg); } } endTime = timeToDateStr(endTime); if(userHeadImg == undefined || userHeadImg == '' || userHeadImg == null || userHeadImg == 'null'){ userHeadImg = "../resources/img/caigoushang_headImg.png"; } html+=' <li class="clearfix" '; if(userId == toUserId){ html +='style = "background-color: #02c2a2;" '; } html+='onclick="onClikeChatUser('+userId+',\''+userHeadImg+'\')">'; html+=' <div class="chatUser layui-col-md12" style="display: inline-block">'; html+=' <img src="'+userHeadImg+'" alt="" class="layui-circle" width="40">'; html+=' <span class="chatUserName layui-col-md6 nowrap">'+userName+'</span>'; html+=' <span class="layui-col-md6 chatUserListTime nowrap">'+endTime+'</span>'; html+='<span class="layui-col-md11 nowrap chatAboutUser">'+chatContent+'</span>'; html+=' <div class="layui-row chatUserInfo">'; html+=' <span class="layui-col-md10 nowrap" style="visibility: hidden" > 1 </span>'; /*未读*/ html+=' <span class="layui-col-md2">'; if(unMsgCount > 0){ html+=' <span class="layui-badge">'+unMsgCount+'</span>'; } html+=' </span>'; html+=' </div>'; html+=' </div>'; html+=' </li>'; } $("#chatListDiv").append(html); }, error : function() { alert("FAIL!"); } }); }
用户列表这个方法很多地方都会调用
1、初始化的时候调用。
2、来消息作为刷新用户列表顺序以及增加未读量的时候调用
3、发送消息作为用户列表顺序刷新调用
下面继续贴代码。贴一下。 initChatWin()这个方法主要是初始化聊天窗口。其实就是去后台取聊天记录生成html出来
//初始化聊天窗口 function initChatWin(temptoUserId,temptoUserheadImg){ toUserId = temptoUserId; toUserHeadImg = temptoUserheadImg; //初始化聊天记录 jQuery.ajax({ url : "getChatRecordCount?toId="+toUserId+"&fromId="+thisId, type : 'get', contentType : 'application/json;charset=UTF-8', success : function(data) { if(data.ret == 200){ var length = data.content; //第一次初始化 /*if(index == -1){*/ index = length; /*}*/ jQuery.ajax({ url : "getChatRecord?toId="+toUserId+"&fromId="+thisId+"&index="+index, type : 'get', contentType : 'application/json;charset=UTF-8', success : function(data) { //递减游标 declinePage(); //上来先清空 $("#"+chatMsgContentDiv).empty(); if(data.ret == 200){ var contents = data.content.data; for(var i = 0 ; i < contents.length; i++){ //装载 resolveEasemobJson(contents[i],thisId,true); } }else{ layer.msg("暂无聊天记录"); } } }) }else{ layer.msg("暂无聊天记录"); } } }) //初始化聊天框中的产品信息 jQuery.ajax({ url : "selectChatProByUserId/"+toUserId+"/"+thisId, type : 'get', contentType : 'application/json;charset=UTF-8', success : function(data) { //上来先清空 $("#chatWinProInfo").empty(); $("#chatWinProList").empty(); if(data.ret == 200){ var contents = data.content; var html = ""; if(contents.length >= 1){ html += '<img src="'+contents[0].loopImg001+'" alt="" class="chatProductTopInfoImg">'; html += ' <span class="chatProductNumTopInfo nowrap layui-col-md12">商品编码:'+contents[0].productNum+'</span>'; html += '<span class="nowrap chatProductTitleTopInfo layui-col-md12">'+contents[0].productName+'</span>'; html += '<span class="chatProductPriceTopInfo layui-col-md12 nowrap">'+contents[0].productMinPrice+'-'+contents[0].productMaxPrice+'</span>'; html += '<div class="layui-row chatProductCreateTime">'; html += ' <span class="layui-col-md4 nowrap">发布者:'+contents[0].userName+'</span>'; html += ' <span class="layui-col-md8 nowrap">发布时间:'+contents[0].createTime+'</span>'; html += '</div>'; $("#chatWinProInfo").append(html); } var listHtml = ""; for(var i = 0 ; i < contents.length; i++){ //装载 listHtml +='<div class="chatProductTopInfo historyProductItem layui-col-md12" style="display: inline-block">'; listHtml +=' <img src="'+contents[i].loopImg001+'" alt="" class="chatProductTopInfoImg">'; listHtml +=' <span class="chatProductNumTopInfo nowrap layui-col-md12">商品编码:'+contents[i].productNum+'</span>'; listHtml +=' <span class="nowrap chatProductTitleTopInfo layui-col-md12">'+contents[i].productName+'</span>'; listHtml +=' <span class="chatProductPriceTopInfo layui-col-md12 nowrap">'+contents[i].productMinPrice+'-'+contents[i].productMaxPrice+'</span>'; listHtml +=' <div class="layui-row">'; //price,proId,proImg,productName var price = contents[i].productMinPrice+'-'+contents[i].productMaxPrice; listHtml +=' <button class="layui-btn ContinueChat" onclick="recordCount("'+price + '","'+ contents[i].loopImg001 + '","'+ contents[i].productName +'",'+ contents[i].proId +','+ toUserId +')">继续沟通</button>'; listHtml +=' </div>'; listHtml +='</div>'; }; $("#chatWinProList").append(listHtml); } } }) }
该方法主要就是初始化聊天列表的时候。需要递减游标。 首先呢。咱们把总的聊天长度取出来。 然后赋值给游标。 这样就可以通过这个数据去后台取聊天记录啦。
比如有20个聊天记录
传递到后台的记录是 20. 后台从redis中。拿到 10-19下标的数据。 生成出来就好啦。
大家看一下 这里生成的时候调用了 resolveEasemobJson方法。
这就是数据统一的好处。 将JSON解析到html统一调用resolveEasemobJson方法
贴一个递减游标的方法
//递减游标 function declinePage(){ //减去分页 index = index-10; if(index < 0){ index= 0; } }
追加的方法。其实就是。。初始化在后面。, 获取聊天记录呢。是在前面
//追加 //falg true 追加到后面, 追加到前面 //查看聊天记录 或者 发送聊天使用。 function appendOrPrepend(falg,html){ if(falg){ $("#"+chatMsgContentDiv).append(html); scrollDown(); }else{ $("#"+chatMsgContentDiv).prepend(html); } }
这里追加到后面有一个样式控制。就是每次追加。让滚动条保持在最下面
//控制滚动条保持在最下面 function scrollDown(){ setTimeout("timeOutScrollDown()",200) } function timeOutScrollDown(){ var scrollHeight = $('#chatMsgContentDivScroll').prop("scrollHeight"); $('#chatMsgContentDivScroll').scrollTop((scrollHeight+200)); scrollHeight = $('#chatMsgContentDivScroll').prop("scrollHeight"); }
在贴出获取分页的方法
其实这个就是获取聊天记录后。倒着循环。装载到html的前面。
//获取分页聊天记录 function getChatPageList(){ jQuery.ajax({ url : "getChatRecord?toId="+toUserId+"&fromId="+thisId+"&index="+index, type : 'get', contentType : 'application/json;charset=UTF-8', success : function(data) { //递减游标 declinePage(); if(data.ret == 200){ var contents = data.content.data; //倒着循环 for(var i = contents.length ; i >= 0; i--){ //装载 resolveEasemobJson(contents[i-1],thisId,false); } }else{ layer.msg("暂无聊天记录"); } } }) }
以上代码呢。其实就是可以做到了。
初始化用户列表, 接收android推送过来的环信的消息并展示啦。
接下来。贴一下发送的前端逻辑吧。
// 私聊发送文本消息,发送表情同发送文本消息,只是会在对方客户端将表情文本进行解析成图片 var sendPrivateText = function (content,toId) { var id = conn.getUniqueId(); var msg = new WebIM.message('txt', id); msg.set({ msg: content, // 消息内容 to: IAMBUYER_PREFIX+toId, // 接收消息对象 roomType: false, success: function (id, serverMsgId) { console.log("send private text Success"); console.log(serverMsgId); console.log(id); var easemobJSON = convertTxt(serverMsgId,toId,msg.value); resolveEasemobJson(easemobJSON,thisId,true); } }); msg.body.chatType = 'singleChat'; conn.send(msg.body); console.log(JSON.stringify(msg)); }; // 私聊发送图片消息 var sendPrivateImg = function (imgId,to,size) { var id = conn.getUniqueId(); var msg = new WebIM.message('img', id); var input = document.getElementById(imgId); // 选择图片的input var file = WebIM.utils.getFileUrl(input); // 将图片转化为二进制文件 var allowType = { 'jpg': true, 'gif': true, 'png': true, 'bmp': true }; var option = { apiUrl: WebIM.config.apiURL, file: file, to: IAMBUYER_PREFIX+to, roomType: false, chatType: 'singleChat', onFileUploadError: function () { console.log('onFileUploadError'); }, onFileUploadComplete: function () { console.log('onFileUploadComplete'); }, success: function (id, serverMsgId) { console.log('Success'); console.log(serverMsgId); console.log(id); console.log(JSON.stringify(msg)); var url = msg.body.body.url; var filename = msg.body.body.filename; var secret = msg.body.body.secret; var easemobJSON = convertImg(serverMsgId,to,filename,url,secret,size); resolveEasemobJson(easemobJSON,thisId,true); }, }; // for ie8 try { if (!file.filetype.toLowerCase() in allowType) { console.log('file type error') return } } catch (e) { option.flashUpload = WebIM.flashUpload } msg.set(option); conn.send(msg.body); console.log("111"); console.log(JSON.stringify(msg)); console.log("222"); };
发送我贴了2个。 一个是txt , 一个是发送img
这里需要注意的就是
convertTxt 这个方法。
其实呢。 环信webIM这块做的就比较尴尬。 它回调跟发送生成出来的JSON不一致,因为咱们采用了他回调的JSON格式进行处理。 所以需要将发送的JSON转成咱们使用的JSON格式
//因为环信发送 跟 回调的JS不一致 //我们需要将每一条聊天记录扔到redis中。所以将发送的记录转换成可以扔的数据 function convertTxt(id,to,sourceMsg){ var easemobJson = { "id": id, "type": "chat", "from": IAMBUYER_PREFIX + thisId, "to": IAMBUYER_PREFIX + to, "ext": { }, "sourceMsg": sourceMsg, "error": false, "errorText": "", "errorCode": "" }; insertRedis(easemobJson,EASEMOBTYPE.txt); return easemobJson; } //因为环信发送 跟 回调的JS不一致 //我们需要将每一条聊天记录扔到redis中。所以将发送的记录转换成可以扔的数据 function convertImg(id,to,filename,url,secret,size){ var easemobJson = { "id": id, "type": "chat", "from": IAMBUYER_PREFIX + thisId, "to": IAMBUYER_PREFIX + to, "url": url, "secret": secret, "filename": filename, "file_length": size, "width": 0, "height": 0, "filetype": "", "accessToken": "", "ext": { }, "error": false, "errorText": "", "errorCode": "" } insertRedis(easemobJson,EASEMOBTYPE.img); return easemobJson; }
这样处理一下就好了!!
我在这里说明一下表情的转换。下面是表情图片的定义
var smileS = { '\\[Smile\\]':'ee_1.png', '\\[Happy\\]':'ee_2.png', '\\[Blink\\]':'ee_3.png', '\\[surprised\\]':'ee_4.png', '\\[Tongue\\]':'ee_5.png', '\\[Sunglasses\\]':'ee_6.png', '\\[anger\\]':'ee_7.png', '\\[purse\\]':'ee_8.png', '\\[Shy\\]':'ee_9.png', '\\[Unhappy\\]':'ee_10.png', '\\[weep\\]':'ee_11.png', '\\[daze\\]':'ee_12.png', '\\[Snowman\\]':'ee_13.png', '\\[Curse\\]':'ee_14.png', '\\[doctor\\]':'ee_15.png', '\\[pout\\]':'ee_16.png', '\\[sweets\\]':'ee_17.png', '\\[sleepy\\]':'ee_18.png', '\\[Pie\\]':'ee_19.png', '\\[Shut\\]':'ee_20.png', '\\[Whisper\\]':'ee_21.png', '\\[Frown\\]':'ee_22.png', '\\[Lookdown\\]':'ee_23.png', '\\[heart\\]':'ee_24.png', '\\[Heartbroken\\]':'ee_25.png', '\\[Moon\\]':'ee_26.png', '\\[Stars\\]':'ee_27.png', '\\[sunlight\\]':'ee_28.png', '\\[Rainbow\\]':'ee_29.png', '\\[colour\\]':'ee_30.png', '\\[Kiss\\]':'ee_31.png', '\\[Redlips\\]':'ee_32.png', '\\[Rose\\]':'ee_33.png', '\\[Goldleaf\\]':'ee_34.png', '\\[Fabulous\\]':'ee_35.png' };
//表情 图片转表情 function imgToSamil(htmlmsg){ //处理第一层 删除除img标签以外的所有标签 var regex = /<\/?((?!img).)*?\/?>/g; htmlmsg = htmlmsg.replace(regex,""); htmlmsg=htmlmsg.replace(/ /ig,'');//去掉 //处理第二层 将img转换成固定的符号 for (var smile in smileS) { var key = smile.replace("\\","").replace("\\",""); var val = smileS[smile]; var head='<\\/?((img).*?)(('; var food=').*?)\\/?>'; /*var ImgRegex = /<\/?((img).*?)((ee_1.png).*?)\/?>/g;*/ var ImgRegex = new RegExp(head+val+food,'g'); htmlmsg = htmlmsg.replace(ImgRegex,key); } return htmlmsg; } //表情 表情转图片 function samilToImg(htmlmsg){ //将表情转换成图片 for (var smile in smileS) { var key = smile; var val = smileS[smile]; /*var ImgRegex = /<\/?((img).*?)((ee_1.png).*?)\/?>/g;*/ if(htmlmsg.indexOf(key.replace("\\","").replace("\\","")) != -1){ // 包含 /*var ImgRegex = new RegExp(key,'gm'); console.log(key); console.log(val); htmlmsg = htmlmsg.replace(ImgRegex,val);*/ console.log(key); console.log(val); var imgHtml = '<img src="../resources/webIm/smile/'+val+'" />' htmlmsg = htmlmsg.replace(new RegExp(key,'gm'),imgHtml); } } return htmlmsg; }
resolveEasemobJson中会使用到samilToImg 将表情字符串转换成图片
在自己给对方发送消息的时候会使用到 讲图片转成表情
//发送消息 function sendChatMsg(){ var htmlmsg = $("#msgBox").html(); sendPrivateText(imgToSamil(htmlmsg),toUserId); $("#msgBox").empty(); $("#msgBox").focus(); }
思路其实就是:我发送的时候呢。 会先设置到待发送框中。 然后点击发送会获取数据。 这时。需要将 img标签转换成 固定的表情字符。 然后调用resolveEasemobJson生成到聊天框中。 生成的时候在转换回来。
这样转换其实是因为要给android推送消息。 推送的消息一定是表情字符而不是 img图片。
在做这里的时候 说明一下
htmlmsg.indexOf的效率比 match 方法高很多。 我在跑match的时候经常循环的时候浏览器会挂掉。
直接用replace 进行循环也会挂掉。
所以这里先用indexOf判断。如果存在在进行替换。这样才OK
好的!兄弟们。。你们如果能看到这。。我谢谢你们。
因为。。。咱们终于!!
要开始贴后端代码了!!!
-------------------------------------------------------------------------------
先来保存聊天记录的控制层
@RequestMapping("/saveChatRecord") public void saveChatRecord(HttpServletRequest request , @RequestBody EasemobChatWebMsgVO easemobChatWebMsgVO){ taskExecutor.execute(new EasemobChatThread(easemobService,easemobChatWebMsgVO)); }
这里呢。我单起了一个线程异步的去放聊天记录。这样前端是异步。后端也是异步。用户基本无感知。
逻辑层代码
public void saveChatRecordRedis(EasemobChatWebMsgVO easemobChatWebMsgVO){ String to = easemobChatWebMsgVO.getTo(); String from = easemobChatWebMsgVO.getFrom(); //key 使用 toID&&fromID 组成 String key =IamBuyerRedisKey.getRedisChatKey1(to,from); String key1 =IamBuyerRedisKey.getRedisChatKey2(to,from); //定义标量确定是否放入缓存成功 boolean falg = false; //正反拼查询ID是否存在 if(redisUtil.hasKey(key)){ //存在就扔进缓存中 synchronized (this) { redisUtil.lSet(key, easemobChatWebMsgVO,EXPIRY_TIME); falg = true; } }else{ if(redisUtil.hasKey(key1)){ synchronized (this) { redisUtil.lSet(key1, easemobChatWebMsgVO,EXPIRY_TIME); falg = true; } } } //没扔进缓存中定义变量扔进缓存中 if(!falg){ //防止同一时间创建集合进行覆盖 synchronized (this){ redisUtil.lSet(key,easemobChatWebMsgVO,EXPIRY_TIME); } } }
KEY的拼接规则我就不说了。。 大家自己根据自己的喜好拼接吧。。
在贴出一个获取聊天记录的吧!
@Override public List<Object> getChatRecord(Integer toId, Integer fromId, Integer index) { List<Object> list = new ArrayList<>(); //key 使用 toID&&fromID 组成 String key = IamBuyerRedisKey.getRedisChatKey1(IamBuyerRedisKey.IMABUYER_CHAT+toId,IamBuyerRedisKey.IMABUYER_CHAT+fromId); String key1 = IamBuyerRedisKey.getRedisChatKey2(IamBuyerRedisKey.IMABUYER_CHAT+toId,IamBuyerRedisKey.IMABUYER_CHAT+fromId); long statrIndex = (index-LENGTH); long endIndex = (index-1); if(statrIndex < 0){ statrIndex = 0; } if(endIndex < 0){ endIndex = 0; } System.out.println("本次查询游标为:"+statrIndex + "-" + endIndex); //正反拼查询ID是否存在 if(redisUtil.hasKey(key)){ //取出集合 list = redisUtil.lGet(key, statrIndex, endIndex); }else{ if(redisUtil.hasKey(key1)){ list = redisUtil.lGet(key1,statrIndex,endIndex); } } return list; }
这个比较简单啦。就是根据前端传递过来的游标值进行redis中查询数据。
继续贴代码。在贴出一个获取聊天列表的代码
//获取聊天列表 以及聊天列表的最后一句话 @Override public List<ChatUserVO> selectChatByUserId(Integer userId) { List<ChatUserVO> chatUserVOS = commRecordMapper.selectChatList(userId); //循环装载最后一条聊天记录 以及 聊天时间 for (ChatUserVO chatUserVO : chatUserVOS) { String userHeadImg = chatUserVO.getUserHeadImg(); if(!StringUtils.isEmpty(userHeadImg)){ try { chatUserVO.setUserHeadImg(UploadUtil.getHttpFilePath(userHeadImg)); } catch (Exception e) { e.printStackTrace(); } } String fromId = IamBuyerRedisKey.IMABUYER_CHAT + chatUserVO.getThisId(); String toId = IamBuyerRedisKey.IMABUYER_CHAT + chatUserVO.getUserId(); String redisChatKey1 = IamBuyerRedisKey.getRedisChatKey1(fromId, toId); String redisChatKey2 = IamBuyerRedisKey.getRedisChatKey2(fromId, toId); //未读消息 String redisChatCountKey1 = IamBuyerRedisKey.getRedisChatCountKey1(fromId, toId); String redisChatCountKey2 = IamBuyerRedisKey.getRedisChatCountKey2(fromId, toId); //有数据的key 聊天的key String redisChatkey = ""; //未读消息的key String redisCahtCountKey = ""; //最后一条记录 String chatEndContent = ""; //最后一条记录的时间 long chatEndTime = 0; //正反拼查询ID是否存在 if(redisUtil.hasKey(redisChatKey1)){ //存在就把缓存中的最后一条记录取出来 redisChatkey = redisChatKey1; }else{ if(redisUtil.hasKey(redisChatKey2)){ //存在就把缓存中的最后一条记录取出来 redisChatkey = redisChatKey2; } } //正反拼查询ID是否存在 未读消息 if(redisUtil.hasKey(redisChatCountKey1)){ redisCahtCountKey = redisChatCountKey1; }else{ if(redisUtil.hasKey(redisChatCountKey2)){ redisCahtCountKey = redisChatCountKey2; } } //装载聊天记录 if(!"".equals(redisChatkey)){ long listSize = redisUtil.lGetListSize(redisChatkey); Object o = redisUtil.lGetIndex(redisChatkey, listSize - 1); if(o instanceof EasemobChatWebMsgVO){ EasemobChatWebMsgVO easemobChatWebMsgVO = (EasemobChatWebMsgVO) o; //装载最后一条的时间 chatEndTime = easemobChatWebMsgVO.getExt().getTimestamp(); //装载数据类型 String msgType = easemobChatWebMsgVO.getMsgType(); if(msgType.equals(KorChatTypeEnum.TXT.getDesc())){ chatEndContent =easemobChatWebMsgVO.getSourceMsg(); }else if(msgType.equals(KorChatTypeEnum.IMG.getDesc())){ chatEndContent ="图片消息"; }else if(msgType.equals(KorChatTypeEnum.LOC.getDesc())){ chatEndContent ="地址消息"; }else if(msgType.equals(KorChatTypeEnum.AUDIO.getDesc())){ chatEndContent ="语音消息"; }else if(msgType.equals(KorChatTypeEnum.PROD_LINK.getDesc())){ chatEndContent ="商品连接消息"; }else if(msgType.equals(KorChatTypeEnum.PROD.getDesc())){ chatEndContent ="商品链接消息"; } } } //装载未读消息 if(!"".equals(redisCahtCountKey)){ AtomicLong unMsgCount = (AtomicLong) redisUtil.get(redisCahtCountKey); chatUserVO.setUnMsgCount(unMsgCount.longValue()); } if("".equals(chatEndContent)){ chatEndContent = "三天内暂无记录"; } chatUserVO.setChatContent(chatEndContent); if(chatEndTime != 0 ){ //对比继续沟通的时间 跟 最后一条聊天记录的时间。 谁大 。 谁大就用谁的 if(chatEndTime > chatUserVO.getEndTime().getTime()){ chatUserVO.setEndTime(new Date(chatEndTime)); } } } //排序 Collections.sort(chatUserVOS, new ChatUserVO()); return chatUserVOS; }
这里因为有我的业务逻辑存在。我简单说明一下
我会先从数据库中取出来用户沟通的记录。 获取一个初始的聊天列表。然后我会根据这2个人的ID拼接出redis中存储聊天记录的key进行查询聊天记录。 把最后一条聊天记录放到集合中。。 最后按照最后的聊天时间进行排序。这个集合。
好啦。以上的代码的思路就可以实现出来
发送与接收文字、表情、图片、地址消息、自定义消息 --》拉取聊天记录 (三天内的)
今天写累了。 明天继续写