/** * @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 static function isHelpDeskSet($channelId, $accountId) { $accountId = new \MongoId($accountId); $setting = HelpDeskSetting::findOne(['accountId' => $accountId, 'channels.id' => $channelId]); if (!empty($setting)) { return true; } return false; }
/** * Get the detail information of the helpDesk setting * * <b>Request Type: </b>GET<br/> * <b>Request Endpoint: </b>http://{server-domain}/api/helpdesk/settings<br/> * <b>Content-type: </b>Application/json<br/> * <b>Summary: </b>This api is for get the detail information of the helpDesk setting.<br/> * * <b>Response Example: </b> * <pre> * { * "maxW*aitTim": 3, * "maxClient": 5, * "ondutyTime": "8:00", * "offdutyTime": "18:00", * "systemReplies": [ * { * "name": "wait_for_service", * "type": "waitting", * "replyText": "wait", * "isEnabled": true * }, * { * "name": "close_service", * "type": "close", * "replyText": "close", * "isEnabled": true * }, * { * "name": "non_working_time", * "type": "nonworking", * "replyText": "non working time", * "isEnabled": true * }, * { * "name": "auto_brake", * "type": "brake", * "replyText": "brake", * "isEnabled": true * }, * { * "name": "connect_success", * "type": "success", * "replyText": "success", * "isEnabled": true * }, * { * "name": "desk_droping", * "type": "droping", * "replyText": "droping", * "isEnabled": true * } * ] * } * </pre> */ public function actionIndex() { $accountId = $this->getAccountId(); if (!$accountId) { $accountId = $this->getQuery('cid'); } if (empty($accountId)) { throw new BadRequestHttpException("AccountId is required"); } return HelpDeskSetting::getInstance($accountId); }
/** * Remove bound a channel account * * <b>Request Type</b>: DELETE<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/management/channel/{channelaccountId}<br/><br/> * <b>Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for removing bound a channel account. * <br/><br/> * * <b>Request Params</b>:<br/> * channelAccount: string, the channel account id<br/> * channelType: string, channel type("weibo" , "wechat", ...)<br/> * <br/><br/> * * <b>Response Params:</b><br/> * ack: integer, mark the delete result, 0 means query fail, 1 means delete successfully<br/> * msg: string, if query fail, it contains the error message<br/> * data: array, json array to deleted channel detail information<br/> * <br/><br/> * * <b>Request Example:</b><br/> * <pre> * { * "channelAccount": "gh_fdba39256c8e", * "channelType": "wechat", * } * </pre> * <br/><br/> * * <b>Response Example</b>:<br/> * <pre> * { * 'data' : [] * } * </pre> */ public function actionDelete($id) { $accountId = $this->getAccountId(); $params = $this->getParams(); if (empty($params['type']) || !in_array($params['type'], [Channel::WEIBO, Channel::ALIPAY])) { throw new BadRequestHttpException(Yii::t('common', 'data_error')); } $channelType = $params['type']; // Recovery micro-blog authorized access token if ($channelType == Channel::WEIBO) { $token = $params['weiboToken']; Yii::$app->weiboConnect->revokeWeiboToken($token); } //refine to call wechat-connection system api(Delete One Account) $result = Yii::$app->weConnect->deleteAccount($id); if ($result) { if (!Channel::disableByChannelIds($accountId, [$id])) { throw new \yii\web\ServerErrorHttpException("delete account channel failed"); } if (false === HelpDeskSetting::updateAll(['$pull' => ['channels' => ['id' => $id]]], ['accountId' => $accountId])) { throw new \yii\web\ServerErrorHttpException("delete helpdesk setting channel failed"); } } else { throw new \yii\web\ServerErrorHttpException("delete channel failed"); } return []; }
/** * 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']); } }
/** * Remove a website into help desk setting * * <b>Request Type: </b>PUT<br/> * <b>Request Endpoint: </b>http://{server-domain}/api/helpdesk/setting/remove-website<br/> * <b>Content-type: </b>Application/json<br/> * <b>Summary: </b>This api is for removing a website into help desk setting.<br/> * * <b>Request Example: </b> * <pre> * { * "settingId": '52d791327ae252f9149547cb', * "websiteId": '52d791307ae252f9149547c9' * } * </pre> */ public function actionRemoveWebsite() { $settingId = $this->getParams('settingId'); $websiteId = $this->getParams('websiteId'); if (!empty($websiteId) && !empty($settingId)) { $settingId = new \MongoId($settingId); // Add a channel into help desk setting $result = HelpDeskSetting::updateAll(['$pull' => ['websites' => ['id' => $websiteId]]], ['_id' => $settingId]); if ($result) { return $websiteId . ''; } throw new ServerErrorHttpException('remove website fail'); } throw new BadRequestHttpException('parameters missing'); }
/** * Create menu * * <b>Request Type</b>: POST<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/channel/menus<br/><br/> * <b>Response Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used to create/update menu. * <br/><br/> * * <b>Request Params</b>:<br/> * channelId: string<br/> * menu.name: string<br/> * menu.keycode: string<br/> * menu.type: string, VIEW or CLICK<br/> * menu.subMenus.name: string<br/> * menu.subMenus.type: string<br/> * menu.subMenus.msgType: TEXT or NEWS<br/> * menu.subMenus.content: string, if TEXT<br/> * menu.subMenus.content.articles: array, if NEWS<br/> * <br/><br/> * * <br/><br/> * * <b>Request Example</b>:<br/> * <pre> * { * "channelId": "5473ffe7db7c7c2f0bee5c71", * "menu": [ * { * ... * }, * { * "name": "去踢球", * "type": "CLICK", * "subMenus": [ * { * "name": "menu1-sub1", * "type": "VIEW", * "msgType": "TEXT", * "content": "hello world" * }, * { * "name": "menu1-sub2", * "type": "VIEW", * "msgType": "URL", * "content": "http://www.baidu.com" * }, * { * "name": "menu1-sub3", * "type": "VIEW", * "msgType": "NEWS", * "content": { * "articles": [ * { * "title": "新闻", * "description": "APEC会议举行第三天", * "sourceUrl": "http://www.baidu.com", * "picUrl": "http://www.baidu.com/image.jpg" * }, * { * "title": "新闻", * "description": "APEC会议举行第三天", * "sourceUrl": "http://www.baidu.com", * "picUrl": "http://www.baidu.com/image.jpg" * } * ] * } * } * ] * }, * { * ... * } * ] * } * * <b>Response Params:</b><br/> * msg: string, if query fail, it contains the error message<br/> * <br/><br/> * * <br/><br/> * * <b>Response Example</b>:<br/> * <pre> * { * "message": "OK" * } * </pre> */ public function actionCreate() { $menu = $this->getParams(); $channelId = $this->getChannelId(); $accountId = $this->getAccountId(); unset($menu['channelId']); $actions = Yii::$app->channelMenu->getMenuActions($channelId, $accountId, true); $result = Yii::$app->weConnect->createMenu($channelId, $menu['menu'], $actions); if ($result) { $conditon = ['accountId' => $accountId, 'channels.id' => $channelId]; $helpDeskSetting = HelpDeskSetting::findOne($conditon); if (!empty($helpDeskSetting)) { $isSet = Yii::$app->weConnect->isSetHelpDesk($menu['menu']); $channelResult = HelpDeskSetting::updateAll(['$set' => ['channels.$.isSet' => $isSet]], $conditon); if (!$channelResult) { throw new ServerErrorHttpException('Set menu channel status fail.'); } } return ['message' => 'OK']; } else { throw new ServerErrorHttpException('Create menu fail.'); } }
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); } }
/** * Send reply message to WeConnect user by type * @param array $client * @param string $accountId * @param string $type */ public static function sendSystemReplyByType($client, $accountId, $type, $content = null) { $needReplySourceArray = [ChatConversation::TYPE_WECHAT, ChatConversation::TYPE_WEIBO, ChatConversation::TYPE_ALIPAY]; if (in_array($client['source'], $needReplySourceArray)) { $helpDeskSetting = HelpDeskSetting::getInstance($accountId); $defaultSystemReplies = $helpDeskSetting->systemReplies; if ($type !== HelpDeskSetting::REPLY_CUSTOM) { foreach ($defaultSystemReplies as $defaultSystemReply) { if ($defaultSystemReply['type'] === $type) { $content = $defaultSystemReply['isEnabled'] ? $defaultSystemReply['replyText'] : ''; } } } if (!empty($content)) { $message = ['msgType' => ChatMessage::MSG_TYPE_TEXT, 'content' => $content, 'createTime' => TimeUtil::msTime()]; Yii::$app->weConnect->sendCustomerServiceMessage($client['openId'], $client['sourceChannel'], $message); } else { if ($content === null) { throw new ServerErrorHttpException('Incorrect name for default helpdesk system reply'); } } } }