公司有需求做一个聊天功能。 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,productName
var 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); // 选择图片的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进行查询聊天记录。 把最后一条聊天记录放到集合中。。 最后按照最后的聊天时间进行排序。这个集合。
好啦。以上的代码的思路就可以实现出来
发送与接收文字、表情、图片、地址消息、自定义消息 --》拉取聊天记录 (三天内的)
今天写累了。 明天继续写