基于融云api开发客服聊天功能
以下所有内容均为本人自己开发总结的经验,如有雷同,不胜荣幸!
最近公司在做一款app,app里面涉及到与客服聊天功能。
于是公司就接入了融云api聊天系统。
app端(前端)聊天由专业的ios andorid开发人员完成,我负责开发后台客服聊天即可。
目前使用的是ssm 4.0框架,jdk1.8
框架就自行搭建了。能运行就行。下面就详细聊聊接入融云,并开发聊天。
注:融云是收费的。各位看官如果是使用收费模式,则不需要观看该文章,因为我是自行开发的。
涉及功能:1对1聊天,聊天记录存储,会话列表,未读消息,是否在线(这些功能属于后台,前端的我不管。)
1:1vs1 聊天,(从app端看来是用户对客服1vs1,从客服对app端则是1vs n)(n:即多个用户,所以这边会有会话聊天列表)
因为只有1对1聊天是免费的。所以除了1对1聊天,其他功能全部本地开发,并未使用融云api。
融云api配置我就不说了。网上一大堆。随便看看就ok。
配置只有一点要注意:
这个要放进来。这个是融云的聊天格式,
不论是前端还是后端。用户聊天都必须使用token 这是融云要求的。所以获得token是服务端完成的。
如果上面的图片没有放入到项目中,那么你服务端获取token是失败的。
这个坑是摸索半天,看了半天的源码才搞通
String userToken = UserExample.returnToken(users.getUsers_only_md5(), usersInfo.getUsers_info_nick(),RongYunKeyEnum.APPKEY.getValue(),
RongYunKeyEnum.APPSECRET.getValue());
req.setAttribute("userToken", userToken);
returnToken 是我在 UserExample 类里自己封装了方法,当参数传进来而已。自己随便写就行。
服务端token写好了。页面端需要2个值, token 和appKey 用request 传作用域就行
记得返回到聊天的页面来。
融云是用id 作为聊天的唯一对象。所以我这边用自己数据库的唯一列作为传递到融云的id值。所以我传递到页面上,方面2人聊天。
我这边解释下,我是本地跟本地聊天。并未涉及到和app聊天。所以我要区分,该登录是用户还是客服。可以用2个登录方法登录页面来实现。(identity:区分身份(users/manager))
<script type="text/javascript">
$(document).keyup(function(event){
if(event.keyCode ==13){
privateMessage();
}
});
var instance = null;
var userInfo = {
appKey : "${appKey}",
token : "${userToken}"
};
function showTips(data){
var dom = document.getElementById("show");
dom.style.display = "block";
if(data){
dom.innerHTML += "<li>" + data + "</li>";
}else{
dom.innerHTML = "";
}
}
var callbacks = {
getInstance : function(_instance){
instance = _instance;
},
receiveNewMessage : function(message){
// 判断消息类型
showTips("新消息,类型为:" + message.messageType);
// showResult("新消息",message,start);
console.log("messageUId:" + message.messageUId + ", messageId:" + message.messageId);
console.log(message);
},
getCurrentUser : function(userInfo){
userId = userInfo.userId;
$("#login").val(userId);
//afterConnected();
}
};
init(userInfo, callbacks);
</script>
页面上这段js 用来初始化融云 或者说用户是否登录成功了。可以F12查看的。
聊天:就是。我发消息给你,你收到消息,你发消息给我,我收到消息。
var recallMessage = null, clearMessage;
function markMessage(message){
recallMessage = message;
clearMessage = message;
}
// 消息监听器
RongIMClient.setOnReceiveMessageListener({
// 接收到的消息
onReceived: function (message) {
// 判断消息类型
console.log(message.content.content);
if("附加信息" == message.content.extra){
//这边要修改成
//是否跟当前用户在聊天
//判断用户来增加未读数量还是显示在当前了聊天框中
//所以一下该function需要进行修改。
//getNewMsgUser();
}
//有新消息进来的时候,先进入数据库执行,该用户是否能接收消息,
//如果不能接收,则进行未读消息数量修改,
//否则直接进入聊天界面
var htmls = "<div class=\"leftd\">"+
"<div style=\"float:left;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech left\" style=\"margin-left:61px;\">"+message.content.content+"</div>"+
"</div>";
$("#liaotian").append(htmls);
$("#getMessage").val(message.content.content);
//message接受到的消息(包含发送的信息,也可以在extra中添加要传递的值,如:时间等)
$('#liaotian').scrollTop($('#liaotian')[0].scrollHeight);
}
});
该方法是用来接收有人发消息过来。中间有一段注释的内容。我还在开发中,你们也可以自己想想,文字描述很清晰
//私聊
function privateMessage(){
var sendMsg = $("#sendMessage").val();
if("" == sendMsg){
alert("请输入聊天内容");
$('#sendMessage').focus();
return;
}
var conversationtype = RongIMLib.ConversationType.PRIVATE; // 私聊
var targetId = $("#toUserId").val(); // 目标 Id
var msg = new RongIMLib.TextMessage({content:sendMsg,extra:"附加信息"});
var conversationtype = RongIMLib.ConversationType.PRIVATE; // 单聊,其他会话选择相应的消息类型即可。
var pushData = "your pushInfo";//这个暂时还没研究
RongIMClient.getInstance().sendMessage(conversationtype, targetId, msg, {
onSuccess: function (message) {
//发送成功后,将该条发送的消息记录到数据库中
//如果数据库插入成功,则进行页面的发送消息append到html聊天框里。
var htmls = "<div class=\"rightd\">"+
"<div style=\"float:right;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech right\" style=\"margin-right:61px;\">"+message.content.content+"</div>"+
"</div>";
$("#liaotian").append(htmls);
var sendMsg = $("#sendMessage").val("");
console.log(message.content.content);
$('#liaotian').scrollTop($('#liaotian')[0].scrollHeight);
},
onError: function (errorCode,message) {
console.log(message.content.content);
}
}, false, pushData
);
}
该方法就是我发送消息,发送聊天内容等
//获取会话列表
function findMsgUser(){
var currUserId = '${currUserId}';
$.get("${ctx}/webMessageUserList/selectWebMessageUserList.htmls",{only_md5:'${currUserId}'},function(data){
var objList = eval(data);
var htmls = "";
if(objList.code == 20000){
for(i=0;i<objList.entity.length;i++){
var b = objList.entity[i].users_only_md5;
if(objList.entity[i].users_only_md5 != currUserId){
htmls += "<div style=\"width:180px;height:60px;\" onclick=\"pushUser('"+objList.entity[i].users_only_md5+"')\">"+
"<img alt=\"用户头像\" width=\"40px\" height=\"40px\" src=\""+objList.entity[i].users_info_photo+"\">"+
"<label>"+objList.entity[i].users_info_nick+"</label>"+
" <label style=\"color:red;\">"+objList.entity[i].web_message_user_list_read_count+"</label></div>";
}else{
htmls += "<div style=\"width:180px;height:60px;\" onclick=\"pushMaster('"+objList.entity[i].master_only_md5+"')\">"+
"<img alt=\"用户头像\" width=\"40px\" height=\"40px\" src=\""+objList.entity[i].users_info_photo+"\">"+
"<label>"+objList.entity[i].users_info_nick+"</label>"+
" <label style=\"color:red;\">"+objList.entity[i].web_message_user_list_read_count+"</label></div>";
}
}
}else{
var htmls = "<div style=\"width:180px;height:60px;\">"+
"<img alt=\"用户头像\" width=\"40px\" height=\"40px\" src=\"${ctx}/img/20180917141606.png\">"+
"<label>暂无会话用户</label></div>";
}
$("#newMsgUser").html(htmls);
});
}
该方法是获取会话列表。因为融云的会话列表是收费的。所以我自己写一个会话列表:
需要创建数据库,然后将客服与用户聊天创建一条会话数据,作为聊天的列表模式,不需要重复创建,需要自己判断,
怎么创建这个数据呢。首先。客服是不能主动找未聊天过的 用户聊!!!!即只有app端先发起客服聊天,客服才有资格去回复。
所以会话列表的数据创建,应该有app端发起时,在接口处去创建会话列表数据。
:目前这一块我还没有写。所以自己创建的几个临时的数据,
而且我这边是本地跟本地聊,即打开2个浏览器去聊。所以暂时还没去写,不过,并不难。
//客服跟用户聊天
function pushUser(users_only_md5){
if(toUserId != users_only_md5){
toUserId = users_only_md5;
hisHtmls = "";
$("#liaotian").html(hisHtmls);
$("#toUserId").val(users_only_md5);
getHisMsgUser(users_only_md5);
}
}
//用户跟客服聊天
function pushMaster(master_only_md5){
if(toUserId != master_only_md5){
toUserId = master_only_md5;
hisHtmls = "";
$("#liaotian").html(hisHtmls);
$("#toUserId").val(master_only_md5);
getHisMsgMaster(master_only_md5);
}
}
有以上2个方法,是因为我用2个浏览器去聊天的,所以要区分开用户和客服。
//获取跟该用户的历史聊天记录
function getHisMsgUser(users_only_md5){
if(regNull(hisHtmls)){
$.get("${ctx}/webMessage/selectWebMessageUsers.htmls",
{
users_only_md5:users_only_md5,
master_only_md5:'${currUserId}'
},
function(data){
var obj = eval(data);
pushHTML(obj.entity,"users");
});
}
}
//获取跟该客服的历史聊天记录
function getHisMsgMaster(master_only_md5){
if(regNull(hisHtmls)){
$.get("${ctx}/webMessage/selectWebMessageUsers.htmls",
{
users_only_md5:'${currUserId}',
master_only_md5:master_only_md5
},
function(data){
var obj = eval(data);
pushHTML(obj.entity,"master");
});
}
}
以上2个方法的初衷,主要取决于是谁先点。用户界面点客服聊天,和客服界面点用户聊天。
//将获得的聊天记录布局到html中
function pushHTML(list,obj){
if("error" == list){
hisHtmls = "暂无历史聊天记录";
}else{
list = eval(list);
for(i=list.length-1;i>=0;i--){
if(obj != "users"){
if(regNull(list[i].user_content)){
//管理员发送
hisHtmls += "<div class=\"leftd\">"+
"<div style=\"float:left;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech left\" style=\"margin-right:61px;\">"+list[i].master_content+"</div>"+
"</div>";
}else{
//用户发送
hisHtmls += "<div class=\"rightd\">"+
"<div style=\"float:right;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech right\" style=\"margin-right:61px;\">"+list[i].user_content+"</div>"+
"</div>";
}
}else{
if(regNull(list[i].user_content)){
//管理员发送
hisHtmls += "<div class=\"rightd\">"+
"<div style=\"float:right;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech right\" style=\"margin-right:61px;\">"+list[i].master_content+"</div>"+
"</div>";
}else{
//用户发送
hisHtmls += "<div class=\"leftd\">"+
"<div style=\"float:left;\">"+
"<img src=\"../img/20180917141606.png\" style=\"width:40px;height:40px;\"/>"+
"</div>"+
"<div class=\"speech left\" style=\"margin-right:61px;\">"+list[i].user_content+"</div>"+
"</div>";
}
}
}
}
$("#liaotian").append(hisHtmls);
//该jquery是为了让滚动条一直居底部
$('#liaotian').scrollTop($('#liaotian')[0].scrollHeight);
}
<script type="text/javascript">
/**初始化页面所加载的数据*/
//获取会话列表
findMsgUser();
</script>
<body>
登录用户ID:
<input type="text" id="login" value="${currUserId}" readonly="readonly"/> 目标ID:
<input type="text" id="toUserId" value="" readonly="readonly"/>
<input type="text" id="identity" value="${identity}" readonly="readonly"/></p>
<div style="width:610px;height:500px;">
<div id="liaotian" style="border: 1px solid red;width:400px;height:500px;overflow-x: hidden;overflow-y: auto;float:left;">
</div>
<div id="newMsgUser" style="border:1px solid red;width:200px;height:500px;float:right;overflow-x: hidden;overflow-y: auto;">
</div>
</div>
</p>
<div style="width:400px;height:50px">
<div style="width:300px;height:49px;float: left;">
<textarea id="sendMessage" rows="" cols=""
style="width: 320px;height:49px"></textarea>
</div>
<div style="width:80px;height:49px;float:right;">
<input type="button" id="push" value="发送"
style="width:80px;height:49px;" onclick="privateMessage()" />
</div>
</div>
</body>
<script src="${ctx}/rong/RongIMLib-2.3.3.min.js"></script>
<script src="//cdn.ronghub.com/RongEmoji-2.2.4.min.js"></script>
<script src="//cdn.ronghub.com/RongIMVoice-2.2.4.min.js"></script>
<script src="${ctx}/rong/init.js"></script>
<script src="${ctx}/js/jquery-2.1.4.min.js"></script>
<script src="${ctx}/common/common.js"></script>
<style type="text/css">
div.speech {
margin: 10px 0;
padding: 8px;
table-layout: fixed;
word-break: break-all;
position: relative;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, from(#ffffff),
color-stop(0.1, #ececec), color-stop(0.5, #dbdbdb),
color-stop(0.9, #dcdcdc), to(#8c8c8c));
border: 1px solid #989898;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
}
div.speech:before {
content: '';
position: absolute;
width: 0;
height: 0;
left: 15px;
top: -20px;
border: 10px solid;
border-color: transparent transparent #989898 transparent;
}
div.speech:after {
content: '';
position: absolute;
width: 0;
height: 0;
left: 17px;
top: -16px;
border: 8px solid;
border-color: transparent transparent #ffffff transparent;
}
div.speech.right {
box-shadow: -2px 2px 5px #CCC;
margin-right: 10px;
width: 75%;
float: right;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, from(#e4ffa7),
color-stop(0.1, #bced50), color-stop(0.4, #aed943),
color-stop(0.8, #a7d143), to(#99BF40));
}
div.speech.right:before {
content: '';
position: absolute;
width: 0;
height: 0;
top: 9px;
bottom: auto;
left: auto;
right: -10px;
border-width: 9px 0 9px 10px;
border-color: transparent #989898;
}
div.speech.right:after {
content: '';
position: absolute;
width: 0;
height: 0;
top: 10px;
bottom: auto;
left: auto;
right: -8px;
border-width: 8px 0 8px 9px;
border-color: transparent #bced50;
}
div.speech.left {
box-shadow: 2px 2px 2px #CCCCCC;
margin-left: 10px;
width: 75%;
float: left;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, from(#ffffff),
color-stop(0.1, #eae8e8), color-stop(0.4, #E3E3E3),
color-stop(0.8, #DFDFDF), to(#D9D9D9));
}
div.speech.left:before {
content: '';
position: absolute;
width: 0;
height: 0;
top: 9px;
bottom: auto;
left: -10px;
border-width: 9px 10px 9px 0;
border-color: transparent #989898;
}
div.speech.left:after {
content: '';
position: absolute;
width: 0;
height: 0;
top: 10px;
bottom: auto;
left: -8px;
border-width: 8px 9px 8px 0;
border-color: transparent #eae8e8;
}
.leftimg {
float: left;
margin-top: 10px;
}
.rightimg {
float: right;
margin-top: 10px;
}
.leftd {
clear: both;
float: left;
}
.rightd {
clear: both;
float: right;
}
.clear {
clear: both;
}
.speed .left {
float: left;
}
.speed .right {
float: right;
}
</style>
<script type="text/javascript">
//记录常量
var hisHtmls = "";
var toUserId = "";
</script>
页面的样式不是很好看,讲究用用。
还有好几个功能。目前正在开发中,等开发好了之后在更新上来。
以上的功能,目前已完成聊天,会话列表,聊天记录等。但是聊天记录还想还没保存到本地数据库,
之后开发 聊天记录存,未读数量,是否在线。等功能。
等写完在更新吧。
希望以上一点小小的功能 能与大家一起学习,一起分享。
该文档的代码顺序我并没有去整理,我是一段截取一段注释来描述该功能。如果看官需要复制代码,请注意下html的顺序,
理论上,代码是没有问题。但是需要修改一些部分参数,因为我是连数据库的。所以你们自行创建的数据库表字段,需要放到该页面上来。替换了 即可正常聊天。