| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- <?php
- namespace WebIM;
- use Swoole;
- use Swoole\Filter;
- class Server extends Swoole\Protocol\CometServer
- {
- /**
- * @var Store\File;
- */
- protected $storage;
- /**
- * clientId(客户端id)
- * @var array
- */
- protected $users;
- /**
- * 上一次发送消息的时间
- * @var array
- */
- protected $lastSentTime = array();
- const MESSAGE_MAX_LEN = 1024; //单条消息不得超过1K
- const WORKER_HISTORY_ID = 0;
- function __construct($config = array())
- {
- //将配置写入config.js
- $config_js = <<<HTML
- var webim = {
- 'server' : '{$config['server']['url']}'
- }
- HTML;
- file_put_contents(WEBPATH . '/config.js', $config_js);
- //检测日志目录是否存在
- $log_dir = dirname($config['webim']['log_file']);
- if (!is_dir($log_dir))
- {
- mkdir($log_dir, 0777, true);
- }
- if (!empty($config['webim']['log_file']))
- {
- $logger = new Swoole\Log\FileLog($config['webim']['log_file']);
- }
- else
- {
- $logger = new Swoole\Log\EchoLog(true);
- }
- $this->setLogger($logger); //Logger
- /**
- * 使用文件或redis存储聊天信息
- */
- $this->storage = new Storage($config['storage']);
- $this->origin = $config['server']['origin'];
- parent::__construct($config);
- }
- /**
- * 登录
- * @param $client_id
- * @param $msg
- */
- function cmd_login($client_id, $msg)
- {
- $uid = Filter::escape($msg['uid']);
- $role = Filter::escape($msg['role']);
- $chatUid = Filter::escape($msg['chatUid']);
- $info['name'] = Filter::escape(strip_tags($msg['name']));
- $info['avatar'] = Filter::escape($msg['avatar']);
- $chatLogKey = Filter::escape($msg['chatLogKey']);
- $isCustomer = $role == 'customer' ? true : false;
- //回复给登录用
- $resMsg = array(
- 'cmd' => 'login',
- 'fd' => $client_id,
- 'uid' => $uid,
- 'chatUid' => $chatUid,
- 'name' => $info['name'],
- 'avatar' => $info['avatar'],
- 'chatLogKey' => $chatLogKey,
- 'isCustomer' => $isCustomer ? 1 : 0,
- );
- //把会话存起来
- $this->users[$client_id] = $resMsg;
- $this->storage->login($client_id, $resMsg, $uid, $chatUid, $isCustomer);
- $this->sendJson($client_id, $resMsg);
- //用户登录消息
- $loginMsg = array(
- 'cmd' => 'fromMsg',
- 'from' => 0,
- 'channal' => 1,
- 'clientId' => $client_id,
- 'changeClientId' => 1,
- 'data' => $info['name'] . "上线了",
- );
- if($isCustomer){
- $clientIds = $this->storage->getMerchantM2C($uid, $chatUid);//取对方客户端号(即商家)
- $this->log("customer login success <--> client id : #" . json_encode($clientIds) . " , customerId: $uid, sjId: $chatUid");
- }else{
- $clientIds = $this->storage->getCustomerC2M($chatUid, $uid);//取对方客户端号(即客户)
- $this->storage->remMerchantChater($chatUid, $uid);
- $merchantClientIds = $this->storage->getMerchantChatList($client_id, $uid);
- $this->log('商家聊天界面登录111 -- 商家聊天列表刷新消息数:clientIds' . json_encode($clientIds));
- if(!empty($merchantClientIds)){
- //将消息发送给所有商家打开的页面
- $this->broadcastToMerchant($merchantClientIds, $chatUid, -1);
- $this->log('商家聊天界面登录222 -- 商家聊天列表刷新消息数');
- }
- $this->log("merchant login success <--> client id : #" . json_encode($clientIds) . ", customerId: $chatUid, sjId: $uid");
- }
- if(!empty($clientIds)){
- foreach ($clientIds as $clientId){
- $this->send($clientId, json_encode($loginMsg));
- }
- }
- }
- /**
- * 下线时,通知指定的人
- */
- function onExit($client_id)
- {
- $userInfo = $this->storage->getUser($client_id);
- //聊天界面登录的退出
- if ($userInfo) {//聊天界面登录的,之前有缓存,故在此会进入
- $resMsg = array(
- 'cmd' => 'offline',
- 'fd' => $client_id,
- 'from' => 0,
- 'channal' => 0,
- 'clientId' => 0,
- 'data' => $userInfo['name'] . "下线了",
- );
- $this->storage->logout($client_id);
- unset($this->users[$client_id]);
- //当所有客户端都下线了时,将下线消息发送给对方
- $uid = $userInfo['uid'];
- $chatUid = $userInfo['chatUid'];
- if($userInfo['isCustomer'] == 1){
- $clientIds = $this->storage->getMerchantM2C($uid, $chatUid);//取对方(即商家)
- $clientIdCount = $this->storage->existCustomerClientC2M($uid, $chatUid);//自己(客户)是否还有在线的客户端
- $this->log("customer logout, client ids : " . json_encode($clientIds) . ", logout clientId #".$client_id);
- }else{
- $clientIds = $this->storage->getCustomerC2M($chatUid, $uid);//取对方(即客户)
- $clientIdCount = $this->storage->existMerchantClientC2M($chatUid, $uid);//自己(商家)是否还有在线的客户端
- $this->log("merchant logout, client ids : " . json_encode($clientIds). ", logout clientId #".$client_id);
- }
- if(!empty($clientIds) && $clientIdCount == 0){
- foreach ($clientIds as $clientId){
- $this->send($clientId, json_encode($resMsg));
- }
- }
- }
- //商家聊天列表页登录的退出
- if(empty($userInfo)){//商家聊天列表页登录的,之前无缓存,故在此会进入
- $this->storage->merchantChatListLogout($client_id);
- $this->log("merchant chatList page logout : " . $client_id);
- }
- $this->log("onOffline: " . $client_id);
- }
- function onTask($serv, $task_id, $from_id, $data)
- {
- $req = unserialize($data);
- if ($req)
- {
- switch($req['cmd'])
- {
- case 'getHistory':
- $history = array('cmd'=> 'getHistory', 'history' => $this->storage->getHistory($req['fd']));
- if ($this->isCometClient($req['fd'])) {
- return $req['fd'].json_encode($history);
- } else {//WebSocket客户端可以task中直接发送
- $this->sendJson(intval($req['fd']), $history);
- }
- break;
- case 'addHistory':
- if (empty($req['msg'])) {
- $req['msg'] = '';
- }
- $this->log(json_encode($req));
- $this->storage->addHistory($req['fd'], $req['msg']);
- break;
- default:
- break;
- }
- }
- }
- function onFinish($serv, $task_id, $data)
- {
- $this->send(substr($data, 0, 32), substr($data, 32));
- }
- /**
- * 获取在线列表
- */
- function cmd_getOnline($client_id, $msg)
- {
- $resMsg = array(
- 'cmd' => 'getOnline',
- );
- $users = $this->storage->getOnlineUsers();
- $this->log('onLineUsers:'.json_encode($users));
- $info = $this->storage->getUsers(array_slice($users, 0, 100));
- $resMsg['users'] = $users;
- $resMsg['list'] = $info;
- $this->sendJson($client_id, $resMsg);
- }
- /**
- * 获取历史聊天记录
- */
- function cmd_getHistory($client_id, $msg)
- {
- $task['fd'] = $client_id;
- $task['cmd'] = 'getHistory';
- $task['offset'] = '0,100';
- //在task worker中会直接发送给客户端
- $this->getSwooleServer()->task(serialize($task), self::WORKER_HISTORY_ID);
- }
- /**
- * 发送信息请求
- */
- function cmd_message($client_id, $msg)
- {
- $resMsg = $msg;
- $resMsg['cmd'] = 'fromMsg';
- $userInfo = $this->storage->getUser($client_id);
- $isCustomer = $userInfo['isCustomer'];
- $now = time();
- $who = $isCustomer ? '商家' : '客户';
- if($isCustomer){
- $customerId = $userInfo['uid'];
- $sjId = $userInfo['chatUid'];
- }else{
- $customerId = $userInfo['chatUid'];
- $sjId = $userInfo['uid'];
- }
- if (strlen($msg['data']) > self::MESSAGE_MAX_LEN) {
- $this->sendErrorMessage($client_id, 102, 'message max length is '.self::MESSAGE_MAX_LEN);
- return;
- }
- //上一次发送的时间超过了允许的值,每N秒可以发送一次
- if (!empty($this->lastSentTime[$client_id]) && $this->lastSentTime[$client_id] > $now - $this->config['webim']['send_interval_limit']) {
- $this->sendErrorMessage($client_id, 104, 'over frequency limit');
- return;
- }
- if ($msg['channal'] == 1) {//表示私聊
- $to = isset($msg['to']) ? intval($msg['to']) : 0;//这个可能不准,以下面取的缓存为准
- $this->log('传来的参数判断'.$who.'是否在线:'.$to);
- //这边也能判断商家与客户的聊天界面打开状态(有多少客户端)
- $customerClientIds = $this->storage->getCustomerC2M($customerId, $sjId);
- $this->log('##### 客户clientids:'.json_encode($customerClientIds));
- $merchantClientIds = $this->storage->getMerchantM2C($customerId, $sjId);
- $this->log('##### 商家clientids:'.json_encode($merchantClientIds));
- $cacheTo = $isCustomer ? (empty($merchantClientIds) ? 0 : 1) : (empty($customerClientIds) ? 0 : 1);//重新判断在线情况
- $this->log('取缓存判断是否在线:'.$cacheTo);
- if(($to > 0 && $cacheTo == 0) || ($to == 0 && $cacheTo == 1)){
- $to = $cacheTo;
- $this->log($who.'在线状态:|*** 缓存 与 传来参数判断不一至 ***|');
- }
- if($to <= 0){//不在线
- $this->log('cmd_message chat person not on line');
- if($isCustomer){//当前是客户,消息接收方是商家
- $merchantClientIds = $this->storage->getMerchantChatList($client_id, $sjId);
- $this->log('商家聊天列表接收消息111:clientIds '. json_encode($merchantClientIds));
- if(!empty($merchantClientIds)){
- //将消息发送给所有商家打开的页面
- $this->broadcastToMerchant($merchantClientIds, $customerId);
- $this->log('商家聊天列表接收消息222');
- }else{
- $this->log('有历史消息');
- }
- $this->storage->addMerchantNewChater($customerId, $sjId, $now);//记录给商家发来新消息的客户
- $this->storage->addMerchantChaterLog($customerId, $sjId, $now);//记录与商家聊天的客户
- }else{//当前是商家,消息接收方是客户
- $this->storage->addMerchantChaterLog($customerId, $sjId, $now);//记录商家聊过的客户
- }
- }else{//在线
- }
- if(!empty($customerClientIds)){
- $newResMsg = $resMsg;
- if($isCustomer){
- $newResMsg['msgType'] = 'C2M';//客户对商家
- $newResMsg['customerId'] = $customerId;
- }
- foreach ($customerClientIds as $clientId){
- if ($clientId != $client_id) {
- $this->sendJson($clientId, $newResMsg);
- }
- }
- }
- if(!empty($merchantClientIds)){
- $newResMsg = $resMsg;
- if(!$isCustomer){
- $newResMsg['msgType'] = 'M2C';//商家对客户
- $newResMsg['sjId'] = $sjId;
- }
- foreach ($merchantClientIds as $clientId){
- if ($clientId != $client_id) {
- $this->sendJson($clientId, $newResMsg);
- }
- }
- }
- $this->storage->addHistory($client_id, $msg['data']);
- }elseif ($msg['channal'] == 0) {//未使用
- $this->log('notice >>>>>>> cmd_message not use channel 0');
- }
- //记录本次消息发送的时间
- $this->lastSentTime[$client_id] = $now;
- }
- /**
- * 接收到消息时
- * @see WSProtocol::onMessage()
- */
- function onMessage($client_id, $ws)
- {
- $this->log("onMessage #$client_id: " . $ws['message']);
- $msg = json_decode($ws['message'], true);
- if (empty($msg['cmd']))
- {
- $this->sendErrorMessage($client_id, 101, "invalid command");
- return;
- }
- $func = 'cmd_'.$msg['cmd'];
- if (method_exists($this, $func))
- {
- $this->$func($client_id, $msg);
- }
- else
- {
- $this->sendErrorMessage($client_id, 102, "command $func no support.");
- return;
- }
- }
- /**
- * 发送错误信息
- * @param $client_id
- * @param $code
- * @param $msg
- */
- function sendErrorMessage($client_id, $code, $msg)
- {
- $this->sendJson($client_id, array('cmd' => 'error', 'code' => $code, 'msg' => $msg));
- }
- /**
- * 发送JSON数据
- * @param $client_id
- * @param $array
- */
- function sendJson($client_id, $array)
- {
- $msg = json_encode($array);
- if ($this->send($client_id, $msg) === false)
- {
- $this->close($client_id);
- }
- }
- /**
- * 广播JSON数据
- * @param $client_id
- * @param $array
- */
- function broadcastJson($sesion_id, $array)
- {
- $msg = json_encode($array);
- $this->broadcast($sesion_id, $msg);
- }
- function broadcast($current_session_id, $msg)
- {
- foreach ($this->users as $client_id => $name)
- {
- if ($current_session_id != $client_id)
- {
- $this->send($client_id, $msg);
- }
- }
- }
- /*-------------------------------------------- 商家聊天列表页 ------------------------------------------*/
- /**
- * 登录
- * @param $client_id
- * @param $msg
- */
- function cmd_chatListMerchantLogin($client_id, $msg)
- {
- $uid = Filter::escape($msg['uid']);
- $role = Filter::escape($msg['role']);
- $info['name'] = Filter::escape(strip_tags($msg['name']));
- $info['avatar'] = Filter::escape($msg['avatar']);
- $isCustomer = $role == 'customer' ? true : false;
- //回复给登录用
- $resMsg = array(
- 'cmd' => 'login',
- 'fd' => $client_id,
- 'uid' => $uid,
- 'name' => $info['name'],
- 'avatar' => $info['avatar'],
- 'isCustomer' => $isCustomer ? 1 : 0,
- );
- //把会话存起来
- //$this->users[$client_id] = $resMsg;
- $this->storage->chatListLogin($client_id, $resMsg, $uid, $isCustomer);
- $this->sendJson($client_id, $resMsg);
- //广播给其它在线用户
- //$resMsg['cmd'] = 'newUser';
- //将上线消息发送给所有人
- //$this->broadcastJson($client_id, $resMsg);
- }
- /**
- * 获取商家聊天人的历史记录
- * @param $client_id
- * @param $msg
- */
- function cmd_getMerchantChatLog($client_id, $msg){
- $resMsg = array(
- 'cmd' => 'getMerchantChatLog',
- );
- $merchant = $this->storage->getMerchant($client_id);
- $sjId = $merchant['uid'];
- $array = $this->storage->getMerchantChaterLog($sjId);
- $newMsgCustomers = [];
- foreach ($array['newMsgCustomers'] as $uid){
- $key = 'xhUser_' . $uid;
- $customer = $this->storage->getCustomer($key);
- $newMsgCustomers[] = ['id'=>$customer['id'], 'userName'=>$customer['userName']];
- }
- $resMsg['newMsgCustomers'] = $newMsgCustomers;
- $historyCustomers = [];
- foreach($array['historyCustomers'] as $uid){
- $key = 'xhUser_' . $uid;
- $customer = $this->storage->getCustomer($key);
- $historyCustomers[] = ['id'=>$customer['id'], 'userName'=>$customer['userName']];
- }
- $resMsg['historyCustomers'] = $historyCustomers;
- $this->log('获取商家聊天人的历史记录: merchantid '.$sjId );
- $this->sendJson($client_id, $resMsg);
- }
- /**
- * 给商家客户端广播消息
- * @param $clientIds
- * @param $customerId
- * @param int $count 消息数(正数增加,负数减少)
- */
- function broadcastToMerchant($clientIds, $customerId, $count = 1){
- //刷新客户消息数用
- $key = 'xhUser_' . $customerId;
- $customer = $this->storage->getCustomer($key);
- $customer = ['id'=>$customer['id'], 'userName'=>$customer['userName']];
- $resMsg = array(
- 'cmd' => 'refreshMsgCount',
- 'customer' => $customer,
- 'count' => $count,
- );
- foreach ($clientIds as $clientId){
- $this->sendJson($clientId, $resMsg);
- }
- }
- /**
- * 客户在微信上给商家发消息
- * @param $client_id
- * @param $msg
- */
- function cmd_sendMsgC2M($client_id, $msg){
- $resMsg = $msg;
- $resMsg['cmd'] = 'fromMsg';
- $now = time();
- $isCustomer = 1;//当前是客户
- $customerId = $msg['uid'];//客户Uid
- $sjId = $msg['chatUid'];//商家MerchantId
- $who = $isCustomer ? '商家' : '客户';
- if (strlen($msg['data']) > self::MESSAGE_MAX_LEN)
- {
- $this->sendErrorMessage($client_id, 102, 'message max length is '.self::MESSAGE_MAX_LEN);
- return;
- }
- if ($msg['channal'] == 1) {//表示私聊
- //判断商家与客户的聊天界面打开状态(有多少客户端)
- $customerClientIds = $this->storage->getCustomerC2M($customerId, $sjId);
- $this->log('##### 客户clientids:'.json_encode($customerClientIds));
- $merchantClientIds = $this->storage->getMerchantM2C($customerId, $sjId);
- $this->log('##### 商家clientids:'.json_encode($merchantClientIds));
- $cacheTo = $isCustomer ? (empty($merchantClientIds) ? 0 : 1) : (empty($customerClientIds) ? 0 : 1);//重新判断在线情况
- $this->log('取缓存判断'.$who.'是否在线:'.$cacheTo);
- if($cacheTo <= 0){//不在线
- if($isCustomer){//当前是客户,消息接收方是商家
- $clientIds = $this->storage->getMerchantChatList($client_id, $sjId);
- $this->log('商家聊天列表接收消息 1.sendMsgC2M:clientIds '. json_encode($clientIds));
- if(!empty($clientIds)){
- //将消息发送给所有商家打开的页面
- $this->broadcastToMerchant($clientIds, $customerId);
- $this->log('商家聊天列表接收消息 2.sendMsgC2M');
- }else{
- $this->log('写入历史消息');
- }
- $this->storage->addMerchantNewChater($customerId, $sjId, $now);//记录给商家发来新消息的客户
- $this->storage->addMerchantChaterLog($customerId, $sjId, $now);//记录与商家聊天的客户
- }else{//当前是商家,消息接收方是客户
- $this->log('cmd:sendMsgC2M not use in merchant (当前是商家,消息接收方是客户)');
- }
- }else{//在线
- }
- if(!empty($customerClientIds)){
- $newResMsg = $resMsg;
- if($isCustomer){
- $newResMsg['msgType'] = 'C2M';//客户对商家
- $newResMsg['customerId'] = $customerId;
- }
- foreach ($customerClientIds as $clientId){
- if ($clientId != $client_id) {
- $this->sendJson($clientId, $newResMsg);
- }
- }
- }
- if(!empty($merchantClientIds)){
- $newResMsg = $resMsg;
- if(!$isCustomer){
- $newResMsg['msgType'] = 'M2C';//商家对客户
- $newResMsg['sjId'] = $sjId;
- }
- foreach ($merchantClientIds as $clientId){
- if ($clientId != $client_id) {
- $this->sendJson($clientId, $newResMsg);
- }
- }
- }
- $cacheData = ['chatLogKey'=>$msg['chatLogKey'], 'isCustomer'=>$isCustomer];
- $this->storage->addHistoryByData($cacheData, $msg['data']);
- }elseif ($msg['channal'] == 0) {//未使用
- $this->log('cmd:sendMsgC2M channal not use 0');
- }
- }
- }
|