/** * @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']); } } } } } }
public function actionIndex() { $accountId = $this->getAccountId(); $currentHelpdeskId = $this->getUserId(); $query = HelpDesk::find(); $clientOpenId = $this->getQuery('clientOpenId'); if ($orderBy = $this->getQuery('orderBy')) { if (StringUtil::isJson($orderBy)) { $orderBy = Json::decode($orderBy, true); foreach ($orderBy as $key => $value) { if ($value === 'asc' || $value === 'ASC') { $orderBy[$key] = SORT_ASC; } else { $orderBy[$key] = SORT_DESC; } } } else { $orderBy = [$orderBy => SORT_DESC]; } $query->orderBy($orderBy); } $allHelpdesks = $query->where(['accountId' => $accountId, 'isDeleted' => false, 'isActivated' => true, 'isEnabled' => true])->andWhere(['not in', '_id', [$currentHelpdeskId]])->orderBy(['clientCount' => SORT_ASC])->all(); $result = []; if ($allHelpdesks) { foreach ($allHelpdesks as $helpdesk) { array_push($result, $helpdesk->toArray()); } } $lastChatConversation = ChatConversation::getLastChatByClient($clientOpenId, $currentHelpdeskId, $accountId); if ($lastChatConversation) { $lastServeHelpdeskId = (string) $lastChatConversation->desk['id']; if (!empty($result)) { $allOnlineHelpdesks = []; foreach ($result as $index => $item) { if ($item['isOnline'] && $item['id'] === $lastServeHelpdeskId) { $item['isLastChat'] = true; array_unshift($allOnlineHelpdesks, $item); } else { if ($item['isOnline']) { array_push($allOnlineHelpdesks, $item); } } } $result = $allOnlineHelpdesks; } } return $result; }
/** * Login * * <b>Request Type</b>: POST<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/chat/site/login<br/><br/> * <b>Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for the help desk to login. * <br/><br/> * * <b>Request Params</b>:<br/> * email: string, the user email, required<br/> * password: string, the user password, required<br/> * <br/><br/> * * <b>Response Params:</b><br/> * ack: integer, mark the create result, 0 means create successfully, 1 means create fail<br/> * msg: string, if create fail, it contains the error message<br/> * data: array, json array to describe the users detail information<br/> * <br/><br/> * * <b>Request Example:</b><br/> * <pre> * { * "email" : "*****@*****.**", * "password" : "abc123" * } * </pre> * <br/><br/> * * <b>Response Example</b>:<br/> * <pre> * { * 'ack' : 1, * 'data' : { * "accessToken" : "7f2d1e92-9629-8429-00be-2d9c6d64acdb", * "userInfo" : { * "name" : "harry", * "avatar" : "path/to/avatar" * } * } * } * </pre> */ public function actionLogin() { $params = $this->getParams(); $deviceToken = $this->getParams('deviceToken'); $environment = $this->getParams('environment'); if (empty($params['email']) || empty($params['password'])) { throw new BadRequestHttpException("parameters missing"); } $helpdesk = HelpDesk::getByEmail($params['email']); if (empty($helpdesk)) { throw new ForbiddenHttpException("用戶不存在"); } if (!$helpdesk->isActivated) { throw new ForbiddenHttpException("用戶未激活,请激活后使用"); } if (!$helpdesk->isEnabled) { throw new ForbiddenHttpException("该账号已被禁用,请与管理员联系"); } if ($helpdesk->validatePassword($params['password'])) { $tokens = Token::getUnexpiredByUserId($helpdesk->_id); if (!empty($tokens)) { $data = ['isForcedOffline' => true, 'id' => $helpdesk->_id . '']; $accountId = $tokens[0]->accountId; Yii::$app->tuisongbao->triggerEvent(ChatConversation::EVENT_FORCED_OFFLINE, $data, [ChatConversation::CHANNEL_GLOBAL . $accountId]); //deviceToken changed, push forcedOffline if (empty($deviceToken) && !empty($helpdesk->deviceToken) || !empty($deviceToken) && !empty($helpdesk->deviceToken) && $deviceToken != $helpdesk->deviceToken) { $extra = ['deskId' => $helpdesk->_id . '', 'sentTime' => TimeUtil::msTime()]; ChatConversation::pushMessage($helpdesk->_id, ChatConversation::EVENT_FORCED_OFFLINE, $extra); } Token::updateAll(['$set' => ['expireTime' => new \MongoDate()]], ['_id' => ['$in' => Token::getIdList($tokens)]]); } $isFirstLogin = empty($helpdesk->lastLoginAt); $accessToken = Token::createByHelpDesk($helpdesk); if (isset($deviceToken)) { $helpdesk->loginDevice = HelpDesk::MOBILEAPP; } else { $helpdesk->loginDevice = HelpDesk::BROWSER; } $helpdesk->deviceToken = $deviceToken; $helpdesk->environment = $environment; $helpdesk->lastLoginAt = new \MongoDate(); $helpdesk->save(true, ['deviceToken', 'loginDevice', 'environment', 'lastLoginAt']); $userInfo = ['badge' => $helpdesk->badge, 'name' => $helpdesk->name, 'email' => $helpdesk->email, 'language' => $helpdesk->language, 'avatar' => empty($helpdesk->avatar) ? '' : $helpdesk->avatar, 'id' => (string) $helpdesk->_id, 'accountId' => (string) $helpdesk['accountId'], 'notificationType' => $helpdesk->notificationType, 'isFirstLogin' => $isFirstLogin]; return ["accessToken" => $accessToken['accessToken'], 'userInfo' => $userInfo]; } else { throw new ForbiddenHttpException("密码错误"); } }
public function actionIndex() { $deskId = $this->getQuery('deskId'); $clientOpenId = $this->getQuery('clientOpenId'); $sentTime = $this->getQuery('sentTime'); $page = $this->getQuery('page', 1); $perPage = $this->getQuery('per-page', 10); $start = ($page - 1) * $perPage; if (empty($clientOpenId) || empty($deskId) || !isset($sentTime)) { throw new BadRequestHttpException('Missing required fields'); } $accountId = $this->getAccountId(); $conversations = ChatConversation::getByClient($clientOpenId, $accountId, new \MongoId($deskId)); $conversationIds = ChatConversation::getIdList($conversations); $chatMessages = ChatMessage::getByConversations($conversationIds, $perPage, $start, $sentTime); $messages = []; foreach ($chatMessages as $chatMessage) { $messages[] = ['conversationId' => $chatMessage['conversationId'] . '', 'messageId' => $chatMessage->_id . '', 'content' => $chatMessage['content'], 'isReply' => $chatMessage['isReply'], 'sentTime' => $chatMessage['sentTime']]; } return $messages; }
public function actionIndex() { $params = $this->getQuery(); if (empty($params['memberId'])) { throw new BadRequestHttpException(Yii::t('common', 'parameters_missing')); } //get members openIds $member = Member::findByPk(new MongoId($params['memberId'])); $openIds = empty($member->socials) ? [] : ArrayHelper::getColumn($member->socials, 'openId'); $openIds[] = $member->openId; //get lastChatDate $lastConversation = ChatConversation::getLastByOpenIds($openIds); $lastChatDate = empty($lastConversation) ? '' : $lastConversation->date; //get conversations $params['openIds'] = $openIds; $accountId = $this->getAccountId(); $conversations = ChatConversation::search($params, $accountId); $result = $this->serializeData($conversations); $result['lastChatDate'] = $lastChatDate; return $result; }
public function actionStatistics() { $startTime = $this->getQuery('startTime'); $endTime = $this->getQuery('endTime'); $accountId = $this->getAccountId(); $condition = ['accountId' => $accountId, 'isDeleted' => BaseModel::NOT_DELETED]; if (!empty($startTime) && !empty($endTime)) { $condition['createdAt'] = ['$gt' => new \MongoDate(TimeUtil::ms2sTime($startTime)), '$lt' => new \MongoDate(TimeUtil::ms2sTime($endTime))]; } //get the original statistics from db.(raw data) $clientCount = ChatConversation::getClientCount($condition); $conversationCount = ChatConversation::count($condition); $clientMessageCount = ChatMessage::countClientMessage($condition); $conversationDailyStatistics = ChatConversation::getDailyData($condition); $messageDailyStatistics = ChatMessage::getDailyData($condition); //format the statistics for chart $categories = []; $messageCountSeries = []; $clientCountSeries = []; $conversationCountSeries = []; foreach ($conversationDailyStatistics as $conversationDay) { foreach ($messageDailyStatistics as $messageDay) { if ($conversationDay['date'] == $messageDay['date']) { $conversationDay['messageCount'] = $messageDay['messageCount']; } } if (empty($conversationDay['messageCount'])) { $conversationDay['messageCount'] = 0; } $categories[] = $conversationDay['date']; $messageCountSeries[] = $conversationDay['messageCount']; $clientCountSeries[] = $conversationDay['clientCount']; $conversationCountSeries[] = $conversationDay['conversationCount']; } $statistics = ['categories' => $categories, 'series' => [['name' => 'helpdesk_users_count', 'data' => $clientCountSeries], ['name' => 'helpdesk_sessions_count', 'data' => $conversationCountSeries], ['name' => 'helpdesk_sent_message_count', 'data' => $messageCountSeries]]]; return ['clientCount' => $clientCount, 'conversationCount' => $conversationCount, 'clientMessageCount' => $clientMessageCount, 'statistics' => $statistics]; }
/** * Tranfer client to another helpdesk * * <b>Request Type: </b>POST<br/> * <b>Request Endpoint: </b>http://{server-domain}/api/chat/conversation/transfer * <b>Summary: </b> This api is for transfer helpdesk.<br/> * * <b>Request Parameters: </b><br/> * accesstoken: string<br/> * deskId: string, id of the desk.<br/> * clientOpenId: string, the openId of the client.<br/> * conversationId: string, the id of chatConversation.<br/> * targetDeskId: string, the id of the target desk * * <b>Response Example: </b><br/> * { * "status" => "ok" * } */ public function actionTransfer() { $cache = Yii::$app->cache; $targetDeskId = $this->getParams('targetDeskId'); $conversationId = $this->getParams('conversationId'); $accountId = $this->getAccountId(); $conversations = $cache->get('conversations' . $accountId); if ($conversations) { $lastChatConversation = ChatConversation::findByPk(new \MongoId($conversationId)); if ($lastChatConversation->status === ChatConversation::STATUS_CLOSED) { throw new BadRequestHttpException('ChatConversation has been closed'); } $client = $lastChatConversation->client; $clientOpenId = $lastChatConversation->client['openId']; $deskMongoId = $lastChatConversation->desk['id']; $deskId = (string) $deskMongoId; $targetDeskMongoId = new \MongoId($targetDeskId); $helpDesk = HelpDesk::findByPk($deskMongoId); $targetHelpDesk = HelpDesk::findByPk($targetDeskMongoId); $maxClientLimit = HelpDeskSetting::getMaxClientCount($accountId); if ($targetHelpDesk->clientCount >= $maxClientLimit) { throw new BadRequestHttpException('Target helpdesk has exceed the max serve number'); } // Set the previous chat conversation status to 'closed' $lastChatConversation->status = ChatConversation::STATUS_CLOSED; if (!$lastChatConversation->update()) { LogUtil::error(['message' => 'Update previous helpdesk chatConversation status failed', 'error' => $lastChatConversation->errors], 'helpdesk'); throw new ServerErrorHttpException('Update previous helpdesk chatConversation status failed'); } else { HelpDesk::decClientCount($deskMongoId); } // Remove the client from original desk client list, and add to transfered desk client list foreach ($conversations[$deskId] as $index => $openId) { if ($openId == $clientOpenId) { unset($conversations[$deskId][$index]); } } $conversations[$targetDeskId][] = $clientOpenId; Yii::$app->cache->set('conversations' . $accountId, $conversations); // Create chatConversation record in db $chatConversation = ChatConversation::saveRecord($targetHelpDesk, $client, ChatConversation::STATUS_OPEN, $accountId); // Generate response data $newChannelName = ChatConversation::getChannelName($targetDeskId, $clientOpenId); $lastChatTime = ChatConversation::getLastChatTime($targetDeskMongoId, $clientOpenId, $accountId); $chatTimes = ChatConversation::getChatTimes($clientOpenId, $accountId); $previousDesk = $lastChatConversation->desk; $previousDesk['id'] = $deskId; $desk = $chatConversation->desk; $desk['id'] = $targetDeskId; $data = ['conversationId' => (string) $chatConversation->_id, 'desk' => $desk, 'previousDesk' => $previousDesk, 'client' => $client, 'channel' => $newChannelName, 'lastChatTime' => $lastChatTime, 'chatTimes' => $chatTimes, 'startTime' => TimeUtil::msTime()]; // Trigger transfer event to global channel, and the desk-client channel to make the client change listening channel Yii::$app->tuisongbao->triggerEvent(ChatConversation::EVENT_DESK_TRANSFER, $data, [ChatConversation::CHANNEL_GLOBAL . $accountId, $lastChatConversation->conversation]); // Push state $pushExtra = ['openId' => $clientOpenId, 'conversationId' => (string) $chatConversation->_id, 'previousDeskId' => $deskId, 'sentTime' => TimeUtil::msTime()]; $previousDeskName = empty($previousDesk['name']) ? '' : $previousDesk['name']; $pushMessage = str_replace('{desk}', $previousDeskName, ChatConversation::PUSH_MESSAGE_TRANSFER); ChatConversation::pushMessage($previousDesk['id'], ChatConversation::EVENT_DESK_TRANSFER, $pushExtra); ChatConversation::pushMessage($desk['id'], ChatConversation::EVENT_DESK_TRANSFER, $pushExtra, $pushMessage); LogUtil::info(['event' => ChatConversation::EVENT_DESK_TRANSFER, 'desk' => $desk, 'previousDesk' => $previousDesk, 'client' => $client], 'helpdesk'); return array_merge($data, ['status' => 'ok']); } }
public static function saveRecord($desk, $client, $status, $accountId) { $chatConversation = new ChatConversation(); $channelName = self::getChannelName($desk->_id, $client['openId']); $chatConversation->conversation = $channelName; $chatConversation->status = $status; $chatConversation->desk = ['id' => $desk->_id, 'badge' => $desk->badge, 'email' => $desk->email, 'avatar' => $desk->avatar, 'name' => $desk->name]; $chatConversation->client = $client; $chatConversation->accountId = $accountId; $chatConversation->lastChatTime = TimeUtil::msTime(); if (!$chatConversation->save()) { LogUtil::error(['message' => 'save chat conversation failed', 'error' => $chatConversation->errors], 'helpdesk'); throw new ServerErrorHttpException('save chatConversation failed'); } else { HelpDesk::incClientCount($desk->_id); } return $chatConversation; }
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); } }
public static function saveRecord($content, $sentTime, $isReply, $conversationId, $accountId) { $chatMessage = new ChatMessage(); $chatMessage->content = $content; $chatMessage->sentTime = $sentTime; $chatMessage->isReply = $isReply; $chatMessage->conversationId = $conversationId; $chatMessage->accountId = $accountId; if (!$chatMessage->save()) { LogUtil::error(['message' => 'save chatMessage failed', 'error' => $chatMessage->errors], 'helpdesk'); throw new ServerErrorHttpException('save chatConversation failed'); } //update the lastChatTime for chatConversation ChatConversation::setLastChatTime($sentTime, $conversationId); return $chatMessage; }
/** * HelpDesk offline * @param string $deskId * @param MongoId $accountId * @param Object $extra * @return array */ public static function leave($deskId, $accountId, $extra) { $cache = Yii::$app->cache; //remove it from conversation caches $conversations = $cache->get('conversations' . $accountId); unset($conversations[$deskId]); $cache->set('conversations' . $accountId, $conversations); //remove it from online caches self::cacheOnlineDesks($deskId, (string) $accountId, ChatConversation::EVENT_USER_REMOVED); //remove the helpdesk ping time $onlineHelpDesks = $cache->get(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId); unset($onlineHelpDesks[$deskId]); $cache->set(HelpDesk::CACHE_PREFIX_HELPDESK_PING . $accountId, $onlineHelpDesks); //get the conversation records in db according to the deskId $conversationInstances = ChatConversation::findOpenByDeskId(new \MongoId($deskId), $accountId); foreach ($conversationInstances as $conversationInstance) { $clientTemp = $conversationInstance->client; $desk = $conversationInstance->desk; //trigger deskLeft event $data = ['conversationId' => (string) $conversationInstance['_id'], 'desk' => $desk, 'client' => $clientTemp, 'extra' => $extra, 'sentTime' => TimeUtil::msTime()]; $channels = [ChatConversation::getChannelName($deskId, $clientTemp['openId'])]; Yii::$app->tuisongbao->triggerEvent(ChatConversation::EVENT_DESK_LEFT, $data, $channels); //set the status of the chatConversation to "closed" ChatConversation::closeById($conversationInstance['_id']); HelpDesk::sendSystemReplyByType($clientTemp, $accountId, isset($extra['type']) ? $extra['type'] : HelpDeskSetting::REPLY_CLOSE); } //flush the client count in db helpDesk::flushClientCount(new \MongoId($deskId)); LogUtil::info(['event' => 'deskLeft', 'deskId' => $deskId], 'helpdesk'); return ['status' => 'ok']; }