一、基於workman框架php
github:https://github.com/walkor/workerman-chathtml
文檔:http://www.workerman.net/gatewaydoc/前端
demo:git
二、前端代碼github
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("出現錯誤"); };
三、PHP端代碼web
<?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')); } }
四、啓動PHP服務文件json
php event.php start瀏覽器