/** * @args {"description": "Delay: Clean offline client and helpdesk every minute"} */ public function perform() { $accounts = Account::findAll([]); foreach ($accounts as $account) { $accountId = $account->_id; $setting = HelpDeskSetting::findOne(['accountId' => $accountId]); if (!empty($setting)) { $maxWaitTime = $setting->maxWaitTime; // Close timeout conversation $chatConversations = ChatConversation::findAll(['accountId' => $accountId, 'status' => ChatConversation::STATUS_OPEN, 'lastChatTime' => ['$lt' => TimeUtil::msTime(time() - $maxWaitTime * 60)]]); foreach ($chatConversations as $chatConversation) { HelpDesk::disconnect($chatConversation->_id, ['type' => 'brake']); } // Delete timeout pending client $pendingClients = PendingClient::findAll(['accountId' => $accountId, 'lastPingTime' => ['$lt' => TimeUtil::msTime(time() - PendingClient::PING_THRESHOLD)]]); foreach ($pendingClients as $pendingClient) { $pendingClient->delete(); } // Clean offline helpdesk $cache = Yii::$app->cache; $onlineHelpDesks = $cache->get(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId); if (!empty($onlineHelpDesks)) { foreach ($onlineHelpDesks as $deskId => $lastPingTime) { if ($lastPingTime < TimeUtil::msTime(time() - HelpDesk::PING_THRESHOLD)) { HelpDesk::leave($deskId, $accountId, ['type' => 'droping']); } } } } } }
/** * Push status * * <b>Request Type: </b>POST<br/> * <b>Request Endpoint: </b>http://{server-domain}/api/chat/conversation/state<br/> * <b>Content-type: </b>application/json<br/> * <b>Summary: </b>This is api is for push the status of the client or helpDesk to the server<br/> * * <b>Request Parameters: </b><br/> * channelName: string, the name of the channel<br/> * eventName: string, the name of the event, should be 'deskLeft', 'deskJoined', 'clientLeft', 'clientJoined', 'clientPing', 'pendingLeft' <br/> * consersationId: string, the identifier of the conversation.<br/> * desk: array, the information of the help desk.<br/> * desk.badge: string, the badge id of the helpDesk.<br/> * desk.id: string, the identifier of the helpDesk.<br/> * desk.email: string, the email of the helpDesk.<br/> * desk.avatar: string, the url of the avatar of the helpDesk.<br/> * client: array, the information of the client.<br/> * client.nick: string, the nickname of the client.<br/> * client.avatar: string, the url of the avatar of the client.<br/> * client.openId: string, the openId of the client.<br/> * client.source: string, 'web' or 'wechat', the source.<br/> * * <b>Request Example: </b><br/> * <pre> * { * "eventName": "clientJoined", * "conversationId": "149035b7b81374aa468b4566", * "desk": { * "badge": "T04247", * "id": "54a24c12db4c0ecc048b4568", * "email": "*****@*****.**", * "avatar": "http://test.com/avatar.png" * }, * "client": { * "nick": "devin", * "avatar": "http://test.com/avatar.png", * "openId": "2234231234f2d12", * "source": "website", * "messages": [ * ... * ] * }, * "extra": { * //extra data * } * } * * <b>Response Example: </b><br/> * <pre> * {"status": "ok"} * </pre> * * for clientJoined * </pre> * { * "status": "ok", * "helpDesk": { * "id": "54a24c12db4c0ecc048b4568", * "badge": "T04247", * "email": "*****@*****.**", * "avatar": "/images/management/image_hover_default_avatar.png", * "isEnabled": true, * "isActivated": false, * "name": null, * "language": "zh_cn", * "clientCount": 1, * "isOnline": false, * "conversationCount": 2, * "maxClient": 5 * }, * "channel": "wm-chat-54a24c12db4c0ecc048b4568-2234231234f2d12", * "conversationId": "54a295addb4c0ecd048b456c" * } * </pre> */ public function actionState() { $eventName = $this->getParams('eventName'); $conversationId = $this->getParams('conversationId'); $desk = $this->getParams('desk'); $client = $this->getParams('client'); $extra = $this->getParams('extra'); $cache = Yii::$app->cache; $accountId = $this->getAccountId(); $response = []; if (!empty($desk['id'])) { $deskMongoId = new \MongoId($desk['id']); } if (empty($eventName)) { throw new BadRequestHttpException('Missing required fields'); } switch ($eventName) { case 'deskJoined': if (empty($desk) || empty($deskMongoId)) { throw new BadRequestHttpException('Missing required fields'); } $response = HelpDesk::join($desk['id'], $accountId); Yii::$app->tuisongbao->triggerEvent(HelpDesk::EVENT_ONLINE_STATUS, [], [HelpDesk::CHANNEL_GLOBAL . $accountId]); break; case 'deskLeft': if (empty($desk) || empty($deskMongoId)) { throw new BadRequestHttpException('Missing required fields'); } // cannot get accountId when logout from work order system first $cid = new \MongoId($this->getParams('cid')); $accountId = empty($accountId) ? $cid : $accountId; $response = HelpDesk::leave($desk['id'], $accountId, $extra); Yii::$app->tuisongbao->triggerEvent(HelpDesk::EVENT_ONLINE_STATUS, [], [HelpDesk::CHANNEL_GLOBAL . $accountId]); break; case 'clientJoined': if (empty($client)) { throw new BadRequestHttpException('Missing required fields'); } $client['nick'] = "guest-" . $client['openId']; $accountId = $this->getParams('cid'); if (empty($accountId)) { throw new BadRequestHttpException('Missing accountId'); } $accountId = new \MongoId($accountId); $client['accountId'] = $accountId; $conversation = ChatConversation::findOpenByClientId($client['openId'], $accountId); if (!empty($conversation)) { //Return the original conversation information if the convesation is not closed $response = ['status' => 'ok', 'desk' => $conversation->desk, 'client' => $conversation->client, 'channel' => $conversation->conversation, 'conversationId' => (string) $conversation->_id, 'lastChatTime' => $conversation->lastChatTime, 'chatTimes' => ChatConversation::getChatTimes($conversation->client['openId'], $accountId)]; } else { $response = HelpDesk::connect($client, $accountId); } break; case 'clientLeft': if (empty($conversationId)) { throw new BadRequestHttpException('Missing required fields'); } $accountId = $this->getParams('cid'); if (empty($accountId)) { throw new BadRequestHttpException("Missing accountId"); } $accountId = new \MongoId($accountId); $response = HelpDesk::disconnect(new \MongoId($conversationId), $extra); break; case 'clientPing': if (empty($client) || empty($client['openId'])) { throw new BadRequestHttpException("missing client field or openId for client"); } $response = ['status' => 'ok']; if (PendingClient::setLastPingTimeByOpenId($client['openId']) == 0) { $response['status'] = 'fail'; } break; case 'deskPing': if (empty($desk) || empty($desk['id'])) { throw new BadRequestHttpException("missing desk field or id for desk"); } $response = ['status' => 'ok']; if (!HelpDesk::setLastPingTimeById($desk['id'], $accountId)) { $response['status'] = 'fail'; } break; case 'pendingLeft': if (empty($client) || empty($client['openId'])) { throw new BadRequestHttpException("missing client"); } $status = PendingClient::deleteAll(['openId' => $client['openId']]) ? 'ok' : 'fail'; $response['status'] = $status; break; //for develop only, query the conversation status //for develop only, query the conversation status case 'status': $response['online'] = $cache->get('conversations' . $accountId); $response['onsocket'] = $cache->get('wm-online-desks' . $accountId); if (!empty($desk['id'])) { $response['webhook-time'] = $cache->get('wm-update-time' . $desk['id']); } break; default: throw new BadRequestHttpException('Illegal event name'); break; } return array_merge($response, ['sentTime' => TimeUtil::msTime()]); }
public function perform() { $args = $this->args; $message = $args['message']; $type = $message['msgType']; $content = $message['content']; $WEConnectAccountInfo = $args['account']; $WEConnectUserInfo = $args['user']; $accountId = new \MongoId($args['accountId']); $accountInfoType = null; $source = null; switch ($WEConnectAccountInfo['channel']) { case 'WEIBO': $source = ChatConversation::TYPE_WEIBO; $accountInfoType = 'WEIBO'; break; case 'ALIPAY': $source = ChatConversation::TYPE_ALIPAY; $accountInfoType = 'ALIPAY'; break; case 'WEIXIN': $source = ChatConversation::TYPE_WECHAT; $accountInfoType = $WEConnectAccountInfo['accountType']; break; default: throw new BadRequestHttpException("Unsupported channel type"); break; } $client = ['nick' => $WEConnectUserInfo['nickname'], 'avatar' => $WEConnectUserInfo['headerImgUrl'], 'openId' => $WEConnectUserInfo['id'], 'originId' => $WEConnectUserInfo['originId'], 'source' => $source, 'sourceChannel' => $WEConnectUserInfo['accountId'], 'accountId' => $accountId, 'accountInfo' => ['type' => $accountInfoType, 'name' => $WEConnectAccountInfo['name']]]; ResqueUtil::log(['message' => 'get message from wechat', 'WEConnectAccountInfo' => $WEConnectAccountInfo, 'WEConnectUserInfo' => $WEConnectUserInfo, 'accountId' => $accountId]); if (empty($type) || empty($content)) { ResqueUtil::log(['message' => 'missing required fields', 'senario' => 'accepting messages from WeConnect', 'WEConnectAccountInfo' => $WEConnectAccountInfo, 'WEConnectUserInfo' => $WEConnectUserInfo]); HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_ERROR); return; } try { if ('EVENT' === $type) { switch ($content) { case 'CONNECT': $isInWorkingHour = HelpDeskSetting::isInWorkingHours($accountId); if ($isInWorkingHour) { if (empty($WEConnectAccountInfo)) { ResqueUtil::log(['message' => 'Account parameters missing', 'senario' => 'Wechat message, event connect']); HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_ERROR); return; } //check if the wechat enduser has connected to a helpdesk $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId); if (!empty($conversation)) { ResqueUtil::log(['message' => 'Have connect to helpdesk already', 'senario' => 'Wechat message, event connect', 'user' => $WEConnectUserInfo, 'accountId' => $accountId]); HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_CUSTOM, ChatConversation::NO_DUPLICATE_CLIENT); return; } return HelpDesk::connect($client, $accountId); } else { //send the disconnect event to WeConnect Yii::$app->weConnect->sendCustomerServiceMessage($WEConnectUserInfo['id'], $WEConnectUserInfo['accountId'], ['msgType' => ChatConversation::WECHAT_MESSAGE_TYPE_EVENT, 'content' => 'DISCONNECT']); HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_NONWORKING); } break; case 'DISCONNECT': //get the conversationId $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId, ['type' => 'left']); if (empty($conversation)) { return ['status' => 'ok']; } //disconnect return HelpDesk::disconnect($conversation->_id, ['type' => 'brake']); break; default: throw new BadRequestHttpException("Unsupported event content type"); break; } } else { //get the conversation information $conversation = ChatConversation::findOpenByClientId($WEConnectUserInfo['id'], $accountId); if (empty($conversation)) { $pendingClient = PendingClient::findOne(['openId' => $WEConnectUserInfo['id'], 'accountId' => $accountId]); if (!empty($pendingClient)) { HelpDesk::sendSystemReplyByType($client, $accountId, HelpDeskSetting::REPLY_WAITTING); return ['status' => 'ok']; } else { return HelpDesk::connect($client, $accountId); } } $desk = $conversation->desk; $desk['id'] = (string) $desk['id']; $client = $conversation->client; $sentTime = TimeUtil::msTime(); //trigger send message event $name = ChatConversation::EVENT_CHAT_MESSAGE; $data = ['conversationId' => (string) $conversation->_id, 'desk' => $desk, 'client' => $client, 'chatMessage' => ['content' => ['msgType' => $type, 'body' => $content], 'sentTime' => $sentTime], 'isReply' => false]; //add chatMessage record $chatMessage = ChatMessage::saveRecord(['msgType' => $type, 'body' => $content], $sentTime, false, $conversation->_id, $accountId); //helpDesk web client message $channels = [ChatConversation::getChannelName($desk['id'], $client['openId'])]; $data['messageId'] = (string) $chatMessage->_id; Yii::$app->tuisongbao->triggerEvent($name, $data, $channels); //helpDesk mobile client message $pushExtra = ['messageId' => (string) $chatMessage->_id, 'openId' => $client['openId'], 'conversationId' => (string) $conversation->_id, 'sentTime' => $chatMessage->sentTime]; $pushMessage = empty($client['nick']) ? $content : $client['nick'] . ':' . $content; ChatConversation::pushMessage($desk['id'], ChatConversation::EVENT_CHAT_MESSAGE, $pushExtra, $pushMessage); return ['status' => 'ok']; } } catch (Exception $e) { LogUtil::error(['message' => $e->getMessage()], 'helpdesk'); return HelpDesk::sendSystemReplyByType($client, $account, HelpDeskSetting::REPLY_ERROR); } }
/** * Push the client to the pending client queue * @param array $client * @author Devin.Jin */ public static function enQueue($client) { if (empty(self::findOne(['openId' => $client['openId']]))) { $pendingClient = new PendingClient(); $pendingClient->load($client, ''); $pendingClient->lastPingTime = TimeUtil::msTime(); if (!$pendingClient->save()) { LogUtil::error(['error' => 'Save pending client failed', 'errors' => $pendingClient->getErrors()], 'helpdesk'); throw new ServerErrorHttpException("failed to save pending client for unknown reason"); } } }
/** * Get the clients in pending * @param string $deskId * @param MongoId $accountId * @return integer $count */ public static function connectPendingClient($deskId, $accountId, $count = 1) { $clients = PendingClient::deQueue($deskId, $count); foreach ($clients as $client) { self::createConnection($client, $deskId, $accountId); } }