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客服聊天的更多相关文章

  1. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(二) 实现聊天室连接

    上一篇已经简单介绍了layim WebUI即时通讯组件和获取数据的后台方法.现在要讨论的是SingalR的内容,之前都是直接贴代码.那么在贴代码之前先分析一下业务模型,顺便简单讲一下SingalR里的 ...

  2. java 网站用户在线和客服聊天

    注:本文来源于<java 网站用户在线和客服聊天> 这是应用到项目中的一个例子. 实现原理是将信息存储到Application域里面.然后使用Struts2 Action 用json格式的 ...

  3. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能

    休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...

  4. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天   看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器 ...

  5. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据   最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...

  6. JAVA结合WebSocket实现简单客服聊天功能

    说明:该示例只简单的实现了客服聊天功能. 1.聊天记录没有保存到数据库中,一旦服务重启,消息记录将会没有,如果需要保存到数据库中,可以扩展 2.页面样式用的网上模板,样式可以自己进行修改 3.只能由用 ...

  7. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(六)之 好友申请、同意、拒绝

    不知道距离上一篇多久没有写了,可能是因为忙(lan)的关系吧.废话不多说,今天要介绍的不算什么新知识,主要是逻辑上的一些东西.什么逻辑呢,加好友,发送好友申请,对方审批通过,拒绝.(很遗憾,对方审批通 ...

  8. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(五) 补充:历史记录 和 消息提醒

    有开发者提问怎么做历史记录功能和即使不打开聊天窗口有消息提醒功能.简单抽时间写了点代码.不过只是基本思路,具体细节没有实现. 正如前几篇博客中提到的,读取历史记录什么时候读取呢?按照常理,应该是打开聊 ...

  9. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前端组件配合后台完成即时聊天等功能.当然用到的技术就是ASP.NET SingalR框架.本人不会c ...

随机推荐

  1. mongodb备份还原

    备份:mongodump mongodump常用参数 --db:指定导出的数据库 --collection:指定导出的集合 --excludeCollection:指定不导出的集合 --host :远 ...

  2. Centos7 安装gitLab

    我这里使用的是centos 7 64bit,我试过centos 6也是可以的! 1. 安装依赖软件 yum -y install policycoreutils openssh-server open ...

  3. C语言中结构体内存对齐

    先写一个小程序: #include<stdio.h> struct student  {    int a;   char k;   short m; }; int main() { st ...

  4. .Net Core实践3 配置文件

    环境 .netcore2.1 / vs2017 / win10 / centos7 在.netcore项目中读取配置文件,先添加应用程序配置文件App.config.这个是类库项目的配置文件名. Sy ...

  5. kubernetes 1.14安装部署metrics-server插件

    简单介绍: 如果使用kubernetes的自动扩容功能的话,那首先得有一个插件,然后该插件将收集到的信息(cpu.memory..)与自动扩容的设置的值进行比对,自动调整pod数量.关于该插件,在ku ...

  6. 超详细的Guava RateLimiter限流原理解析

    超详细的Guava RateLimiter限流原理解析  mp.weixin.qq.com 点击上方“方志朋”,选择“置顶或者星标” 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是 ...

  7. Python 下划线

    单下划线 "单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和自类对象自己能访问到这些变量. 例子:以单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口 ...

  8. 2018-2019-2 20165232《网络对抗技术》Exp1 缓冲区溢出实验

    2018-2019-2 20165232<网络对抗技术>Exp1 缓冲区溢出实验 实验点1:逆向及Bof基础实践 实践任务 用一个pwn1文件. 该程序正常执行流程是:main调用foo函 ...

  9. 第一节:EF Core简介和CodeFirst和DBFirst两种映射模式(以SQLite和SQLServer为例)

    一. EF简介 1. 定义 Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台的数据访问技术,它还是一种对象关系映射器(ORM),它使.NET 开发人员能够使用面向对 ...

  10. Python——正则表达式初步应用(一)

    1.先附上转载(www.cnblogs.com/huxi)的一张图,有重要的参考价值,其含义大家请通过阅读来理解. 2.附上初步学习Python时编写的一个爬糗事百科段子的代码. # -*- codi ...