公司有需求做一个聊天功能。 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+='οnclick="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,productNamevar price = contents[i].productMinPrice+'-'+contents[i].productMaxPrice;listHtml +='        <button class="layui-btn ContinueChat" οnclick="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);               // 选择图片的inputvar 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 ie8try {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的拼接规则我就不说了。。 大家自己根据自己的喜好拼接吧。。

在贴出一个获取聊天记录的吧!

 @Overridepublic 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中查询数据。

继续贴代码。在贴出一个获取聊天列表的代码

 //获取聊天列表 以及聊天列表的最后一句话@Overridepublic 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 聊天的keyString redisChatkey = "";//未读消息的keyString 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进行查询聊天记录。 把最后一条聊天记录放到集合中。。 最后按照最后的聊天时间进行排序。这个集合。

好啦。以上的代码的思路就可以实现出来

发送与接收文字、表情、图片、地址消息、自定义消息  --》拉取聊天记录 (三天内的)

今天写累了。 明天继续写

JavaWeb聊天(Redis+环信) 一、发送接收消息、聊天记录拉取相关推荐

  1. 环信服务器发送消息ext,环信服务器发送消息

    环信服务器发送消息 内容精选 换一换 5G消息开发有两种接入方式:通过直接调用API接口详情请参见开发流程.代码样例详情请参见开发流程.代码样例通过应用魔方实现接入详情请参考进入AppCube开发环境 ...

  2. Android 环信IM接受离线消息

    Android 环信IM接受离线消息 已经按照官方文档上面写了,结果还是没有接受到离线消息,但是离线消息确实显示在了列表里面. 实际上 android 环信IM是有这个方法的,只是平时没有开启而已.我 ...

  3. ActiveMQ 部署及发送接收消息

    ActiveMQ 部署及发送接收消息 一.           下载 下载地址:http://activemq.apache.org/ 我这里使用的版本为当前最新5.8.0. 下载版本有Windows ...

  4. 【Spring】使用Spring和AMQP发送接收消息(下)

    为什么80%的码农都做不了架构师?>>>    上篇讲了RabbitMQ连接工厂的作用是用来创建RabbitMQ的连接,本篇就来讲讲RabbitMQ的发送消息.通过RabbitMQ发 ...

  5. vue 环信im 发送图片、接收图片

    1.使用input的file选择器获取图片,accept是移动端写法,不加image在移动端无法调起 <input type="file" ref="img&quo ...

  6. iOS 即时视频和聊天(基于环信)

    先上效果图: 屏幕快照 2015-07-30 下午5.19.46.png 说说需求:开发一个可以进行即时视频聊天软件. 最近比较忙,考完试回到公司就要做这个即时通信demo.本来是打算用xmpp协议来 ...

  7. 环信服务器发送消息ext,发送消息

    发送消息 聊天相关 API,可以发送文本消息,发送图片消息,发送语音消息,发送视频消息,发送透传消息和发送扩展消息. 发送文件类型消息要先把文件上传到环信服务器,参考文档:文件上传下载 REST接口发 ...

  8. 环信:发送头像和昵称(从消息扩展中获取)

    昵称和头像 注意:以下是在官方Demo3.0的基础上修改的.官方Demo下载地址 环信提供了获取头像和昵称的两种方式: 方法一 从APP服务器获取昵称和头像 昵称和头像的获取:当收到一条消息(群消息) ...

  9. openfire android 发送图片,基于openfire+smack开发Android即时聊天应用[四]-单人聊天、群聊、发送接收文件等...

    这篇文章主要介绍如何实现点对点单人聊天.多人的群聊.以及如何给对方发送文件,如何发送图片消息和语音消息等功能. 1.单人聊天 1.首先创建聊天对象 /** * 创建聊天窗口 * @param jid ...

最新文章

  1. 设计模式(享元模式)
  2. 音频处理九:(参数估计)
  3. boost::fusion::any用法的测试程序
  4. idea配置Maven的本地仓库(打开新的项目时自动更新本地仓库的位置)
  5. voxblox建图教程
  6. Java笔试面试练习题---集合
  7. 大数据日志分析项目架构
  8. 获取字符串全排列 或者 只输出k个的组合
  9. centos 6 apt.sw.be 错误 无法yum安装软件解决方案
  10. 冒号在MATLAB里基本意思
  11. 【转】Java集合间的相互转换
  12. python 自动下载脚本_Python实现115网盘自动下载的方法
  13. 计算机快捷键换行,excel换行快捷键 excel中自动换行的快捷键是什么
  14. 全局最小割集Stoer-Wagner算法
  15. 大型网站架设,LMP+Nginx负载均衡+Keepalived热备+Ceph存储集群架构+Web动静分离架构
  16. mysql 备份数据库结账_年度结转问题综合解答(转)
  17. Elasticsearch:理解 Master,Elections,Quorum 及 脑裂
  18. 一次完整的JVM堆外内存泄漏故障排查记录
  19. 变色龙哈希函数-区块链
  20. 2022登高架设考试题及模拟考试

热门文章

  1. 51单片机实验——按键外部中断实现四进制计数器
  2. 微信共享停车场小程序开发设计方案
  3. react native 研究
  4. kepserver在设备上添加项目失败_隔空投送存储项目失败怎么办
  5. Stacked Attention Networks for Image Question Answering(用于图像问答的堆叠注意力网络)
  6. QP/区块链服务器被攻击了怎么办?
  7. 从val_loss,train_loss,test_loss中产生的问题
  8. 炒菜机器人的弊端_家用炒菜机器人好用吗?都说买了全自动炒菜机后悔了。。...
  9. 使用d3画横向组织架构图,兼容ie8(一)
  10. c语言 计算子网掩码位数,子网数、主机数与子网掩码的关系