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