/** * @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']); } } } } } }
/** * 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("密码错误"); } }
/** * Provide card * * <b>Request Type</b>: POST<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/member/card/provide-card<br/><br/> * <b>Response Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for provide card. * <br/><br/> * * <b>Request Params</b>:<br/> * cardId: string<br/> * cardNumbers: Array, card number<br/> * names: Array * tags: Array<br/> * cardExpiredAt: timestamp<br/> * <br/><br/> * * <b>Response Params:</b><br/> * message: * <br/><br/> * * <br/><br/> * * <b>Response Example</b>:<br/> * <pre> * {"message" : "OK"} * </pre> */ public function actionProvideCard() { $params = $this->getParams(); $accountId = $this->getAccountId(); $params['accountId'] = $accountId . ''; if (empty($params['cardId'])) { throw new BadRequestHttpException('param error'); } if (empty($params['cardExpiredAt'])) { throw new InvalidParameterException(['schedule-picker' => \Yii::t('common', 'required_filed')]); } $cardId = new \MongoId($params['cardId']); $card = MemberShipCard::findByPk($cardId); if (empty($card)) { throw new BadRequestHttpException(\Yii::t('member', 'no_card_find')); } if ($card->isAutoUpgrade) { throw new InvalidParameterException(Yii::t('member', 'error_issue_auto_card')); } if ($params['cardExpiredAt'] < TimeUtil::msTime()) { throw new InvalidParameterException(['schedule-picker' => \Yii::t('member', 'not_less_than_current')]); } $members = []; if (!empty($params['cardNumbers']) && is_array($params['cardNumbers'])) { $members = Member::getByCardNumbers($params['cardNumbers']); if (empty($members)) { throw new InvalidParameterException(['cardNumber' => \Yii::t('member', 'no_member_find')]); } } else { if (!empty($params['names']) && is_array($params['names'])) { $members = Member::getByNames($params['names']); if (empty($members)) { throw new InvalidParameterException(['memberNames' => \Yii::t('member', 'no_member_find')]); } } else { if (!empty($params['tags']) && is_array($params['tags'])) { $members = Member::getByTags($params['tags']); if (empty($members)) { throw new InvalidParameterException(['memberTags' => \Yii::t('member', 'no_member_find')]); } } } } $memberIds = Member::getIdList($members); Member::updateAll(['$set' => ['cardId' => $cardId, 'cardExpiredAt' => $params['cardExpiredAt']]], ['_id' => ['$in' => $memberIds]]); return ['message' => 'OK']; }
/** * Send Mass message * * <b>Request Type</b>: POST<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/channel/mass-messages<br/><br/> * <b>Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for sending mass message. * <br/><br/> * * <b>Request Params</b>:<br/> * channelId: string<br/> * scheduleTime: long, the scheduled time, or empty which means to send right now<br/> * userQuery.tags: array<br/> * userQuery.gender: male, female or empty<br/> * userQuery.country: string<br/> * userQuery.province: string<br/> * userQuery.city: string<br/> * msgType: TEXT or MPNEWS<br/> * content: string, if TEXT<br/> * content.articles, if MPNEWS<br/> * mixed: bool<br/> * <br/><br/> * * <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() { $massmessage = $this->getParams(); $channelId = $this->getChannelId(); if (!empty($massmessage['scheduleTime']) && $massmessage['scheduleTime'] < TimeUtil::msTime()) { throw new InvalidParameterException(['schedule-picker' => Yii::t('channel', 'schedule_time_error')]); } unset($massmessage['channelId']); $result = Yii::$app->weConnect->createMassMessage($channelId, $massmessage); if ($result) { return ['message' => 'OK']; } else { throw new ServerErrorHttpException('Create mass message fail.'); } }
/** * Check bind. * * <b>Request Type</b>: GET<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/mobile/check-bind<br/><br/> * <b>Response Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for check bind. * <br/><br/> * * <b>Request Params</b>:<br/> * <br/><br/> * * <b>Response Params:</b><br/> * redirect<br/> * <br/><br/> * * <br/><br/> * * <b>Response Example</b>:<br/> * <pre> * redirect('http://dev.cp.augmarketing.cn/mobile/center?openId=3DoTAN2jmRmInqhC_CDLN7aSTzvfzo') or * redirect('http://dev.cp.augmarketing.cn/mobile/center?memberId=549a73c3e9c2fb8d7c8b4569') * </pre> */ public function actionCheckBind($type = '', $param = '') { $params = $this->getQuery(); if (empty($params['state'])) { throw new BadRequestHttpException('missing params state'); } $channelId = $params['state']; $channelInfo = Yii::$app->weConnect->getAccounts($channelId); if (empty($channelInfo[0]['channel'])) { throw new BadRequestHttpException('invalid channelId'); } $userChannel = $channelInfo[0]['channel']; $redirect = !empty($type) ? base64_decode($param) : ''; $mainDomain = Yii::$app->request->hostInfo; if ($userChannel == Account::WECONNECT_CHANNEL_ALIPAY) { if (empty($params['auth_code'])) { throw new BadRequestHttpException('missing param auth_code'); } LogUtil::info(['params' => $params, 'channelId' => $channelId, 'message' => 'alipay info'], 'channel'); if ($params['scope'] == 'auth_userinfo') { $alipayUserInfo = Yii::$app->weConnect->getAlipayUserInfo($channelId, $params['auth_code']); $openId = $alipayUserInfo['originId']; } else { //call weconnect to get openId $openId = Yii::$app->weConnect->getAlipayOpenId($channelId, $params['auth_code']); } } else { if (empty($params['openId'])) { $openIdInfo = $this->getOriginAndOpenId($params); $origin = $openIdInfo['origin']; $openId = $openIdInfo['openId']; } else { $origin = Member::WECHAT; $openId = $params['openId']; } } //get member unionId from weconnect try { if (empty($alipayUserInfo)) { //to suport alipay,because alipay did not get followers again $follower = Yii::$app->weConnect->getFollowerByOriginId($openId, $channelId); } else { $follower = $alipayUserInfo; } } catch (ApiDataException $e) { LogUtil::info(['WeConnect Exception' => 'Get follower info error', 'exception' => $e], 'channel'); $follower = null; } //if follower not subscribed and (follower must subscribe before redirect or origin is weibo), redirect if ((empty($follower) || isset($follower['subscribed']) && $follower['subscribed'] != true) && ($this->mustSubscribe($redirect) || $origin === Member::WEIBO)) { return $this->redirect($this->getSubscribePage($origin, $channelId, $type, $redirect)); } //if the channel is alipay,we need to judge the member info whether exists,if the info is empty,wo call the first url if ($userChannel == Account::WECONNECT_CHANNEL_ALIPAY) { if (isset($follower['authorized']) && $follower['authorized'] == false) { if ($params['scope'] == 'auth_userinfo') { LogUtil::info(['message' => 'weConnect authorized fail', 'follower' => $follower], 'channel'); } else { $redirectUrl = $mainDomain . '/api/mobile/check-bind'; if (!empty($type) && !empty($param)) { $redirectUrl .= "/{$type}/{$param}"; } $redirectUrl .= '?state=' . $channelId . '&appId=' . $params['appId']; $redirectUrl = urlencode($redirectUrl); $url = "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?" . "app_id=" . $params['appId'] . "&auth_skip=false&scope=auth_userinfo&redirect_uri={$redirectUrl}"; LogUtil::info(['message' => 'can not get detailed follower info', 'url' => $url, 'follower' => $follower], 'channel'); return $this->redirect($url); } } else { LogUtil::info(['message' => 'authorized', 'follower' => $follower], 'channel'); } } if (!empty($follower['unionId'])) { //unionId exists $unionId = $follower['unionId']; $member = Member::getByUnionid($unionId); if (empty($member)) { //no unionId but openId $member = Member::getByOpenId($openId); if (!empty($member)) { $member->unionId = $unionId; $member->save(true, ['unionId']); } } } else { if (!empty($follower['originId'])) { $unionId = ''; $member = Member::getByOpenId($openId); } else { if (empty($follower) && !empty($params['appid']) && $userChannel == Account::WECONNECT_CHANNEL_WEIXIN) { LogUtil::info(['message' => 'Failed to get follower info', 'follower' => $follower, 'params' => $params]); $appId = $params['appid']; // not a follower, oAuth2.0 to get user_info $member = Member::getByOpenId($openId); if (empty($member)) { $component = Yii::$app->weConnect->getComponentToken(); $componentAppId = $component['componentAppId']; $state = $channelId; $redirectUrl = Yii::$app->request->hostInfo . '/api/mobile/user-info'; if (!empty($redirect)) { $redirectUrl = $redirectUrl . '/' . $type . '/' . $param; } LogUtil::info(['message' => 'oauth2 user_info redirecturl', 'url' => $redirectUrl]); $redirectUrl = urlencode($redirectUrl); $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$appId}&redirect_uri={$redirectUrl}&response_type=code&scope=snsapi_userinfo&state={$state}&component_appid={$componentAppId}#wechat_redirect"; return $this->redirect($url); } else { //member exists, will redirect to member center or 403 } } else { LogUtil::error(['Mobile member' => 'Failed to get follower info']); return $this->redirect('/mobile/common/403'); } } } LogUtil::info(['message' => 'Bind with follower', 'follower' => $follower], 'channel'); if (empty($member)) { //if not exist redirect, get unionId to bind //urlencode to avoid lose $redirect query string when frontend get $redirect $redirect = urlencode($redirect); $redirectUrl = $mainDomain . '/mobile/member'; if ($type == self::TYPE_REDIRECT) { $redirectUrl .= '/activate'; } else { $redirectUrl .= '/center'; } $redirectUrl .= "?openId={$openId}&channelId={$channelId}&unionId={$unionId}&redirect={$redirect}&redirectType={$type}"; } else { //if member is disabled, redirect to 403 if ($member->isDisabled) { return $this->redirect('/mobile/common/403'); } //if exist redirect to member center $social = ['channel' => $channelId, 'openId' => $openId, 'origin' => $origin, 'originScene' => empty($follower['firstSubscribeSource']) ? '' : $follower['firstSubscribeSource']]; $this->addNewSocial($member, $social); $memberId = $member['_id'] . ''; if ($type == self::TYPE_REDIRECT) { $str = strpos($redirect, '?') !== false ? '&' : '?'; $redirectUrl = $redirect . $str . "quncrm_member={$memberId}"; } else { $accountId = new \MongoId($member['accountId']); $token = Token::createForMobile($accountId); if (empty($token['accessToken'])) { throw new ServerErrorHttpException('Failed to create token for unknown reason.'); } $accessToken = $token['accessToken']; $this->setAccessToken($accessToken); if ($type == self::TYPE_REDIRECT_INSIDE) { $str = strpos($redirect, '?') !== false ? '&' : '?'; $redirectUrl = $redirect . $str . "memberId={$memberId}&channelId={$channelId}"; } else { $redirectUrl = $mainDomain . "/mobile/member/center?memberId={$memberId}&channelId={$channelId}"; if (!empty($member->cardExpiredAt) && $member->cardExpiredAt < TimeUtil::msTime()) { $redirectUrl = $redirectUrl . '&cardExpired=1'; } else { $redirectUrl = $redirectUrl . '&cardExpired=0'; } } } } return $this->redirect($redirectUrl); }
/** * 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']); } }
/** * conver the value for date */ public static function converTimeValue($sourceKey) { //to surport to use dot if (strpos($sourceKey, '.')) { $sourceKey = str_replace('.', '-', $sourceKey); } if (!is_numeric($sourceKey) || count($sourceKey) < 10) { $sourceKey = TimeUtil::msTime(strtotime($sourceKey)); } return $sourceKey; }
/** * Get the latest documents of the pending clients. * The clients should ping the server in the threshold or sourced from WeChat. * @param Integer $count the count of the elements to be dequeued * @return array the client. */ public static function deQueue($deskId, $count = 1) { $helpdesk = HelpDesk::findOne($deskId); $condition = ['$or' => [['lastPingTime' => ['$gt' => TimeUtil::msTime(time() - self::PING_THRESHOLD)]], ['source' => ['$in' => [ChatConversation::TYPE_WECHAT, ChatConversation::TYPE_WEIBO, ChatConversation::TYPE_ALIPAY]]]], 'accountId' => $helpdesk->accountId]; if (!empty($helpdesk->tags)) { $condition['tags'] = ['$in' => $helpdesk->tags]; $pendingClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count)->all(); if (count($pendingClients) < $count) { $condition['tags'] = ['$nin' => $helpdesk->tags]; $excludeTagClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count - count($pendingClients))->all(); $pendingClients = array_merge_recursive($pendingClients, $excludeTagClients); } } else { $pendingClients = self::find()->where($condition)->orderBy(['requestTime' => SORT_ASC])->limit($count)->all(); } $clients = []; foreach ($pendingClients as $pendingClient) { $client = ['nick' => $pendingClient->nick, 'avatar' => $pendingClient->avatar, 'openId' => $pendingClient->openId, 'source' => $pendingClient->source, 'sourceChannel' => $pendingClient->sourceChannel]; if ($pendingClient->accountInfo) { $client['accountInfo'] = $pendingClient->accountInfo; } $clients[] = $client; $pendingClient->delete(); } return $clients; }
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); } }
/** * Create interact message * * <b>Request Type</b>: POST<br/><br/> * <b>Request Endpoint</b>:http://{server-domain}/api/channel/messages<br/><br/> * <b>Response Content-type</b>: application/json<br/><br/> * <b>Summary</b>: This api is used for create interact messages. * <br/><br/> * * <b>Request Params</b>:<br/> * channelId: string<br/> * fromUser: string, channel account id like "gh_68ac2bff67ba"<br/> * toUser: string, the user's origin id<br/> * msgType: TEXT or NEWS<br/> * content: string, if TEXT<br/> * content.articles, if NEWS<br/> * <br/><br/> * * <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() { $toUserId = $this->getParams('toUser'); $channelId = $this->getChannelId(); $message = ['msgType' => $this->getParams('msgType'), 'createTime' => TimeUtil::msTime()]; switch ($message['msgType']) { case 'TEXT': $message['content'] = $this->getParams('content'); break; case 'NEWS': $content = $this->getParams('content'); $message['content'] = $content; break; default: # code... break; } $result = \Yii::$app->weConnect->sendCustomerServiceMessage($toUserId, $channelId, $message); if ($result) { return ['message' => 'OK']; } else { throw new ServerErrorHttpException('Create message fail.'); } }
/** * This function is used for set last ping time in cache for helpdesk * @param string $deskId * @param string $accountId * @return boolean Whether the data is stored correctly */ public static function setLastPingTimeById($deskId, $accountId) { $cache = Yii::$app->cache; $pingTimes = $cache->get(self::CACHE_PREFIX_HELPDESK_PING . $accountId); if (empty($pingTimes)) { $pingTimes = []; } $pingTimes[$deskId] = TimeUtil::msTime(); return $cache->set(self::CACHE_PREFIX_HELPDESK_PING . $accountId, $pingTimes); }