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'); } } }