PHP客服聊天
1、基于workman框架
github:https://github.com/walkor/workerman-chat
文档:http://www.workerman.net/gatewaydoc/
demo:
2、前端代码
var client_name,user_id,client_name; // connect();
// 创建websocket
ws = new WebSocket("wss://"+document.domain+":8282"); // 当socket连接打开时,输入用户名
ws.onopen = function (ev) {
var login_data = '{"type":"login","client_name":"'+username+'","uid":'+uid+',"iskefu":'+iskefu+'}';
console.log("websocket握手成功,发送登录数据:"+login_data);
ws.send(login_data);
}; // 当有消息时根据消息类型显示不同信息
ws.onmessage = function (e) {
console.log("onmessage "+e.data);
var data = eval("("+e.data+")");
switch(data['type']){
// 服务端ping客户端
case 'ping':
ws.send('{"type":"pong"}');
break;;
// 登录 更新用户列表
case 'login': if(data['client_list']){
client_list = data['client_list'];
flush_client_list();
bindEvent(username,uid);
localStorage.setItem("kefu_name"+getDates(),data['client_name']);
}else{
//这里是新的用户加入客服端聊天列表
if(client_list[data['client_name']]=="" || client_list[data['client_name']]==undefined){
client_list[data['client_name']] = data['client_id'];
add_client_list(data['client_name'],data['client_id']);
bindEvent(username,uid);
}
} break;
// 发言
case 'say':
var str="";
//用户发送过来的,判断是自己看,还是客户看的央视
if(username!=data['from_client_name']){ googleNoti(data['content'],data['from_client_name']); str+='<div class="conversation-item item-left clearfix"><div class="conversation-user"><img src="/new/images/ryan.png" alt=""></div>';
str+=' <div class="conversation-body"><div class="name">'+data['from_client_name']+'</div>';
str+='<div class="time hidden-xs">'+data['time']+'</div>';
str+='<div class="text">'+data['content']+'</div></div></div>';
// setmsgnum(data['to_client_name']);
// newtalk(data['to_client_name']);
}else{ str+='<div class="conversation-item item-right clearfix"><div class="conversation-user"><img src="/new/images/kunis.png" alt=""></div>';
str+=' <div class="conversation-body"><div class="name">'+data['from_client_name']+'</div>';
str+='<div class="time hidden-xs">'+data['time']+'</div>';
str+='<div class="text">'+data['content']+'</div></div></div>';
} var saycontent=localStorage.getItem(data['to_client_name']+"say"+getDates());
if(saycontent!="" && saycontent!=null && saycontent!=undefined ){
saycontent+=str;
}else{
saycontent=str;
}
appendmsg(saycontent);
localStorage.setItem(data['to_client_name']+"say"+getDates(),saycontent);
break;
// 用户退出 更新用户列表
case 'logout':
delete client_list[data['from_client_name']];
del_client_list(data['from_client_name']);
} }; ws.onclose = function() {
console.log("连接关闭,定时重连");
// kefuconnect(username,uid,iskefu);
};
ws.onerror = function() {
console.log("出现错误");
};
3、PHP端代码
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
//declare(ticks=1); /**
* 聊天主逻辑
* 主要是处理 onMessage onClose
*/
use \GatewayWorker\Lib\Gateway;
use Workerman\Lib\Timer;
use \GatewayWorker\Lib\DataManager; class Events
{ public static $db = null;
/**
* 有消息时
* @param int $client_id
* @param mixed $message
*/
public static function onMessage($client_id, $message)
{
// debug
echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n"; // 客户端传递的是json数据
$message_data = json_decode($message, true);
if(!$message_data)
{
return ;
} // 根据类型执行不同的业务
switch($message_data['type'])
{
// 客户端回应服务端的心跳
case 'pong':
return;
// 客户端登录 message格式: {type:login, name:xx, uid:1} ,添加到客户端,广播给所有客户端xx进入聊天室
case 'login':
// 判断是否有房间号
//************注意默認設置將用戶設置成房間號***************************//
// 把房间号昵称放到session中
$uid = $message_data['uid'];
$iskefu = isset($message_data['iskefu'])?intval($message_data['iskefu']):0;//客服標識
$client_name = htmlspecialchars($message_data['client_name']);//默認為房間
$_SESSION['uid'] = $uid;
$_SESSION['client_name'] = $client_name;
$_SESSION['iskefu'] = $iskefu; $dm=new DataManager();
$dm->Db()->beginTrans();
try{
if(!$iskefu){//判斷是否是客服,以下為用戶的操作
$_SESSION['client_room'][$client_id]=$client_name;//将所有用户的$client_id和client_name放入房间好统一获取及分辨client_id是那个用户的
$data=array();
$data['ip']=$_SERVER['REMOTE_ADDR'];
$data['lastlogin']=time();
$data['isonline']=1;
$data['uid']=$uid; $exists = $dm->isExists($uid); echo '存在'.json_encode($exists);
if(empty($exists)){//判斷用戶是否記錄過
$data['user']=$client_name;
$data['recordtime']=date('Y-m-d H:i:s');
$kefuinfo=$dm->getKefuOne();
$data['kefu']=(!empty($kefuinfo))?$kefuinfo['mger_username']:"";
$data['mger_id'] = $kefuinfo['mger_id'];
$data['clients_id']=$client_id;//记录用户client_id
$dm->insert('shd_user', $data);//將用戶記錄到數據庫
$dm->setKefuUserNum($data['kefu']);//设置客服对接的用户数,为方便获取对接少的用户的客服
}else{
$userinfo=$dm->getUserByUser($uid);
$kefuinfo=$dm->isHaveKefu($uid);//判断是否有客服且是否在线有则返回无则重新获取一个在线客服并返回 echo json_encode($kefuinfo);
$data['kefu'] = $kefuinfo['mger_username'];
$data['mger_id'] = $kefuinfo['mger_id'];
$data['clients_id']=(($userinfo['clients_id']!=="")?$userinfo['clients_id'].',':'').$client_id;//记录用户client_id
$dm->save('shd_user', $data,"`user`='{$client_name}'");//修改用戶數據
$dm->setKefuUserNum($data['kefu']);//设置客服对接的用户数,为方便获取对接少的用户的客服
}
$dm->Db()->commitTrans();
$new_message = array('type'=>$message_data['type'], 'client_id'=>$uid, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s'));
Gateway::joinGroup($client_id, $client_name);//这里以$client_name为房间,一个客户一个房间
$kefuinfo=$dm->getKefuByKefu($data['mger_id']);
if(!empty($kefuinfo)){
$clients_id=(strpos($kefuinfo['clients_id'], ","))?explode(",", $kefuinfo['clients_id']):array($kefuinfo['clients_id']);//获取客服client_id
foreach ($clients_id as $key=>$val){
if($val){
Gateway::joinGroup($val, $client_name);//将客服的client_id加入用户 房间
}
}
$new_message['kefu_name']=$data['kefu'];
$new_message['kefu_id']= $data['mger_id']; }else{
$new_message['kefu_name']=0;
} Gateway::sendToGroup($client_name, json_encode($new_message),$client_id);//把消息发送到房间里,自己不接受
Gateway::sendToCurrentClient(json_encode($new_message)); }else{//一下為客服的操作
$_SESSION['kefu_room'][$client_id]=$client_name;//将所有客服的$client_id和client_name放入Session好统一获取及分辨client_id是那个客服的
Gateway::joinGroup($client_id, $client_name);//将client_id加入进客服本身的房间,而不是用户的房间,用于后面获取客服所有的client_id
$res=$dm->getUserByKefu($client_name);
//一下為客服下的用戶列表
$user_list=array();
foreach ($res as $key=>$val){
$user_list[$val['user']]=$val['uid'];//我這裏默認用戶為房間號
Gateway::joinGroup($client_id, $val['user']);//將客服客戶端加入房間//這裏需要重新加入到房間因爲client_id已經刷新了
$new_message = array('type'=>$message_data['type'], 'kefu_name'=>htmlspecialchars($client_name), 'kefu_id'=>$uid,'clients_id'=>$val['uid'],'client_name'=>htmlspecialchars($val['user']), 'time'=>date('Y-m-d H:i:s'));
Gateway::sendToGroup($val['user'], json_encode($new_message),$client_id);
} //獲取未接待用戶並設置
$res=$dm->setOnlineUserKefu($uid);//获取了5条并设置
foreach ($res as $key=>$val){
if($val){
Gateway::joinGroup($client_id, $val['user']);//將用户加入客服房間
}
} // 获取房间内所有用户列表,這裏我將默認客服有一個房間,切所有客服的client_id房間該客服的房間
//為方便用戶進來時,將客服的client_id 加入到房間進去
$clients_list = Gateway::getClientSessionsByGroup($client_name);
$clients_id=array();
foreach($clients_list as $tmp_client_id=>$item)
{
if(isset($item['client_name'])){
$clients_id[$tmp_client_id] = $item['client_name'];
}
}
$clients_id[$client_id]=$client_name;
$dm->saveKefuClientId($clients_id,$uid);
$dm->Db()->commitTrans();
//獲取客服客戶端用戶列表 // 给当前用户发送用户列表
$new_message = array('type'=>$message_data['type'], 'client_id'=>$uid, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s'));
$new_message['client_list'] = $user_list;
Gateway::sendToCurrentClient(json_encode($new_message));
} }catch (Exception $e){
echo "錯誤異常".$e;
$dm->Db()->rollBackTrans();
}
// 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx} return; // 客户端发言 message: {type:say, to_client_id:xx, content:xx}
case 'say': echo "\n\n"."----------------say onMessage:".$message."\n";
// 非法请求
$uid = $message_data['client_id'];
$client_name =htmlspecialchars($message_data['client_name']);
$kefu_name = $message_data['kefu_name'];
$kefu_id = $message_data['kefu_id'];
$iskefu = isset($message_data['iskefu'])?intval($message_data['iskefu']):0;//客服標識 $dm=new DataManager();
if($iskefu){ $new_message = array(
'type'=>'say',
'from_client_id'=>$kefu_id,//当前用户或者客服在发消息
'from_client_name' =>$kefu_name,
'to_client_id'=>'all',
'content'=>nl2br(htmlspecialchars($message_data['content'])),
'time'=>date('Y-m-d H:i:s'),
);
//一下為客服操作 {"type":"say","client_name":"root","client_id":"1","content":"huifu","kefu_name":"service","kefu_id":"22","iskefu":"1"}
$to_client_name=htmlspecialchars($message_data['client_name']);//客服对用户说或者用户发消息也是要发给自己,在自己的浏览器上记录消息,
$new_message['to_user_id']=$uid;
$new_message['to_user_name']=$client_name;
//所以这里是不管是谁在发,都是要发给用户的,所以这里写死
$new_message['to_client_name']=$to_client_name;
//記錄message
$dm->recordMsg($to_client_name, $kefu_name, nl2br(htmlspecialchars($message_data['content'])), 1,$uid,$kefu_id);//1為給用戶,2為給客服 發送消息
return Gateway::sendToGroup($to_client_name ,json_encode($new_message));
}else{ $new_message = array(
'type'=>'say',
'from_client_id'=>$uid,//当前用户或者客服在发消息
'from_client_name' =>$client_name,
'to_client_id'=>'all',
'content'=>nl2br(htmlspecialchars($message_data['content'])),
'time'=>date('Y-m-d H:i:s'),
); $to_client_name=$client_name;//这里是不管是谁在发,都是要发给用户的,所以这里写死
$new_message['to_client_name']=$to_client_name;
$new_message['to_kefu_id']=$kefu_id;
$new_message['to_kefu_name']=$kefu_name;
//記錄message
$userinfo=$dm->getUserByUser($uid);
$dm->recordMsg($client_name, $userinfo['kefu'], nl2br(htmlspecialchars($message_data['content'])), 2,$uid,$kefu_id);//1為給用戶,2為給客服 發送消息
return Gateway::sendToGroup($client_name ,json_encode($new_message));
} }
} /**
* 当客户端断开连接时
* @param integer $client_id 客户端id
*/
public static function onClose($client_id)
{ // debug
echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n";
$iskefu=$_SESSION['iskefu']; if(!isset($_SESSION['uid']))return;
else $uid=$_SESSION['uid'];
// $clients_list = isset($_SESSION['client_room'])?$_SESSION['client_room']:((isset($_SESSION['kefu_room'])&& $iskefu=1)?$_SESSION['kefu_room']:array());
$dm=new DataManager(); $dm->setClientsIdAndIsOnline($client_id, $uid, $iskefu);
} public static function onWorkerStart(){
//设置一个定时器,3个小时执行一次
$dm=new DataManager();
//判断用户是否10800
Timer::add(1800, array($dm,'updateUserIsOline'));
} }
4、启动PHP服务文件
php event.php start
PHP客服聊天的更多相关文章
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(二) 实现聊天室连接
上一篇已经简单介绍了layim WebUI即时通讯组件和获取数据的后台方法.现在要讨论的是SingalR的内容,之前都是直接贴代码.那么在贴代码之前先分析一下业务模型,顺便简单讲一下SingalR里的 ...
- java 网站用户在线和客服聊天
注:本文来源于<java 网站用户在线和客服聊天> 这是应用到项目中的一个例子. 实现原理是将信息存储到Application域里面.然后使用Struts2 Action 用json格式的 ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能
休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...
- 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天
ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天 看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器 ...
- 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据
ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据 最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...
- JAVA结合WebSocket实现简单客服聊天功能
说明:该示例只简单的实现了客服聊天功能. 1.聊天记录没有保存到数据库中,一旦服务重启,消息记录将会没有,如果需要保存到数据库中,可以扩展 2.页面样式用的网上模板,样式可以自己进行修改 3.只能由用 ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(六)之 好友申请、同意、拒绝
不知道距离上一篇多久没有写了,可能是因为忙(lan)的关系吧.废话不多说,今天要介绍的不算什么新知识,主要是逻辑上的一些东西.什么逻辑呢,加好友,发送好友申请,对方审批通过,拒绝.(很遗憾,对方审批通 ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(五) 补充:历史记录 和 消息提醒
有开发者提问怎么做历史记录功能和即使不打开聊天窗口有消息提醒功能.简单抽时间写了点代码.不过只是基本思路,具体细节没有实现. 正如前几篇博客中提到的,读取历史记录什么时候读取呢?按照常理,应该是打开聊 ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据
最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前端组件配合后台完成即时聊天等功能.当然用到的技术就是ASP.NET SingalR框架.本人不会c ...
随机推荐
- git在多迭代版本的应用
名词解释: 1.迭代: 就是对于项目功能的一个分类.如项目需要新增一个地图功能,则地图功能是一个迭代. 2.gitlab机器人 操作: 1.如果将要进行一个新功能的开发,从稳定分支上拉取创建一个新的分 ...
- MDS
转载:https://blog.csdn.net/victoriaw/article/details/78500894 多维缩放(Multidimensional Scaling, MDS)是一组对象 ...
- iPhone 系统刷机
1. 下载好固件(爱思 或者 pp助手) e.g. http://jailbreak.25pp.com/gujian/ 2. 将电脑与手机连接上,弹出iTunes软件即可 3. 长按手机电源键 关闭手 ...
- 如何用Electron Js创建第一个应用Hello World
什么是Electron Node.js和Chromium的结合品.允许只使用HTML,CSS和JavaScript来开发跨平台桌面应用. 编写第一个Electron程序(Hello World) 在开 ...
- NOIP 2019游记
Update on 2019.4.20 禁赛预定
- Spring Boot学习总结二
Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化.除此之外,Redis还提供一些类 ...
- Codeforces Round #554 (Div. 2)自闭记
A 签到 #include<bits/stdc++.h> using namespace std; ],t[],ans; int main() { scanf("%d%d&quo ...
- SQL随记(六)
1.关于dbms_sql包的一些执行语句 cursor_name := DBMS_SQL.OPEN_CURSOR; --打开游标: DBMS_SQL.PARSE(cursor_name, var_dd ...
- window.location.href 传参中文乱码问题!!!
不是所有地方都会用Ajax 当你使用window.location.href 来传中文参数的时候 如何避免乱码问题 js 是这样写的 下面代码中 方式 封装编码 参数 username ...
- MYCP作业
本次作业主要复习了输入流输出流的内容 作业要求: 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支持两个参数: java MyCP -tx XXX1.txt ...